java 线程安全(二)synchronized

java 线程安全(一)中解释了线程安全问题。

解决线程安全

解决线程安全问题的根本在于对共享数据被加上互斥锁后,在同一个时刻,其他线程只能处于等待的状态,直到当前线程处理完毕释放该锁。

synchronized 可以保证某一时刻,只有一个线程访问某一方法或者代码块(主要是操作共享的数据)
synchronized 可以保证某一个线程操作的结果对其他线程是可见的(完全替代了 Volital 关键字)

synchronized 常见的三钟用法

  1. synchronized 修饰方法

修饰普通方法时相当于给当前实例对象加锁
有多个实例对象时就有多个锁。实例对象之间访问不互斥
如果多个方法都被synchronized修饰,则该对象在这多个方法之间也是互斥的,不能同时访问

  1. synchronized 修饰静态方法

修饰静态方法相对于给当前类加锁(静态方法属于类对象)
有多个实例对象时也只有1个锁,实例对象之间访问也是互斥

  1. synchronized 修饰代码块

修饰代码块时相当于给指定对象给加锁
使用时根据区分传递的实例对象,或者类对象实现加锁

代码实现举例

synchronized 修饰方法

  • 当 2 个线程去执行add方法时,如果没有加 synchronized 修饰,存在线程安全问题(2 个线程存在并行执行),得到的值可能小于 8000
  • 添加 synchronized 修饰实例方法后,相当于在 addThreadObject 实例对象上加了锁,2 个线程所属一个实例对象,执行 add 方法时互斥(同步执行),得到的值等于 8000

代码一:

@Slf4j
public class AddThread implements Runnable {
    static int num = 0;

    public synchronized void add() {
        for (int i = 0; i < 4000; i++) {
            num++;
        }
    }
    @Override
    public void run() {
        add();
    }

    public static void main(String[] args) throws InterruptedException {
        AddThread addThreadObject = new AddThread();
        Thread thread1 = new Thread(addThreadObject);
        Thread thread2 = new Thread(addThreadObject);
        // 1、当 2 个线程去执行add方法时,如果没有加 synchronized 修饰,存在线程安全问题(2 个线程存在并行执行),得到的值可能小于 8000
        // 2、添加 synchronized 修饰实例方法后,相当于在 addThreadObject 实例对象上加了锁,2 个线程所属一个实例对象,执行 add 方法时互斥(同步执行),得到的值等于 8000
        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();

        log.info("最终计算 i = " + AddThread.num);
    }
}

注意:synchronized 修饰方法锁的是实例对象。
如果存在下面情况:

  • 2 个线程对应 2 个实例对象时,如果用 synchronized 修饰实例方法,相当于在 addThreadObject1 实例对象上加了锁A, 在addThreadObject1 实例对象上加了锁B
  • 2 个线程可以同时获得锁(存在2 个线程存在并行执行),得到的值小于 8000
  • 解决这种困境的的方式是将synchronized作用于静态的increase方法

代码二:

// 省略了非核心代码
		AddThread addThreadObject1 = new AddThread();
        AddThread addThreadObject2 = new AddThread();

        Thread thread1 = new Thread(addThreadObject1);
        Thread thread2 = new Thread(addThreadObject2);
        // 2 个线程对应 2 个实例对象时,如果用 synchronized 修饰实例方法,相当于在 addThreadObject1 实例对象上加了锁A, 在addThreadObject1 实例对象上加了锁B
        // 2 个线程可以同时获得锁,存在线程安全问题(2 个线程存在并行执行),得到的值小于 8000
        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();
        log.info("最终计算 i = " + AddThread.num);

synchronized 修饰静态方法

  • 如果用 synchronized 修饰静态方法,相当于在AddThread.class 对象上加锁
  • 2 个线程对应 2 个实例对象时,但同一时刻也只有其中一个线程获得锁,执行 add 方法时互斥(同步执行),得到的值等于 8000

代码三:

// 省略了非核心代码
public  static synchronized void add() {
        for (int i = 0; i < 4000; i++) {
            num++;
        }
AddThread addThreadObject1 = new AddThread();
        AddThread addThreadObject2 = new AddThread();

        Thread thread1 = new Thread(addThreadObject1);
        Thread thread2 = new Thread(addThreadObject2);
        // 如果用 synchronized 修饰静态方法,相当于在AddThread.class 对象上加锁
        // 2 个线程对应 2 个实例对象时,但同一时刻也只有其中一个线程获得锁,执行 add 方法时互斥(同步执行),得到的值等于 8000
        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();

        log.info("最终计算 i = " + AddThread.num);

synchronized 修饰代码块

synchronized 修饰代码块–指定锁作用于类中静态成员对象

  • 如果用 synchronized 修饰代码块,锁作用与类中静态成员对象(静态对象隶属于类)
  • 2 个线程对应 3 个实例对象时,但同一时刻也只有其中一个线程获得锁,执行 add 方法时互斥(同步执行),得到的值等于 12000

代码四:

@Slf4j
public class AddThread implements Runnable {
    static int num = 0;
    static AddThread addThreadStatic = new AddThread();
    @Override
    public void run() {
        synchronized(addThreadStatic){
            for (int i = 0; i < 4000; i++) {
                num++;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
    AddThread addThreadObject1 = new AddThread();
        AddThread addThreadObject2 = new AddThread();

        Thread thread1 = new Thread(addThreadObject1);
        Thread thread3 = new Thread(addThreadObject1);
        Thread thread2 = new Thread(addThreadObject2);

        // 如果用 synchronized 修饰代码块,锁作用与类中的静态对象(静态对象隶属于类)
        // 2 个线程对应 3 个实例对象时,但同一时刻也只有其中一个线程获得锁,执行 add 方法时互斥(同步执行),得到的值等于 8000
        thread1.start();
        thread2.start();
        thread3.start();

        thread1.join();
        thread2.join();
        thread3.join();

        log.info("最终计算 i = " + AddThread.num);
    }
}

synchronized 修饰代码块–指定锁作用于当前实例对象

  • 如果用 synchronized 修饰代码块,锁做作用于当前对象时
  • 2 个线程对应 2 个实例对象时,相当于在 addThreadObject1 实例对象上加了锁A, 在addThreadObject1 实例对象上加了锁B,2 个线程可以同时获得锁,存在线程安全问题(2 个线程存在并行执行),得到的值小于 8000
  • 相当于上面 synchronized 修饰实例方法

代码五:

@Slf4j
public class AddThread implements Runnable {
    static int num = 0;
    static AddThread addThreadStatic = new AddThread();
    @Override
    public void run() {
        synchronized(this){
            for (int i = 0; i < 4000; i++) {
                num++;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
AddThread addThreadObject1 = new AddThread();
        AddThread addThreadObject2 = new AddThread();

        Thread thread1 = new Thread(addThreadObject1);
        Thread thread2 = new Thread(addThreadObject2);
        // 如果用 synchronized 修饰代码块,锁做作用于当前对象时
        // 2 个线程对应 2 个实例对象时,相当于在 addThreadObject1 实例对象上加了锁A, 在addThreadObject1 实例对象上加了锁B,2 个线程可以同时获得锁,存在线程安全问题(2 个线程存在并行执行),得到的值小于 8000
        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();

        log.info("最终计算 i = " + AddThread.num);
    }
}

synchronized 修饰代码块–指定锁作用于类对象

  • 相当于上面 synchronized 作用于静态方法
@Override
    public void run() {
        synchronized(AddThread.class){
            for (int i = 0; i < 4000; i++) {
                num++;
            }
        }
    }

synchronized底层语义原理

简单理解
synchronized 代码块指定锁对象
每个对象在存储时都有对应的锁状态字段,使用了synchronized 关键字修饰后,对应的代码块在底层执行时,会拿锁作用的对象头部的锁状态标识,如果未上锁,则添加锁修改为已锁状态,使用完后释放锁,从而达到同步的效果。
synchronized 修饰方法:
每个方法在方法表中存储时有是否是同步方法的标识 ACC_SYNCHRONIZED,使用synchronized 关键字修饰后,对应的方法在执行时,也会拿锁作用的对象头部的锁状态标识,如果未上锁,则添加锁修改为已锁状态,使用完后释放锁,从而达到同步的效果。

本质上还是 java 对象中存储了关于锁的标识,才致使java中任意对象均可作为锁。

更多底层原理 其他大佬的博客

你可能感兴趣的:(#,线程,java,多线程,synchronized)