当前位置: 首页 > news >正文

Java线程安全:synchronized锁机制详解

目录

一. 线程安全问题的原因

二. 锁

2.1 synchronized 的引入

2.2 synchronized 的特性

2.3 synchronized 的三种使用方法

三. 死锁

3.1 死锁的概念

3.2 死锁的四个必要条件

3.3 如何避免死锁


一. 线程安全问题的原因

  1. 操作系统对线程的调度是随机的(无法应对)
  2. 两个线程针对同一变量进行修改
  3. 修改操作不是原子的
  4. 内存的可见性
  5. 指令重排序

二. 锁

Java提供了很多种锁的实现,整体的思路类似于 “互斥” “独占”资源。锁机制本质上是操作系统提供的功能

Java提供了关键字 —— synchronized

2.1 synchronized 的引入

在具体介绍 synchronized 的三种使用方法之前,我们先来看一个出现线程安全问题的例子--设置两个线程t1,t2,两个线程分别对同一个变量 count,进行自增操作5000次

public class Test {public static int count=0;public static void main(String[] args) throws InterruptedException {Thread t1=new Thread(()->{for (int i = 0; i < 5000; i++) {count++;}});Thread t2=new Thread(()->{for (int i = 0; i < 5000; i++) {count++;}});t1.start();t2.start();t1.join();t2.join();System.out.println("count的值为:"+count);}
}

我们会发现,在多次启用这个代码程序时count的值不尽相同

这段代码出现线程安全问题的核心原因是:多个线程( t1  和 t2 )同时对共享变量 count  执行非原子操作 count++(load add save) ,导致“读 - 改 - 写”步骤交叉,最终数据不一致。

那么我们该怎么样,才能将count++操作变为原子的呢,这时候我们就要用到 synchronized 关键字,进行加锁操作

public class Test {public static int count=0;public static Object locker=new Object();public static void main(String[] args) throws InterruptedException {Thread t1=new Thread(()->{synchronized (locker) {for (int i = 0; i < 5000; i++) {count++;}}});Thread t2=new Thread(()->{synchronized (locker) {for (int i = 0; i < 5000; i++) {count++;}}});t1.start();t2.start();t1.join();t2.join();System.out.println("count的值为:"+count);}
}

那么是什么原理呢

注意:

  • synchronized实际起到的是防止插队的作用(避免上述的三个指令过程中被插队),而不是禁止线程调度(锁竞争失败后,线程并非是被“禁止线程调度”,而是处于特定的等待状态,在该状态下暂时不参与CPU资源的竞争
  • 当t2加锁失败放弃cpu进入阻塞状态时,线程随机调度(除t2线程外的线程都有可能被调度)
  • 只有加锁的对象是同一个对象时才会产生锁竞争,不同对象则不会有
  • 在加锁状态中,不会影响线程的调度,在中间过程还是会调度其它线程,只不过由于锁被占用,其它线程若也是加锁操作,则会加锁失败产生阻塞等待在(没有锁释放的情况下, 线程调度器不会调度它去执行

2.2 synchronized 的特性

1. 互斥

synchronized会起到互斥效果,某个线程执行到某个对象的synchronized中时,其他线程如果也执到到同一个对象synchronized就会阻塞等待.

• 进⼊synchronized修饰的代码块,相当于加锁

• 退出synchronized修饰的代码块,相当于解锁

2. 可重入

synchronized同步块对同一条线程来说是可重入的,不会出现自己把自己锁死的情况

public class Test {public static int count=0;public static void main(String[] args) throws InterruptedException {Test locker=new Test();Thread t1=new Thread(()->{synchronized (locker){synchronized (locker){for (int i = 0; i < 1000; i++) {count++;}}}});t1.start();t1.join();System.out.println("count的值为:"+count);}
}

总之:如果第一次加锁成功后,再进行二次加锁时,synchronized内部会判定第二次加锁的线程是否和第一次加锁是同一个线程,若是则相当于“直接跳过”,反之则产生阻塞(可重入锁的核心特点就是:同一线程可以多次获取同一把锁,而不会因为已经持有这把锁而被阻塞。

补:一个线程可以同时拥有不同对象的锁

2.3 synchronized 的三种使用方法

  • 修饰代码块
  Thread t1=new Thread(()->{synchronized (locker) {for (int i = 0; i < 5000; i++) {count++;}}});

 synchronized(Object obj){ } 中加锁的对象(obj)是谁,对加锁中的内容是没有影响的,只要是对同一个对象加锁即可

一个类中的代码块还可以针对this加锁:(表示对当前实例对象加锁)

public class test {public int count=0;public void func(){synchronized(this){for (int i = 0; i < 10000; i++) {count++;}}}
}

注意:this只能在实例方法,构造方法或实例初始化在使用,不能在静态方法中使用

  • 修饰一个实例方法
public class test {public int count=0;public synchronized void func(){for (int i = 0; i < 10000; i++) {count++;}}public static void main(String[] args) throws InterruptedException {test obj=new test();Thread t1=new Thread(()->{obj.func();});Thread t2=new Thread(()->{obj.func();});t1.start();;t2.start();t1.join();t2.join();System.out.println("count的值为:"+obj.count);}
}
  • 修饰一个静态方法
public class test {public static int count=0;public synchronized static void func(){for (int i = 0; i < 10000; i++) {count++;}}public static void main(String[] args) throws InterruptedException {Thread t1=new Thread(()->{test.func();});Thread t2=new Thread(()->{test.func();});t1.start();;t2.start();t1.join();t2.join();System.out.println("count的值为:"+count);}}

synchronized除了直接修饰静态方法,还可以在静态方法中修饰代码块,并用类名.class的方式获得类对象(不管一个类实例化多少个对象,类对象只有一个的

 public static void func(){synchronized(test.class){for (int i = 0; i < 10000; i++) {count++;}}}

总之:无论那种写法,synchronized()针对什么对象加锁不重要,最重要的是,两个线程是否针对一个对象加锁


三. 死锁

3.1 死锁的概念

死锁指多个进程(或线程)在执行过程中,因争夺资源 ,导致各进程(或线程)都在等待其他进程(或线程)释放已占用的资源,从而相互等待,无法继续推进的一种僵持局面。比如,线程A持有资源1,等待资源2;线程B持有资源2,等待资源1,双方都不释放已持有的资源,就形成了死锁。

3.2 死锁的四个必要条件

  • 锁是互斥的(锁的基本特点)
  • 锁不可被抢占
  • 请求和保持
  • 循环等待/环路等待

任意打破一点即可避免死锁

3.3 如何避免死锁

打破请求和保持----> 代码中避免出现锁的”嵌套“

打破循环等待----> 约定加锁的顺序

    http://www.dtcms.com/a/403480.html

    相关文章:

  1. 浅谈Linux内核的LRU算法作用和原理
  2. 自己做网站 最好的软件下载win10一键优化
  3. Vue3 + Ant Design Vue 实现表格无限滚动加载(自定义指令版)
  4. Golang语言基础篇006_Golang流程控制
  5. 襄樊网站网站建设网站建设中管理员登录的代码怎么写
  6. 打工人日报#20250924
  7. 网站的构思重庆建设工程造价信息
  8. 【lua】luajit 命令行使用指南
  9. 网站配置系统搭建一个网站需要多久
  10. 绿联UGOS Pro九月更新,三端优化,影音相册再升级
  11. JAVA学习-预科部分(路线、博客、预备基础)
  12. 使用IDEA创建项目
  13. Transformer - Multi-Head Attention(多头注意力)
  14. 网站建设及安全管理花店网站模板
  15. A股大盘数据-20250924分析
  16. 双目深度相机--10.双目视觉的主要步骤讲解分析
  17. 【双光相机配准】可见光与红外相机计算Homography
  18. 建网站 几个链接PHP网站建设项目经验
  19. 石家庄哪里能做门户网站的招商局网站建设方案
  20. 南通网站关键词优化广铁建设集团门户网站
  21. Code-First 与数据库迁移工具设计文档
  22. VScode(Visual Studio Code)常用配置大全(持续更新)
  23. 基于Cesium的地图采集点位经纬度工具
  24. zookeeper+kafka
  25. 慈溪市规划建设网站郑州手工外发加工网
  26. HarmonyOS 5 手势系统与高级交互动效开发实战
  27. 怎么网站建设到百度wordpress自带ajax很慢
  28. 手机微网站怎么设计方案网站建设 分析
  29. 【Redis原理】缓存的内部逻辑
  30. Java中的大数据流式计算与Apache Kafka集成!