java高并发、多线程(三)--synchronized

java高并发、多线程(三)

  • synchronized
    • 加锁的方式
    • synchronized的性质
    • synchronized锁升级
    • 优化
    • 注意事项

synchronized

先看下面例子:

public class SynchronizedTest {
    static int count = 0;
    public static void add() {
        for (int i = 0; i < 10000; i++) {
            count++;
        }
    }

    public static void main(String[] args) {
        List<Thread> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            list.add(new Thread(SynchronizedTest::add,"Thread-"+i));
        }
        list.forEach(t->t.start());
        list.forEach(t-> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println(count);
    }
}

运行结果:
java高并发、多线程(三)--synchronized_第1张图片
以上代码开启10个线程对count进行累加10000次,我们希望的结果是10万,但是最终结果是小于等于10万的。这是由于count++这个操作并不是原子操作,代码产生了同步问题,我们可以给相关代码加上锁来解决上面的问题。

加锁的方式

  • 同步代码块上加锁
	private static Object object = new Object();
    public static void add() {
        for (int i = 0; i < 10000; i++) {
            synchronized (object){
                count++;
            }
        }
    }
  • 方法上加锁
    public synchronized static void add() {
        for (int i = 0; i < 10000; i++) {
                count++;
        }
    }

运行结果:
java高并发、多线程(三)--synchronized_第2张图片

synchronized的性质

  • 保证线程间可见性
	static int count = 0;

我们可以观察到上述代码中,我们并没有给count变量采用volatile修饰,但是程序的运行结果是正确的,那么说明synchronized可以替代volatile,能够保证线程间的可见性。

那volatile可以替代synchronized吗?
我们对上述代码做如下修改:

	volatile static int count = 0;
    public /*synchronized*/ static void add() {
        for (int i = 0; i < 10000; i++) {
                count++;
        }
    }

运行结果:
java高并发、多线程(三)--synchronized_第3张图片
由运行结果可知:volatile并不能替代synchronized的作用,它不能保证多个线程共同修改共享变量所带来的不一致问题。

  • synchronized获得的是重入锁
    修改上述代码:
	static int count = 0;
    public synchronized static void add() {
        for (int i = 0; i < 10000; i++) {
                count++;
        }
        //调用另一个同步方法
        sub();
    }
    public synchronized static void sub() {
        for (int i = 0; i < 10000; i++) {
            count--;
        }
    }

运行结果:
java高并发、多线程(三)--synchronized_第4张图片
从运行结果可以知道,一个同步方法可以调用另一个同步方法。

synchronized锁升级

偏向锁
自旋锁
重量级锁

优化

  • 采用细粒度的锁,减少同步代码块中代码量

注意事项

  • 不使用字符串作为锁对象
    例如:
public class SynchronizedTest {

    String lock1 = "lock";
    String lock2 = "lock";

    public void m1(){
        synchronized (lock1){
            System.out.println("m1 start");
            while(true){}
        }
    }
    public void m2(){
        synchronized (lock2){
            System.out.println("m2 start");
            while(true){}
        }
    }

    public static void main(String[] args) {
        SynchronizedTest st = new SynchronizedTest();
        new Thread(st::m1).start();
        new Thread(st::m2).start();
    }
}

运行结果:
java高并发、多线程(三)--synchronized_第5张图片
由上面的例子可以看出:代码原意是想m1与m2方法获取不同的锁,但是由于string常量池的性质,导致两把锁实际上指向的是同一个对象,从而m1与m2方法仅能执行其中的一个。

  • 不要改变锁的引用
    例如:
public class SynchronizedTest {

    private Object o = new Object();

    public void m1(){
        synchronized (o){
            System.out.println("m1 start");
            while(true){}
        }
    }
    public void m2(){
        synchronized (o){
            System.out.println("m2 start");
            while(true){}
        }
    }

    public static void main(String[] args) {
        SynchronizedTest st = new SynchronizedTest();
        new Thread(st::m1).start();
        try {
            Thread.sleep(1000);
            //改变o指向的对象
            st.o = new Object();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(st::m2).start();
    }
}

运行结果:
java高并发、多线程(三)--synchronized_第6张图片
从这个例子可以看出,当改变了锁对象的引用后m2方法可以得到执行。因此锁对象建议加上final关键字修饰

	private final Object o = new Object();

你可能感兴趣的:(多线程,高并发,java,多线程)