Synchronized原理

110 阅读2分钟

Synchronized原理图解

image.png

为什么要使用Synchronized

//两个线程对初始值为0的静态变量,一个做自增,一个做自减,各自5000次,结果却不是0.
public class Test {

    static int counter = 0;

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            for (int j = 0; j < 5000; j++) {
                counter++;
            }
        }, "线程一");

        Thread t2 = new Thread(() -> {
            for (int k = 0; k < 5000; k++) {
                counter--;
            }
        }, "线程二");

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(counter);
    }
}

问题分析:以上的结果可能是正数,负数,0的原因在于Java中对静态变量的自增自减并不是原子性操作,如果要彻底理解,必须从字节码来进行分析,例如对于i++而言(i为静态变量),实际产生的JVM字节码指令为:

getstatic      i  //获取静态变量i的值
iconst_1          //准备常量1
iadd              //自增
putstatic      i  //将修改后的值存入静态变量i

而对应i--也是类似

getstatic      i  //获取静态变量i的值
iconst_1          //准备常量1
isub              //自减
putstatic      i  //将修改后的值存入静态变量i

而java的内存模型如下,完成静态变量的自增,自减需要在主存和工作内存中进行数据交换。

临界区

一个程序运行多个线程本身是不存在问题的,问题出在多个线程访问共享资源,多个线程读共享资源其实也没有问题,但在多个线程对共享资源读写时指令交错,就会出现问题,一段代码内如果存在对共享资源的多线程读写操作,那么这段代码被称为临界区。例如:

static int count = 0;

static void increment()
//临界区
{
    count++;
}

static void decrement()
//临界区
{
    count--;
}

当多个线程在临界区内执行,由于代码的执行序列不可预测而导致结果无法预测,从而发生了竞态条件

Synchronized的使用

为了避免临界区的竞态条件产生,有以下两种解决方案:

阻塞式:synchronized(对象锁), Lock

非阻塞式:原子变量

Synchronize被称为对象锁,它采用互斥的方式让同一时刻最多只有一个线程能持有对象锁,其它线程要是想获取这个对象锁时就会被阻塞住,这样就能保证有锁的线程可以安全的执行临界区的代码块,同时也避免了线程切换的问题。

synchronized(对象){
    //临界区
}

//1.修饰实例方法,此时,synchronized加锁的对象就是这个方法所在实例的本身。
public synchronized void add(){
       i++;
}

//2.修饰静态方法,此时,synchronized加锁的对象为当前静态方法所在类的Class对象。
public static synchronized void add(){
       i++;
}

//3.修饰代码块,此时synchronized加锁对象即为传入的这个对象实例。
public void add() {
    synchronized (this) {
        i++;
    }
}

案例:

public class Test {

    static int counter = 0;

    static Object obj = new Object();

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            for (int j = 0; j < 5000; j++) {
                synchronized (obj){
                    counter++;
                }
            }
        }, "线程一");

        Thread t2 = new Thread(() -> {
            for (int k = 0; k < 5000; k++) {
                synchronized (obj){
                    counter--;
                }
            }
        }, "线程二");

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(counter);
    }
}

//执行结果: 0