Java Concurrency - synchronized 关键字
当有多个线程竞争共享资源时,对资源的访问顺序敏感,则可能造成数据不一致。为了保证共享资源不被多个线程同时访问,则需要将竞争共享资源的代码置于临界区,临界区保证在同一时间内最多只能有一个线程执行该代码段。
先看一段由竞争共享资源造成数据不一致的代码:
public class BankAccount {
private int balance; public BankAccount(int balance) {
this.balance = balance;
} public int getBalance() {
return balance;
} public void withdraw(int amount) {
if (balance >= amount) {
try {
System.out.println(Thread.currentThread().getName() + " is going to sleep.");
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " woke up."); balance -= amount;
System.out.println(String.format("%s drew %d, the balance is %d now.", Thread.currentThread().getName(), amount, balance));
} else {
System.out.println("Sorry, not enough for " + Thread.currentThread().getName());
}
}
} public class DrawMoneyTask implements Runnable { private BankAccount bankAccount; public DrawMoneyTask(BankAccount bankAccount) {
this.bankAccount = bankAccount;
} @Override
public void run() {
for (int i = 0; i < 3; i++) {
bankAccount.withdraw(10);
if (bankAccount.getBalance() < 0) {
System.err.println("Overdrawn!");
}
}
} } public class Main {
public static void main(String[] args) {
BankAccount bankAccount = new BankAccount(50);
Runnable drawMoneyTask = new DrawMoneyTask(bankAccount); Thread huey = new Thread(drawMoneyTask, "Huey");
Thread jane = new Thread(drawMoneyTask, "Jane"); huey.start();
jane.start();
}
}
运行结果:
Jane is going to sleep.
Huey is going to sleep.
Huey woke up.
Jane woke up.
Jane drew 10, the balance is 30 now.
Huey drew 10, the balance is 30 now.
Jane is going to sleep.
Huey is going to sleep.
Huey woke up.
Jane woke up.
Jane drew 10, the balance is 10 now.
Jane is going to sleep.
Huey drew 10, the balance is 20 now.
Huey is going to sleep.
Huey woke up.
Jane woke up.
Huey drew 10, the balance is 0 now.
Jane drew 10, the balance is -10 now.
Overdrawn!
Overdrawn!
Huey 和 Jane 两个线程同时向账户取钱。虽然取款前先判断余额是否足够,但是仍出现超额提取的情况。这是因为两个线程同时访问修改账户信息造成数据不一致。当余额剩下 10 时,Huey 准备取款时,发现余额足够,然后进入第 14 行代码,线程休眠。这时 Jane 也来取款,同样发现余额还有 10,也进入第 14 行代码,然后休眠。Huey 醒来取了 10,余额便只剩下 0。Jane 醒来也取了 10,这时余额剩下 -10,发生超额。
为了避免超额,我们限制 Huey 和 Jane 两个线程不能同时执行取款操作。若当前有线程在执行取款操作,则其他线程挂起等待直到取款操作结束。我们可以使用 synchronized 关键字来实现这一功能。
public synchronized void withdraw(int amount) {
if (balance >= amount) {
try {
System.out.println(Thread.currentThread().getName() + " is going to sleep.");
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " woke up."); balance -= amount;
System.out.println(String.format("%s drew %d, the balance is %d now.", Thread.currentThread().getName(), amount, balance));
} else {
System.out.println("Sorry, not enough for " + Thread.currentThread().getName());
}
}
使用 synchronized 关键字修饰 withdraw 方法表示,整段 withdraw 方法代码为临界区,即同一时间内最多只能有一个线程执行 withdraw 方法,其他线程需等待直到当前线程执行完毕。使用 synchronized 修饰 withdraw 方法后,运行结果为:
Huey is going to sleep.
Huey woke up.
Huey drew 10, the balance is 40 now.
Huey is going to sleep.
Huey woke up.
Huey drew 10, the balance is 30 now.
Huey is going to sleep.
Huey woke up.
Huey drew 10, the balance is 20 now.
Jane is going to sleep.
Jane woke up.
Jane drew 10, the balance is 10 now.
Jane is going to sleep.
Jane woke up.
Jane drew 10, the balance is 0 now.
Sorry, not enough for Jane
synchronized 关键字除了修饰方法(同步方法),还可以修饰对象(同步代码块)。使用 synchronized 修饰对象的引用,通常会修饰 this 关键字,也可以修饰其他对象的引用。
synchronized (this) {
// ...
}
synchronized 修饰 this 关键字的作用与修饰普通方法类似,差别在于:当修饰方法时,整个方法都是临界区;当修饰 this 时,只有 { } 内的代码段是临界区。使用 synchronized 关键字在一定程度上会影响性能,因为在同一时间内最多只能有一个线程执行临界区的代码,不能发挥并行的优点。因此,可以根据实际情况,使用 synchronized 修饰对象的引用而非方法,调整临界区的大小,只同步访问修改共享资源的代码,其他竞争共享资源而又耗时的代码段不进行同步。
修饰 this 与修饰其他对象的引用本质也是相同的,它们都是对象锁,只是锁的对象不同。修饰 this 关键字,锁定的是当前对象。当一个线程获得一个锁时,其他线程如果要访问该锁作用的临界区时,则挂起等待直至锁被释放。
public class SynchronizedClass { public void methodA() {
synchronized (this) {
System.out.println("In methodA. " + new Date());
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} public void methodB() {
synchronized (this) {
System.out.println("In methodB. " + new Date());
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} public class TaskA implements Runnable { private SynchronizedClass synchronizedObject; public TaskA(SynchronizedClass synchronizedObject) {
this.synchronizedObject = synchronizedObject;
} @Override
public void run() {
synchronizedObject.methodA();
}
} public class TaskB implements Runnable { private SynchronizedClass synchronizedObject; public TaskB(SynchronizedClass synchronizedObject) {
this.synchronizedObject = synchronizedObject;
} @Override
public void run() {
synchronizedObject.methodB();
}
} public class Main {
public static void main(String[] args) {
SynchronizedClass synchronizedObject = new SynchronizedClass(); Thread threadA = new Thread(new TaskA(synchronizedObject));
Thread threadB = new Thread(new TaskB(synchronizedObject)); threadA.start();
threadB.start();
}
}
当有一个线程 A 正在执行 methodA 中 synchronized 修饰的代码段时,即线程 A 获得了 synchronizedObject 对象的锁。如果此时有线程 B 要执行 methodB 中 synchronized 修饰的代码段,同样需要获得 synchronizedObject 的锁才能进入 methodB 方法中的临界区,所以线程 B 需要等待线程 A 释放 synchronizedObject 对象锁后才能执行 methodB 方法中的临界区。观察运行结果:
In methodA. Sun Oct 09 14:59:43 CST 2016
In methodB. Sun Oct 09 14:59:46 CST 2016
需要注意的是,传入 TaskA 和 TaskB 的参数必须是同一个 SynchronizedClass 实例。如果传的是两个不同的 SynchronizedClass 实例,则线程 A 和线程 B 获得的是两个不同的锁,不会形成同步。
修改 Main 方法的内容:
public class Main {
public static void main(String[] args) {
SynchronizedClass synchronizedObjectA = new SynchronizedClass();
SynchronizedClass synchronizedObjectB = new SynchronizedClass(); Thread threadA = new Thread(new TaskA(synchronizedObjectA));
Thread threadB = new Thread(new TaskB(synchronizedObjectB)); threadA.start();
threadB.start();
}
}
现在传给 TaskA 和 TaskB 是两个不同的 SynchronizedClass 实例,观察运行结果:
In methodA. Sun Oct 09 15:13:44 CST 2016
In methodB. Sun Oct 09 15:13:44 CST 2016
前面提到,synchronized 修饰 this 关键字的作用与修饰普通方法的差别只在于临界区的范围不一样。synchronized 修饰普通方法,锁定的也是当前对象。
public class SynchronizedClass { public void methodA() {
synchronized (this) {
// ...
}
} public synchronized void methodB() {
// ...
} }
上述代码,对于同一个 SynchronizedClass 实例。如果有线程 A 正在执行 methodA 方法中 synchronized 修饰的代码段。如果此时有其他线程要执行 methodB,则必须挂起等待直到线程 A 释放锁后才能进入 methodB。
synchronized 还可以修饰静态方法,这样则是持有类锁。假设一个类中有两个静态方法 methodA 和 methodB 被 synchronized 修饰,当有线程 A 正在执行 methodA 时,即线程 A 持有了类锁,此时如果有线程 B 要执行 methodB,那么线程 B 需要挂起等待直至线程 A 释放锁。
import java.util.Date; public class SynchronizedClass { public synchronized static void methodA() {
System.out.println("In methodA. " + new Date());
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
} public synchronized static void methodB() {
System.out.println("In methodB. " + new Date());
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
} public static void main(String[] args) {
Thread threadA = new Thread(new Runnable() {
public void run() {
SynchronizedClass.methodA();
}
}); Thread threadB = new Thread(new Runnable() {
public void run() {
SynchronizedClass.methodB();
}
}); threadA.start();
threadB.start();
}
}
观察结果:
In methodB. Sun Oct 09 15:44:18 CST 2016
In methodA. Sun Oct 09 15:44:21 CST 2016
对象锁和类锁是两种不同的锁。
public class SynchronizedClass { public synchronized static void methodA() {
// ...
} public synchronized void methodB() {
// ...
}
}
当有线程 A 执行 methodA 方法时,不会影响其他线程执行 methodB 方法。
关键字 synchronized 拥有锁重入功能,即当一个线程持有一个锁时,在方法中再次请求该锁时,是可以再次得到该锁的。
public class SynchronizedClass { public synchronized void methodA() {
System.out.println("In methodA. ");
methodB();
} public synchronized void methodB() {
System.out.println("In methodB. ");
} public static void main(String[] args) {
final SynchronizedClass synchronizedObject = new SynchronizedClass(); Thread thread = new Thread(new Runnable() {
public void run() {
synchronizedObject.methodA();
}
}); thread.start();
}
}
Java Concurrency - synchronized 关键字的更多相关文章
- Java 多线程 —— synchronized关键字
java 多线程 目录: Java 多线程——基础知识 Java 多线程 —— synchronized关键字 java 多线程——一个定时调度的例子 java 多线程——quartz 定时调度的例子 ...
- Java的synchronized关键字:同步机制总结
JAVA中synchronized关键字能够作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块.搞清楚synchronized锁定的是哪个对象,就能帮助我们设计更安全的多线程程 ...
- java中synchronized关键字分析
今天我们来分析一下java中synchronized关键字.首先来看一段java代码:(本地编译环境为mac,jdk1.8的环境) Demo.java package com.example.spri ...
- Java基础-synchronized关键字的用法(转载)
synchronized--同步 顾名思义是用于同步互斥的作用的. 这里精简的记一下它的使用方法以及意义: 当synchronized修饰 this或者非静态方法或者是一个实例的时候,所同步的锁是加在 ...
- 从分布式锁角度理解Java的synchronized关键字
分布式锁 分布式锁就以zookeeper为例,zookeeper是一个分布式系统的协调器,我们将其理解为一个文件系统,可以在zookeeper服务器中创建或删除文件夹或文件.设D为一个数据系统,不具备 ...
- java基础Synchronized关键字之对象锁
java中Synchronized关键字之对象锁 当有多个线程对一个共享数据进行操作时,需要注意多线程的安全问题. 多线程的同步机制对资源进行加锁,使得在同一个时间,只有一个线程可以进行操作,同 ...
- java中synchronized关键字的用法
在java编程中,经常需要用到同步,而用得最多的也许是synchronized关键字了,下面看看这个关键字的用法. 因为synchronized关键字涉及到锁的概念,所以先来了解一些相关的锁知识. j ...
- 【原创】对Java的synchronized关键字的学习
在Java中,每一个线程都有一个内部锁.当我们使用synchronized关键字时,就是利用这个内部锁来实现线程对某个对象的锁定控制. 那么,如果某个对象中有两个方法,方法一和方法二都使用了synch ...
- Java多线程synchronized关键字
synchronized关键字代表着同步的意思,在Java中被synchronized修饰的有三种情况 1.同步代码块 //锁为objsynchronized(obj){ while(true){ i ...
随机推荐
- 神经网络环境搭建,windows上安装theano和keras的流程
今天碰到有朋友问道怎么在windows下安装keras,正好我刚完成搭建,总结下过程,也算是一个教程吧,给有需要的朋友. 步骤一:安装python. 这一步没啥好说的,下载相应的python安装即可, ...
- 百度地图 >> 自定义控件
前言 百度地图API中预定义的UI控件,比如NavigationControl平移缩放控件,CopyrightControl版权控件,MapTypeControl地图类型控件....,这些都继承自抽象 ...
- 在WCF中不使用svc文件直接使用cs文件
在 配置中有个节点可以实现 此功能 <serviceHostingEnvironment multipleSiteBindingsEnabled="true" > &l ...
- Struts2 学习笔记 11 Result part2
之前学习了result type 和global result 我们现在来说一下 1.Dynamic Result动态结果集.先来看一下小项目的目录 首页的两个链接访问user/user?type=1 ...
- [OSG]如何用Shader得到物体的世界坐标
来自:http://www.cnblogs.com/hesicong/archive/2008/05/27/1208312.html 最近群里面有个朋友问我关于如何得到OpenGL世界坐标的问题,当时 ...
- delphi 2010 资源文件使用
Project Recources... //1提取出资源 procedure TForm1.Button1Click(Sender: TObject);begin w ...
- range-bar
https://github.com/edmodo/range-bar
- cocos2d粒子效果
第9章 粒子效果 游戏开发者通常使用粒子系统来制作视觉特效.粒子系统能够发射大量细小的粒子并对他们进行渲染,而且效率要远高于渲染同样数目的精灵.粒子系统可以模拟下雨.火焰.雪.爆炸.蒸气拖尾以及其他多 ...
- LeetCode: Reverse Words in a String:Evaluate Reverse Polish Notation
LeetCode: Reverse Words in a String:Evaluate Reverse Polish Notation Evaluate the value of an arithm ...
- linux进程后台运行的几种方法
转载:http://hi.baidu.com/ntuxmzvdpzbnuxq/item/79131b93f606a348f0421562 我 们经常会碰到这样的问题,用 telnet/ssh 登录了远 ...