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 ...
随机推荐
- jQuery基础学习2——DOM和jQuery对象
<body> <h3>例子</h3> <p title="选择你最喜欢的水果." >你最喜欢的水果是?</p> < ...
- 如何开发原生的 JavaScript 插件(知识点+写法)
一.前言 通过 "WWW" 原则我们来了解 JavaScript 插件这个东西 第一个 W "What" -- 是什么?什么是插件,我就不照搬书本上的抽象概念了 ...
- Objective-C的singleton模式
最近因为在ios应用开发中,考虑到一些公共方法的封装使用,就决定使用单例模式的写法了..不知道,Object-c中的单例模式的写法是否和java中的写法是否有所区别?于是阿堂从网上一搜,发现“ Obj ...
- OOP设计模式[JAVA]——03职责链模式
职责链模式 Responsibility of Chain 在职责链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链.请求在这个链上传递,直到链上的某一个对象决定处理此请求.发出这个请求 ...
- Line去年营收超5亿美元 远超竞争对手WhatsApp
原文地址: http://news.cnblogs.com/n/206072/ 凭借着修改表情取悦国际用户的做法,日本移动消息应用 Line 在全球的用户总数已经超过 4 亿.Line.微信.What ...
- 一种基于Welch's t检验的二元关系挖掘
现实中常常需要挖掘两种因素之间的关联,Welch's t检验很适合其中的nomial-numerical的关系挖掘.比如天气状况对销量的影响,或者天气情况对交通流量的影响等等.我们可以按照下雨/不下雨 ...
- 配置集群Nginx+Memcached+Tomcat集群配置
上班之余抽点时间出来写写博文,希望对新接触的朋友有帮助.今天在这里和大家一起学习一下配置集群 1. Nginx Nginx是通过将多个Web Server绑定到同一个IP地址下,以实现多个WebS ...
- CSS模块化
1. Base2. Layout3. Module4. State5. Theme 1) Base rules Base rules are the defaults. eg: ;; } input[ ...
- ios开发——常用经典算法OC篇&冒泡/快速
冒泡排序与快速排序 1.序言 ios开发中涉及到算法的地方还真不多,除非你的应用程序真的非常大,或者你想你的应用程序性能非常好才会去想到关于算法方面的性能优化,而在ios开发中真的能用得到的也就是关于 ...
- Demo Swig
演示使用swig工具创建c语言的java接口,生成.so库和java接口文件. 在此之前先要安装swig,安装方法:sudo apt-get install swig 1.使用eclipse创建工程. ...