synchronized关键字是java中线程并发保证原子性的一种方式

  • 互斥锁
  • 可重入锁
  • 锁定的是一个堆上的对象
  • 在代码块上,方法上,静态方法上
  • 异常上自动释放锁
synchronized的使用方法
public class SeaTiger implements Runnable {
    Object o = new Object();
    @Override
    public void run() {
        synchronized (o) {
            System.out.print(Thread.currentThread().getName() + " ");
            for (int i = 0; i < 5; i++) {
                System.out.print(i + " ");
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        SeaTiger seatiger = new SeaTiger();
        for(int i=0; i<3; i++) {
            new Thread(seatiger, "seatiger" + i).start();
        }
    }
}

在上述代码中,synchronized (o)锁定的是o这个引用执行堆内存的真正对象.有五个线程同时去执行run方法,哪一个线程先获取锁先执行,其他线程就得等下执行线程释放锁.那个获取锁,哪个执行

执行结果
seatiger0 0 1 2 3 4
seatiger2 0 1 2 3 4
seatiger1 0 1 2 3 4

修改main方法的测试程序

public static void main(String[] args) {
        SeaTiger seatiger = new SeaTiger();
        SeaTiger seatiger2 = new SeaTiger();
        for(int i=0; i<3; i++) {
            new Thread(seatiger, "1seatiger" + i).start();
        }
        for(int i=0; i<3; i++) {
            new Thread(seatiger2, "2seatiger" + i).start();
        }
    }

执行结果
1seatiger0 2seatiger1 0 0 1 2 3 4
1seatiger2 1 0 2 1 3 2 4
3 4
1seatiger1 0 1 2seatiger2 0 1 2 3 2 4
3 4
2seatiger0 0 1 2 3 4

第二次的结果乱,原因分析
在第一个main方法中SeaTiger seatiger = new SeaTiger();只创建了一次对象,对应的new Object();也只有一次,所以从头到尾只有一把锁.
在第二个测试方法中,创建了两个对象,同时也就创建了两把锁,每个对象去执行自己的线程任务时,都会找自己需要的那把锁.
所以seatigerseatiger2相互之间是锁不住

如果多个对象相互之间互相锁住,只要保证获取的是同一把锁
其中一种的实现方式就是可以将通过索引获取的那个对象设置为单例模式
` static Object o;

static {
    o = new Object();
}`

这样可以保证o指向的对象为单列,这样不管多少个不同对象的执行线程获取的都是同一把锁
测试第二个方法
1seatiger0 0 1 2 3 4
2seatiger1 0 1 2 3 4
1seatiger2 0 1 2 3 4
1seatiger1 0 1 2 3 4
2seatiger0 0 1 2 3 4
2seatiger2 0 1 2 3 4

再次声明,不是锁定的代码快或者方法,锁定的是堆上一个实实在在的对象

作用方法上

synchronized关键字还可以作用于,方法上

    public synchronized void run() {
        
    }

这样写锁定的就是自身对象this,就是seatigerseatiger2自身所指向的对象相当于synchronized(this)

作用静态方法上

synchronized可以作用域静态方法上

public synchronized static void m() {
}

作用域静态方法上相当于锁定的是class对象synchronized(T.class)

因为每一个类都只有一个class对象,所以不管对象启动线程,获取的同时同一个把,作用和获取单列对象上的锁,效果一样

脏读问题(dirtyRead),对写方法加锁,对读方法不加锁
public class Seatiger {
    private String name;
    public synchronized void set() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.name = "langlihuge";
    }
    public String get(){
        return name;
    }
    
    public static void main(String[] args) throws Exception{
        Seatiger seatiger = new Seatiger();
        //业务写方法
        new Thread(()-> seatiger.set()).start();
        System.out.println(seatiger.get());
        Thread.sleep(300);
        System.out.println(seatiger.get());
    }
}

执行结果
null
langlihuge

对于一个加了锁的方法,读方法没有加锁,在读的时候,可能读要,写方法中没有完全处理完的数据.

可重入锁

一个同步方法可以调用另外一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该对象的锁.
也就是说synchronized获得的锁是可重入的
注意:两个同步方法中持有的是同一把锁..这把锁,会有一个标志记录,如果一个调另一个,那么这把锁的这个标记就是2

第一种情况

public class Seatiger {
    synchronized void f1() throws Throwable{
        System.out.println("f1 start()...");
        TimeUnit.SECONDS.sleep(1);
        f2();
    }    
    synchronized void f2() throws Throwable{
        TimeUnit.SECONDS.sleep(3);
        System.out.println("f2 start()...");
    }    
    public static void main(String[] args) {
        new Thread(()-> {
            try {
                new Seatiger().f1();
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }).start();
    }
}

f1中调用了f2中的方法,虽然都有锁一样的,但是仍然可以调用,可重入

第二种情况
继承中有可能发生的情形,子类调用父类的同步方法

public class T {
    synchronized void m() {
        System.out.println("m start");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("m end");
    }
    
    public static void main(String[] args) {
        new TT().m();
    }    
}
class TT extends T {
    @Override
    synchronized void m() {
        System.out.println("child m start");
        super.m();
        System.out.println("child m end");
    }
}

这里锁都是子类对象,所以是同一把锁

同步方法中出现异常,会释放锁

程序在执行过程中,如果出现异常,没有try,catch处理,默认情况下释放锁,其他线程,继续抢夺锁资源


seatiger
0 声望0 粉丝