Java多线程之线程的互斥处理
Java多线程之线程的互斥处理
一、前言
多线程程序中的各个线程都是自由运行的,所以它们有时就会同时操作同一个实例。这在某些情况下会引发问题。例如,从银行账户取款时,余额确认部分的代码应该是像下面这样的。
if (可用余额大于取款金额) {
从可用余额中减掉取款金额
}
首先确认可用余额,确认是否允许取款。如果允许,则从可用余额上减掉取款金额。这样才不会导致可用余额变为负数。
但是,如果两个线程同时执行这段代码,那么可用余额就有可能会变为负数。
假设可用余额=1000元,取款金额= 1000元,那么这种情况就如下图所示:

线程A 和线程B 同时操作时,有时线程B 的处理可能会插在线程A 的“可用余额确认”和“从可用余额上减掉取款金额”这两个处理之间。
这种线程A 和线程B 之间互相竞争(race)而引起的与预期相反的情况称为数据竞争(datarace)或竞态条件(race condition)。
这时候就需要有一种“交通管制”来协助防止发生数据竞争。例如,如果一个线程正在执行某一部分操作,那么其他线程就不可以再执行这部分操作。这种类似于交通管制的操作通常称为互斥(mutual exclusion)。这种处理就像十字路口的红绿灯,当某一方向为绿灯时,另一方向则一定是红灯。
Java 使用关键字synchronized 来执行线程的互斥处理。
二、synchronized 方法
如果声明一个方法时,在前面加上关键字synchronized,那么这个方法就只能由一个线程运行。只能由一个线程运行是每次只能由一个线程运行的意思,并不是说仅能让某一特定线程运行。这种方法称为synchronized 方法,有时也称为同步方法。
如下所示的类就使用了synchronized 方法。Bank(银行)类中的deposit(存款)和withdraw(取款)这两个方法都是synchronized 方法。
包含deposit 和withdraw 这两个synchronized 方法的Bank 类(Bank.java)
public class Bank {
private int money;
private String name;
public Bank(String name, int money) {
this.name = name;
this.money = money;
}
/**
* 存款
* @param m
*/
public synchronized void deposit(int m) {
money += m;
}
/**
* 取款
* @param m
* @return
*/
public synchronized boolean withdraw(int m) {
if (money >= m) {
money -= m;
return true; // 取款成功
} else {
return false; // 余额不足
}
}
public String getName() {
return name;
}
}
如果有一个线程正在运行Bank 实例中的deposit 方法,那么其他线程就无法运行这个实例中的deposit 方法和withdraw 方法,需要排队等候。
Bank 类中还有一个getName 方法。这个方法并不是synchronized 方法,所以无论其他线程是否正在运行deposit 或withdraw,都可以随时运行getName 方法。
一个实例中的synchronized 方法每次只能由一个线程运行,而非synchronized 方法则可以同时由两个以上的线程运行。下图展示了由两个线程同时运行getName 方法的情况。

synchronized 方法不允许同时由多个线程运行。上图中,我们在synchronized 方法左侧放了一个代表“锁”的长方形来表示这点。当一个线程获取了该锁后,长方形这块儿就像筑起的墙一样,可以防止其他线程进入。
下图展示了由一个线程运行deposit 方法的情况。由于该线程获取了锁,所以其他线程就无法运行该实例中的synchronized 方法。图中,表示锁的长方形被涂成了灰色,这表示该锁已被某一线程获取。

请注意,上图中,非synchronized 的getName 方法完全不受锁的影响。不管线程是否已经获取锁,都可以自由进入非synchronized 方法。
当正在使用synchronized 方法的线程运行完这个方法后,便会释放锁。下图中的长方形锁变为白色表示这个锁已被释放。

当锁被释放后,一直等待获取锁的线程中的某一个线程便会获取该锁。但无论何时,获取锁的线程只能是一个。如果等待的线程有很多个,那么没抢到的线程就只能继续等待。下图展示的是新获取锁的另一个线程开始运行synchronized 方法的情况。

每个实例拥有一个独立的锁。因此,并不是说某一个实例中的synchronized 方法正在执行中,其他实例中的synchronized 方法就不可以运行了,下图展示了bank1 和bank2 这两个实例中的synchronized 方法由不同的线程同时运行的情况。

- 关于锁和监视
线程的互斥机制称为监视(monitor)。另外,获取锁有时也叫作“拥有(own)监视”或“持有(hold)锁”。
当前线程是否已获取某一对象的锁可以通过Thread.holdsLock 方法来确认。当前线程已获取对象obj 的锁时,可使用assert 来像下面这样表示出来。
assert Thread.holdsLock(obj);
三、synchronized 代码块
如果只是想让方法中的某一部分由一个线程运行,而非整个方法,则可使用synchronized代码块,格式如下所示。
synchronized (表达式) {
.......................
}
其中的“表达式”为获取锁的实例。synchronized 代码块用于精确控制互斥处理的执行范围。
◆◆synchronized实例方法和synchronized代码块
假设有如下synchronized 实例方法。
synchronized void method() {
.......................
}
这跟下面将方法体用synchronized 代码块包围起来是等效的。
void method () {
synchronized (表达式) {
.......................
}
}
也就是说,synchronized 实例方法是使用this的锁来执行线程的互斥处理的。
◆◆synchronized静态方法和synchronized代码块
假设有如下synchronized 静态方法。synchronized 静态方法每次只能由一个线程运行,这一点和synchronized 实例方法相同。但synchronized 静态方法使用的锁和synchronized 实例方法使用的锁是不一样的。
Class Something {
static synchronized void method() {
.......................
}
}
这跟下面将方法体用synchronized代码块包围起来是等效的。
Class Something {
static void method() {
synchronized (Something.class) {
...................
}
}
}
也就是说,synchronized静态方法是使用该类的类对象的锁来执行线程的互斥处理的。Something.class是Something 类对应的java.lang.Class 类的实例。
参考:图解Java多线程设计模式
Java多线程之线程的互斥处理的更多相关文章
- Java多线程之线程的通信
Java多线程之线程的通信 在总结多线程通信前先介绍一个概念:锁池.线程因为未拿到锁标记而发生的阻塞不同于前面五个基本状态中的阻塞,称为锁池.每个对象都有自己的锁池的空间,用于放置等待运行的线程.这些 ...
- Java多线程之线程的同步
Java多线程之线程的同步 实际开发中我们也经常提到说线程安全问题,那么什么是线程安全问题呢? 线程不安全就是说在多线程编程中出现了错误情况,由于系统的线程调度具有一定的随机性,当使用多个线程来访问同 ...
- Java多线程之线程协作
Java多线程之线程协作 一.前言 上一节提到,如果有一个线程正在运行synchronized 方法,那么其他线程就无法再运行这个方法了.这就是简单的互斥处理. 假如我们现在想执行更加精确的控制,而不 ...
- Java多线程之线程其他类
Java多线程之线程其他类 实际编码中除了前面讲到的常用的类之外,还有几个其他类也有可能用得到,这里来统一整理一下: 1,Callable接口和Future接口 JDK1.5以后提供了上面这2个接口, ...
- Java多线程之线程的控制
Java多线程之线程的控制 线程中的7 种非常重要的状态: 初始New.可运行Runnable.运行Running.阻塞Blocked.锁池lock_pool.等待队列wait_pool.结束Dea ...
- Java多线程父子线程关系 多线程中篇(六)
有的时候对于Java多线程,我们会听到“父线程.子线程”的概念. 严格的说,Java中不存在实质上的父子关系 没有方法可以获取一个线程的父线程,也没有方法可以获取一个线程所有的子线程 子线程的消亡与父 ...
- 关于Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇高质量的博文)
Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇质量高的博文) 前言:在学习多线程时,遇到了一些问题,这里我将这些问题都分享出来,同时也分享了几篇其他博客主的博客,并且将我个人的理解也分享 ...
- Java多线程02(线程安全、线程同步、等待唤醒机制)
Java多线程2(线程安全.线程同步.等待唤醒机制.单例设计模式) 1.线程安全 如果有多个线程在同时运行,而这些线程可能会同时运行这段代码.程序每次运行结果和单线程运行的结果是一样的,而且其他的变量 ...
- JAVA多线程之线程间的通信方式
(转发) 收藏 记 周日,北京的天阳光明媚,9月,北京的秋格外肃穆透彻,望望窗外的湛蓝的天,心似透过栏杆,沐浴在这透亮清澈的蓝天里,那朵朵白云如同一朵棉絮,心意畅想....思绪外扬, 鱼和熊掌不可兼得 ...
随机推荐
- 我造了个好大的"轮子",居然还不是"圆"的!
我造的这个"轮子"指的是集低代码开发与运维为一体的平台,为什么说它不是"圆"的,因为它有些与众不同,甚至可以说是有些另类.至于为什么造这个"轮子& ...
- 💡我们的表单解决方案 el-form-renderer
前言 本文将介绍我们的表单解决方案 @femessage/el-form-renderer,展示我们在 Vue 技术栈下,我们是如何处理以下问题的: 表单项动态显示或隐藏 表单数据联动 表单输入/输出 ...
- vue.js带复选框表单的增删改查
近段时间由于公司项目要求,前端开始使用VUE框架进行开发,最近刚开始学习,做了一个表单的增删改查,和大家分享一下. 页面模型代码设计如下 <template> <div id=&qu ...
- 从三个语言(C++,Java,.Net)的几个性能测试案例来看性能优化
随着时间的发展,现在的虚拟机技术越来越成熟了,在有些情况下,Java,.Net等虚拟机密集计算的性能已经和C++相仿,在个别情况下,甚至还要更加优秀.本文详细分析几个性能测试案例,探讨现象背后的原因. ...
- 阿里云nas使用记录
公司买了阿里云的nas服务用来共享存储,多个web服务器共同挂载同一个nas服务.挂载过程中出现如下报错 NAS报错: [root@BJ-SBC fs]# mount -t nfs 10.10.8.1 ...
- .Net Core CLR FileFormat Call Method( Include MetaData, Stream, #~)
.Net Core CLR PE 文件启动方法,找到函数入口点,调用整个.Net 程式宿主. 使用方法:可以利用Visual Studio新建一个控制台应用程序,然后生成DLL,替换掉本程序DLL, ...
- Asp.Net MVC 高级特性(附带源码剖析)
1. 程序入口(MvcHandler,RouteHandler,HttpModule) 2.异步类包(静态类AsyncResultWrapper),开启整个MVC异步循环 3.Aggregate递归链 ...
- 配置多个JDK存在的问题与解决方案 (亲测可用)
安装多个JDK时的技巧 (亲测可用) 我的电脑本来是JDK8的,后来的想在不同的JDK版本下测试JDK的垃圾回收器. 一开始的的思路是,先安装JDK,为每个JDK配置自己的家目录,然后在想用哪个版本的 ...
- c#实现深拷贝的几种方法
为什么要用到深拷贝呢?比如我们建了某个类Person,并且实例化出一个对象,然后,突然需要把这个对象复制一遍,并且复制出来的对象要跟之前的一模一样,来看下我们一般会怎么做,看代码 public cla ...
- k8s+istio:流量控制之灰度发布
通过Kubernetes+Istio的流量控制实现灰度发布,主要演示通过流量权重实现蓝绿,通过http自定义头实现金丝雀 准备环境 k8s和istio不想自己装的话可以在云上买个按量付费集群,用完即删 ...