本文主要是介绍并发编程线程安全之同步锁Synchronized,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
一、原子性定义
原子性的本质是互斥访问,同一时刻只有一个线程对它进行访问操作
二、原子性问题的简述
public class AutomicDemo {int count = 0;public static void main(String[] args) throws InterruptedException {AutomicDemo automicDemo = new AutomicDemo();Thread thread1 = new Thread(() ->{for (int j = 0; j < 1000; j++) {automicDemo.incr();}});Thread thread2 = new Thread(() ->{for (int j = 0; j < 1000; j++) {automicDemo.incr();}});thread1.start();thread2.start();// join方法保证线程执行完毕thread1.join();thread2.join();System.out.println("i的结果值为:"+automicDemo.count);}public void incr(){count++;}
}
运行结果:

线程1线程2各循环一千次执行i++操作,正常情况下应该得到的值是2000,那么为什么会得到1412呢?这是由于当前代码中的i++操作是非原子性的。

其实一个count++操作是分为3步的:1.加载2.计算3.写入内存。由上图可以看出来,在同一时刻,线程A与线程B同时运行,当线程A刚把count为0的值加载到寄存器的时候,此时线程进行了切换,线程B完成了整个count++操作并把结果写入了内存,此时线程A接着执行,那么线程A加载到寄存器的count还是0,因此它计算后也把count=1存入了内存,这无形中就少了一次计算。思考:那么应该怎么解决这个问题呢?加同步锁synchronized关键字,保证在同一时刻,只有一个线程能够访问并操作count++
思考: 为什么加了synchronized关键字 系统就会认定它为一个同步锁呢,从而避免多线程对该方法 的一个操作呢?(见3.3)synchronized的作用范围是什么呢?(见3.1)锁的本质又是什么呢?(见3.2)
三、Synchronized关键字
3.1 作用范围
- 修饰实例方法 创建不同的对象,都可以访问该实例
- 修饰静态方法 全局锁
- 修饰代码块
synchronized() 括号中可以存储任何一个对象, 影响锁的作用范围,其实就是括号中对象的生命周期
3.2 抢占锁的本质
抢占锁的本质就是如何实现互斥,那么必定有两个条件
- 共享资源
- 锁标记 可以假设 0代表无锁 1 代表有锁
也就是说,抢占锁,一定是共同要访问一个共享的资源,当一个线程先占有了这个资源,就变为有锁状态,阻塞其它的线程。
3.3 锁信息的存储
加了synchronized关键字,系统为何就能识别这是一个同步锁呢,其实,系统根据该关键字,保存了一些信息在作用的对象上。
MarkWord对象头
我们可以通过以下代码打印对象头的信息(不加synchronized和加synchronized的区别)
public static void main(String[] args) {Object lock = new Object();System.out.println(VM.current().details());System.out.println(ClassLayout.parseInstance(lock).toPrintable());}
打印的信息如下

结合下图可以看出,第一行的前8个字节的最后3位001 则为无锁状态

四、Synchronized锁升级
1. 无锁状态
2. 偏向锁: 假设没有线程竞争的时候,A线程进入到同步代码 就会偏向A线程 有线程竞争的时候 线程B再进来 就会做升级 轻量级锁
3. 轻量级锁 主要作用是避免线程阻塞 采用自旋锁的方式
4 .重量级锁 表示的是用户态到内核态的交换 没有获得锁的线程会阻塞,再被唤醒
思考: 线程A已经抢占到了锁,当线程B来竞争的时候,如果是重量级锁,则线程A执行完还需要唤醒线程B, 比较消耗性能,那么有没有一种办法可以避免或者优化这种阻塞呢?于是就引入了轻量级锁,线程B会不断的去重试,如果此时正好线程A结束了,那么线程B就可以执行了,不需要再去唤醒了。举个例子,就比如你去找老王,会先敲几下门,然后如果门没开,则会再去一边等着,等着老王来唤醒。

分析: 当有两个线程的时候,线程A先进入了,则线程B抢占锁的时候,则会先进行自旋,如果抢占到了,则修改lock flag的标记,使用CAS机制保证操作的原子性
五、CAS机制
old: ThreadA
expect: ThreadB
update: ThreadC
CAS机制与乐观锁类似
CAS机制其底层是C++代码,采用了lock指令
举例:
当前线程A获得了偏向锁,线程B来抢占偏向锁
线程B就会来调用CAS,把偏向锁的指针指向自己
CAS(object,线程A的指针,线程B的指针(带更新的值))

这篇关于并发编程线程安全之同步锁Synchronized的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!