java 线程安全(二)synchronized

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

解决线程安全

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

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

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();
		// 是为了让主线程等待 子线程任务执行完 然后再执行下面的 log
        thread1.join();
        thread2.join();

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

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

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

代码二:

// 省略了非核心代码
		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底层语义原理

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

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

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值