首页IT科技多线程锁的实现原理(【多线程与高并发】- synchronized锁的认知)

多线程锁的实现原理(【多线程与高并发】- synchronized锁的认知)

时间2025-07-30 03:23:02分类IT科技浏览4703
导读:synchronized锁的认知 ?生命不息,写作不止...

synchronized锁的认知

?生命不息              ,写作不止

? 继续踏上学习之路                     ,学之分享笔记

? 总有一天我也能像各位大佬一样

? 一个有梦有戏的人 @怒放吧德德

?分享学习心得       ,欢迎指正       ,大家一起学习成长!

简介

synchronized 是 Java 语言的一个关键字                     ,它允许多个线程同时访问共享的资源              ,以避免多线程编程中的竞争条件和死锁问题              。synchronized可以用来给对象或者方法进行加锁       ,当对某个对象或者代码块加锁时                     ,同时就只能有一个线程去执行                     。这种就是互斥关系              ,被加锁的区域称为临界区,而里面的资源就是临界资源       。当一个线程进入临界区的时候                     ,另一个线程就必须等待       。

synchronized可以限制对某个资源的访问                     ,但是它锁的并不是资源本身,可以锁住某个对象              ,只有线程拿到这把锁之后才能够去访问临界资源                     。如下代码                     ,在我们想执行对count变量进行操作的时候       ,线程需要拿到o这个对象              。 public class T1_Synchronized01 { private int count = 1; private Object o = new Object(); public void m1() { synchronized (o) { // 必须先拿到o这个锁 count++; System.out.println(Thread.currentThread().getName() + " count = " + count); } } }

synchronized基础用法

1              、通过对象进行锁

在代码里              ,可以通过创建一个对象                     ,这样要想拿到临界资源       ,就必须先获得到这个对象的锁       。

public class T1_Synchronized01 { private int count = 1; private Object o = new Object(); public void m1() { synchronized (o) { // 必须先拿到o这个锁 count++; System.out.println(Thread.currentThread().getName() + " count = " + count); } } }

2                     、通过this

使用this代表锁住的是当前对象       ,这种方法等同直接把synchronized关键字加在方法前                     。

public class T1_Synchronized01 { private int count = 1; public void m2() { synchronized (this) { // 必须先拿到this的锁 count++; System.out.println(Thread.currentThread().getName() + " count = " + count); } } public synchronized void m3() { // 与m2一样 count++; System.out.println(Thread.currentThread().getName() + " count = " + count); } }

3       、锁定静态方法

锁定静态方法需要通过类.class                     ,或者直接在静态方法上加上关键字              。但是              ,类.class不能使用this来代替。注:在同一个类加载器中       ,class是单例的                     ,这也就能保证synchronized能够只让一个线程访问临界资源                     。

public class T1_Synchronized01 { public static void m4() { // 静态方法 synchronized (T1_Synchronized01.class) { System.out.println(Thread.currentThread().getName()); } } public synchronized static void m5() { System.out.println(Thread.currentThread().getName()); } }

4              、实验测试

①                     、首先测试一下              ,同步和非同步是否可以相互调用

定义两个线程,一个执行同步方法                     ,一个执行非同步方法                     ,如果不能够互相调用,那么              ,非同步线程就需要等待同步线程执行完之后在继续执行                     。 public class T3_Synchronized03 { public synchronized void one() { System.out.println(Thread.currentThread().getName() + " start one method"); try { Thread.sleep(2000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName() + " end one method"); } public void two() { System.out.println(Thread.currentThread().getName() + " execute two method"); } public static void main(String[] args) { T3_Synchronized03 t = new T3_Synchronized03(); new Thread(t::one, "第一个线程").start(); new Thread(t::two, "第二个线程").start(); } }

从运行的结果可以看出是可以的。

②       、读写不全加锁会怎样

通过购票与查询票数来进行模拟读写加锁问题              。

首先                     ,看以下代码是给读写都进行加锁了       ,在扣掉票数的时候              ,休眠了2秒                     ,当线程执行了购票之后       ,通过多个线程去查询票数       ,每次启动线程会睡眠0.5秒                     。 public class T4_Synchronized { private int ticket = 100; // 模拟100张票 public synchronized int getTicket() { // 读 return this.ticket; } public synchronized void buy(int number) { // 写 try { Thread.sleep(2000); } catch (InterruptedException e) { throw new RuntimeException(e); } ticket = ticket - number; } public static void main(String[] args) throws InterruptedException { T4_Synchronized bus = new T4_Synchronized(); System.out.println("刚开始有票数:" + bus.getTicket()); new Thread(() -> bus.buy(1)).start(); for (int i = 1; i <= 10; i++) { Thread.sleep(500); int finalI = i; new Thread(() -> System.out.println("第" + finalI + "次查询余票数:" + bus.getTicket())).start(); } } }

运行之后                     ,我们可以发现              ,数据是正确的       ,尽管是在查询的时候并没有睡眠0.5秒                     ,显示数据依然是期望数据       。

然而              ,当我们把读的锁去掉,运行代码                     ,会发现                     ,数据读出来了脏数据,为了更好的显示              ,查询票数的睡眠不要去掉              。

③       、synchronized的可重入性

定义一个类                     ,类中有两个同步方法       ,他们锁的对象都是当前类              ,如果不能够重入                     ,在one线程启动的时候就会死锁                     。在同步方法one中去调用同步方法two       ,当线程启动的时候       ,已经获取了对象的锁                     ,等调用two方法的时候              ,同样是拿到了这个对象的锁       。所以synchronized是可重入的       。 public class T5_Synchronized { synchronized void one() { try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } two(); System.out.println("one - thread-" + Thread.currentThread().getName() + " end"); } synchronized void two() { try { Thread.sleep(2000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("two - thread-" + Thread.currentThread().getName() + " end"); } public static void main(String[] args) { T5_Synchronized t5 = new T5_Synchronized(); new Thread(t5::one, "one1").start(); new Thread(t5::one, "one2").start(); new Thread(t5::one, "one3").start(); } }

实验结果

④                     、异常会释放锁

当线程执行过程中出现了异常       ,synchronized的锁会被释放                     ,这样其他需要访问这个临界资源的线程就能进入执行                     。 public class T6_Synchronized { int count = 0; synchronized void add() { System.out.println("线程 " + Thread.currentThread().getName() + " start"); while (true) { count++; System.out.println("线程 " + Thread.currentThread().getName() + " now count = " + count); if (count == 3) { throw new NullPointerException("人为异常"); } if (count == 10) { throw new NullPointerException("测试结束"); } } } public static void main(String[] args) throws InterruptedException { T6_Synchronized t = new T6_Synchronized(); new Thread(t::add, "1").start(); Thread.sleep(1000); new Thread(t::add, "2").start(); } }

当第一次异常抛出时              ,线程2就立即进入执行              。

synchronized锁的底层原理

synchronized实现锁的基础就是Java对象头,synchronized锁会将线程ID存入mark word(对象头由标记字)       。关于mark word                     ,先简要了解一下Java对象                     。

在Hotspot 虚拟机中                     ,对象在内存中的存储布局,可以分为三个区域:对象头(Header)              、实例数据(Instance Data)       、对齐填充(Padding)              。synchronized主要是跟对象头有关系              ,在对象头中包含了标记字(mark word)                     、类指针(klass word)和 数组长度(array length

)。也就是通过mark word的字节位数来表示各种锁状态                     。

synchronized锁在线程第一次访问的时候                     ,实际上是没有加锁的       ,只是在mark word中记录了线程ID              ,这种就是偏向锁                     ,默认是认为不会有多个线程抢着用       ,mark word是通过64bit来表示的       ,通过最低2位也就是锁标志位                     ,偏向锁与无锁的值是01              ,轻量级锁用00表示       ,重量级锁用10表示                     ,标记了GC的用11表示              ,无锁与偏向锁低2位是一致的,在倒数第3位有1位来表示偏向锁位:值为1表示偏向锁                     。

这里引用一张掘金博客上的图:https://juejin.cn/post/6978882583492821023

synchronized锁升级

● synchronized锁在线程第一次访问的时候                     ,实际上是没有加锁的                     ,只是在mark word中记录了线程ID,默认也就是使用偏向锁。

● 当第二个线程来争用的时候              ,此时第二个线程会占用cpu                     ,循环等待锁的释放       ,这时候偏向锁也就升级为自旋锁              。

● 当自旋10次之后              ,就会升级为重量级锁                     ,重量级锁是不占用cpu       ,他是使用OS的                     。

当线程数较少              、运行时间较短的时候是比较适合使用自旋锁       ,反之则比较适合重量级锁       。

博文推荐

对于synchronized的学习                     ,我也查阅了许多的相关信息              ,确实很不容易       ,我将认为不错的文章都推荐出来:

【并发编程系列2】synchronized锁升级原理分析(偏向锁-轻量级锁-重量级锁)_双子孤狼的博客-CSDN博客_java锁降级和升级

深入理解Java并发之synchronized实现原理_zejian_的博客-CSDN博客_javasynchronized原理

深入理解synchronized底层原理                     ,一篇文章就够了! - 腾讯云开发者社区-腾讯云

Java 对象、对象头mark word                     、锁升级                     、对象占内存大小 - 掘金

深入理解Java的对象头mark word_了不起的盖茨比              。的博客-CSDN博客_撖寡情憭尋ark word

Java知识进阶-程序员未接触的知识点之对象模型Klass Word-知识铺_实例

?创作不易              ,如有错误请指正,感谢观看!记得点赞哦!?

声明:本站所有文章                     ,如无特殊说明或标注                     ,均为本站原创发布                     。任何个人或组织,在未征得本站同意时              ,禁止复制、盗用              、采集                     、发布本站内容到任何网站       、书籍等各类媒体平台       。如若本站内容侵犯了原著者的合法权益                     ,可联系我们进行处理       。

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
vue点击加载更多(vue数据太多导致页面卡顿解决办法) linux添加默认网关192.168.1.1(linux设置默认网关教程)