当有多个线程竞争共享资源时,对资源的访问顺序敏感,则可能造成数据不一致。为了保证共享资源不被多个线程同时访问,则需要将竞争共享资源的代码置于临界区,临界区保证在同一时间内最多只能有一个线程执行该代码段。

先看一段由竞争共享资源造成数据不一致的代码:

 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 关键字的更多相关文章

  1. Java 多线程 —— synchronized关键字

    java 多线程 目录: Java 多线程——基础知识 Java 多线程 —— synchronized关键字 java 多线程——一个定时调度的例子 java 多线程——quartz 定时调度的例子 ...

  2. Java的synchronized关键字:同步机制总结

    JAVA中synchronized关键字能够作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块.搞清楚synchronized锁定的是哪个对象,就能帮助我们设计更安全的多线程程 ...

  3. java中synchronized关键字分析

    今天我们来分析一下java中synchronized关键字.首先来看一段java代码:(本地编译环境为mac,jdk1.8的环境) Demo.java package com.example.spri ...

  4. Java基础-synchronized关键字的用法(转载)

    synchronized--同步 顾名思义是用于同步互斥的作用的. 这里精简的记一下它的使用方法以及意义: 当synchronized修饰 this或者非静态方法或者是一个实例的时候,所同步的锁是加在 ...

  5. 从分布式锁角度理解Java的synchronized关键字

    分布式锁 分布式锁就以zookeeper为例,zookeeper是一个分布式系统的协调器,我们将其理解为一个文件系统,可以在zookeeper服务器中创建或删除文件夹或文件.设D为一个数据系统,不具备 ...

  6. java基础Synchronized关键字之对象锁

    java中Synchronized关键字之对象锁    当有多个线程对一个共享数据进行操作时,需要注意多线程的安全问题. 多线程的同步机制对资源进行加锁,使得在同一个时间,只有一个线程可以进行操作,同 ...

  7. java中synchronized关键字的用法

    在java编程中,经常需要用到同步,而用得最多的也许是synchronized关键字了,下面看看这个关键字的用法. 因为synchronized关键字涉及到锁的概念,所以先来了解一些相关的锁知识. j ...

  8. 【原创】对Java的synchronized关键字的学习

    在Java中,每一个线程都有一个内部锁.当我们使用synchronized关键字时,就是利用这个内部锁来实现线程对某个对象的锁定控制. 那么,如果某个对象中有两个方法,方法一和方法二都使用了synch ...

  9. Java多线程synchronized关键字

    synchronized关键字代表着同步的意思,在Java中被synchronized修饰的有三种情况 1.同步代码块 //锁为objsynchronized(obj){ while(true){ i ...

随机推荐

  1. Rop 文件上传解决思路

    由于服务请求报文是一个文本,无法直接传送二进制的文件内容,因此必须采用某种转换机制将二进制的文件内容转换为字符串.Rop 采用如下的方式对上传文件进行编码:<fileType>@<B ...

  2. Unity3D之Mecanim动画系统学习笔记(十):Mecanim动画的资源加载相关

    资源加载是必备的知识点,这里就说说Mecanim动画的资源如何打包及加载. 注意,Unity4.x和Unity5.x的AssetBundle打包策略不一样,本笔记是基于Unity4.x的AssetBu ...

  3. Ecshop 学习之路一 2016年6月30日

    以前下载ecshop 都是在ecshop官网上下载,前后台模板都很难看.功能也不太齐全,这次在模板堂下载了ecshop 模板 仿小米的.做一个简单的电商网站. 页面结构还是挺简单的.功能也齐全.用ec ...

  4. 关于 JavaScript 中一个小细节问题 (在控制台中直接 {Name:'王尼玛',Age:20} 对象报错问题)

    在 Chrome 浏览器,大家可能遇到这样一个小问题. 随便输入一个 Object 对象  ,比如 {Name:'王尼玛',Age:20} ,将会报错.之前,也从来没去考虑过到底是为啥原因. 今天,刚 ...

  5. springMVC部署

      一.导入springMVC所需要的jar包   下载地址:http://repo.spring.io/release/org/springframework/spring/   二.springM ...

  6. hibernate AOP

    摘自:http://pandonix.iteye.com/blog/336873/ 此前对于AOP的使用仅限于声明式事务,除此之外在实际开发中也没有遇到过与之相关的问题.最近项目中遇到了以下几点需求, ...

  7. LFI漏洞利用总结(转载)

    主要涉及到的函数include(),require().include_once(),require_once()magic_quotes_gpc().allow_url_fopen().allow_ ...

  8. iOS开发——UI篇&九宫格算法

    九宫格算法 关于iOS开发中九宫格的实现虽然使用不多,而且后面会有更好的方实现,但是作为一个程序员必需要知道的就是九宫格算法的实现. 一:实现思路: (1)明确每一块用得是什么view (2)明确每个 ...

  9. iOS开发——UI篇OC篇&UITableView简单封装

    UITableView简单封装 UITableView时iOS开发中使用最多也是最重的一个UI空间,其实在App Store里面的%80以上的应用都用到了这个控件,所以就给大家介绍一下,前面的文章中也 ...

  10. ios开发——实用技术篇OC篇&获取内存使用情况

    获取内存使用情况 iOS 获取 当前设备 可用内存 及当前 应用 所占内存 (-- ::) 转载 ▼ 标签: ios 设备 可用内存 所占内存 内存 it 分类: iOS // 获取当前设备可用内存及 ...