Java synchronized 多线程同步问题详解
2018-02-23 09:40
387 查看
https://segmentfault.com/a/1190000009225706
同步静态方法(
同步代码块一(
同步代码块二(
public class MainTest {
public static void main(String[] args) {
Bank xMBank = new Bank();
Bank xGBank = new Bank();
Bank xHBank = new Bank();
Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明");
Thread xGThread = new Thread(() -> xGBank.deposit(xGBank, 200), "小刚");
Thread xHThread = new Thread(() -> xHBank.deposit(xHBank, 200), "小红");
xMThread.start();
xGThread.start();
xHThread.start();
}
}
class Bank {
private int money = 1000;
public synchronized void deposit(Bank bank, int money) {
// synchronized (this) { // 同步方法块(实例对象)
// synchronized (bank) { // 同步方法块(实例对象)
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "--当前银行余额为:" + this.money);
this.money += money;
System.out.println(threadName + "--存入后银行余额为:" + this.money);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// }
}
}
运行结果:
修改上述代码,实现同步操作。这里将有两种方案:只实例化一个或在方法块中的锁定类对象。方案一、多个线程只对同一个实例对象操作:
package com.wuxianjiezh.demo.threadpool;
public class MainTest {
public static void main(String[] args) {
Bank xMBank = new Bank();
// Bank xGBank = new Bank();
// Bank xHBank = new Bank();
Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明");
Thread xGThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小刚");
Thread xHThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小红");
xMThread.start();
xGThread.start();
xHThread.start();
}
}
class Bank {
private int money = 1000;
public synchronized void deposit(Bank bank, int money) {
// synchronized (this) { // 同步方法块(实例对象)
// synchronized (bank) { // 同步方法块(实例对象)
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "--当前银行余额为:" + this.money);
this.money += money;
System.out.println(threadName + "--存入后银行余额为:" + this.money);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// }
}
}
运行结果:
方案二、在方法块中锁定类对象:package com.wuxianjiezh.demo.threadpool;
public class MainTest {
public static void main(String[] args) {
Bank xMBank = new Bank();
Bank xGBank = new Bank();
Bank xHBank = new Bank();
Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明");
Thread xGThread = new Thread(() -> xGBank.deposit(xGBank, 200), "小刚");
Thread xHThread = new Thread(() -> xHBank.deposit(xHBank, 200), "小红");
xMThread.start();
xGThread.start();
xHThread.start();
}
}
class Bank {
private int money = 1000;
public void deposit(Bank bank, int money) {
synchronized (Bank.class) { // 全局锁
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "--当前银行余额为:" + this.money);
this.money += money;
System.out.println(threadName + "--存入后银行余额为:" + this.money);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
public class MainTest {
public static void main(String[] args) {
Bank xMBank = new Bank();
Bank xGBank = new Bank();
Bank xHBank = new Bank();
Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明");
Thread xGThread = new Thread(() -> xGBank.deposit(xGBank, 200), "小刚");
Thread xHThread = new Thread(() -> xHBank.deposit(xHBank, 200), "小红");
xMThread.start();
xGThread.start();
xHThread.start();
}
}
class Bank {
private int money = 1000;
public void deposit(Bank bank, int money) {
String threadName = Thread.currentThread().getName();
synchronized (Bank.class) { // 同步方法块(实例对象)
System.out.println(threadName + "--当前银行余额为:" + this.money);
this.money += money;
System.out.println(threadName + "--存入后银行余额为:" + this.money);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
// 假设这里是非常耗时,并且不需要同步控制的操作
Thread.sleep(2000);
System.out.println(threadName + "--和钱无关,不需要同步控制的操作");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
package com.wuxianjiezh.demo.threadpool;
public class MainTest {
public static void main(String[] args) {
Bank xMBank = new Bank();
Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明");
Thread xGThread = new Thread(xMBank::showInfo, "小刚");
xMThread.start();
xGThread.start();
}
}
class Bank {
private int money = 1000;
public void deposit(Bank bank, int money) {
long begin = System.currentTimeMillis();
String threadName = Thread.currentThread().getName();
synchronized (this) { // 同步方法块(实例对象)
this.money += money;
try {
System.out.println(threadName + "--当前银行余额为:" + this.money);
// 模拟一个非常耗时的操作
Thread.sleep(5000);
System.out.println(threadName + "--存入后银行余额为:" + this.money);
long end = System.currentTimeMillis();
System.out.println(threadName + "--存入耗时:" + (end - begin));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 一个与资金操作没有任务关系的同步方法
*/
public void showInfo() {
long begin = System.currentTimeMillis();
String threadName = Thread.currentThread().getName();
synchronized (this) {
try {
System.out.println(threadName + "--开始查看银行信息");
Thread.sleep(5000);
System.out.println(threadName + "--银行详细信息...");
long end = System.currentTimeMillis();
System.out.println(threadName + "--查看耗时:" + (end - begin));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
针对上面的情况,我们可以采用多个实例对象锁的方案解决,比如:package com.wuxianjiezh.demo.threadpool;
public class MainTest {
public static void main(String[] args) {
Bank xMBank = new Bank();
Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明");
Thread xGThread = new Thread(xMBank::showInfo, "小刚");
xMThread.start();
xGThread.start();
}
}
class Bank {
private int money = 1000;
private final Object syncDeposit = new Object(); // 同步锁
private final Object syncShowInfo = new Object(); // 同步锁
public void deposit(Bank bank, int money) {
long begin = System.currentTimeMillis();
String threadName = Thread.currentThread().getName();
synchronized (this.syncDeposit) { // 同步方法块(实例对象)
this.money += money;
try {
System.out.println(threadName + "--当前银行余额为:" + this.money);
// 模拟一个非常耗时的操作
Thread.sleep(5000);
System.out.println(threadName + "--存入后银行余额为:" + this.money);
long end = System.currentTimeMillis();
System.out.println(threadName + "--存入耗时:" + (end - begin));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 一个与资金操作没有任务关系的同步方法
*/
public void showInfo() {
long begin = System.currentTimeMillis();
String threadName = Thread.currentThread().getName();
synchronized (this.syncShowInfo) {
try {
System.out.println(threadName + "--开始查看银行信息");
Thread.sleep(5000);
System.out.println(threadName + "--银行详细信息...");
long end = System.currentTimeMillis();
System.out.println(threadName + "--查看耗时:" + (end - begin));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
public class MainTest {
public static void main(String[] args) {
Bank xMBank = new Bank();
Bank xGBank = new Bank();
Bank xHBank = new Bank();
Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明");
Thread xGThread = new Thread(() -> xGBank.deposit(xGBank, 200), "小刚");
Thread xHThread = new Thread(() -> xHBank.deposit(xHBank, 200), "小红");
xMThread.start();
xGThread.start();
xHThread.start();
}
}
class Bank {
private static int money = 1000;
public synchronized static void deposit(Bank bank, int money) {
// synchronized (Bank.class) { // 全局锁
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "--当前银行余额为:" + Bank.money);
Bank.money += money;
System.out.println(threadName + "--存入后银行余额为:" + Bank.money);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// }
}
}
运行结果:
1. 引言
在 Java 多线程编程中,我们常需要考虑线程安全问题,其中关键字synchronized在线程同步中就扮演了非常重要的作用。下面就对
synchronized进行详细的示例讲解,其中本文构建
thread的写法是采用 Java 8 新增的 Lambda 表达式。如果你对 Lambda 表达式还不了解,可以查看我之前的文章《Java 8 Lambda 表达式详解》。
2. synchronized 锁的是什么
首先我们明确一点,synchronized锁的不是代码,锁的都是对象。锁的对象有以下几种:同步非静态方法(
synchronized method),锁是当前对象的实例对象。
同步静态方法(
synchronized static method),锁是当前对象的类对象(Class 对象)。
同步代码块一(
synchronized (this),
synchronized (类实例对象)),锁是小括号
()中的实例对象。
同步代码块二(
synchronized (类.class)),锁是小括号
()中的类对象(Class 对象)。
2.1 实例对象锁与类对象锁
1)实例对象锁,不同的实例拥有不同的实例对象锁,所以对于同一个实例对象,在同一时刻只有一个线程可以访问这个实例对象的同步方法;不同的实例对象,不能保证多线程的同步操作。2)类对象锁(全局锁),在 JVM 中一个类只有一个与之对应的类对象,所以在同一时刻只有一个线程可以访问这个类的同步方法。3. 示例分析
3.1 同步非静态实例方法
同步非静态方法,实际上锁定的是当前对象的实例对象。在同一时刻只有一个线程可以访问该实例的同步方法,但对于多个实例的同步方法,不同实例之间对同步方法的访问是不受同步影响(synchronized同步失效)。首先我们尝试下,
synchronized同步失败的情况:package com.wuxianjiezh.demo.threadpool;
public class MainTest {
public static void main(String[] args) {
Bank xMBank = new Bank();
Bank xGBank = new Bank();
Bank xHBank = new Bank();
Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明");
Thread xGThread = new Thread(() -> xGBank.deposit(xGBank, 200), "小刚");
Thread xHThread = new Thread(() -> xHBank.deposit(xHBank, 200), "小红");
xMThread.start();
xGThread.start();
xHThread.start();
}
}
class Bank {
private int money = 1000;
public synchronized void deposit(Bank bank, int money) {
// synchronized (this) { // 同步方法块(实例对象)
// synchronized (bank) { // 同步方法块(实例对象)
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "--当前银行余额为:" + this.money);
this.money += money;
System.out.println(threadName + "--存入后银行余额为:" + this.money);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// }
}
}
运行结果:
小明--当前银行余额为:1000 小刚--当前银行余额为:1000 小明--存入后银行余额为:1200 小红--当前银行余额为:1000 小刚--存入后银行余额为:1200 小红--存入后银行余额为:1200从上面的运行结果,我们发现对
Bank中
money的操作并没有同步,
synchronized失效了?这是因为实例对象锁,只对同一个实例生效,对同一个对象的不同实例不保证同步。
修改上述代码,实现同步操作。这里将有两种方案:只实例化一个或在方法块中的锁定类对象。方案一、多个线程只对同一个实例对象操作:
package com.wuxianjiezh.demo.threadpool;
public class MainTest {
public static void main(String[] args) {
Bank xMBank = new Bank();
// Bank xGBank = new Bank();
// Bank xHBank = new Bank();
Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明");
Thread xGThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小刚");
Thread xHThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小红");
xMThread.start();
xGThread.start();
xHThread.start();
}
}
class Bank {
private int money = 1000;
public synchronized void deposit(Bank bank, int money) {
// synchronized (this) { // 同步方法块(实例对象)
// synchronized (bank) { // 同步方法块(实例对象)
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "--当前银行余额为:" + this.money);
this.money += money;
System.out.println(threadName + "--存入后银行余额为:" + this.money);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// }
}
}
运行结果:
小明--当前银行余额为:1000 小明--存入后银行余额为:1200 小红--当前银行余额为:1200 小红--存入后银行余额为:1400 小刚--当前银行余额为:1400 小刚--存入后银行余额为:1600 ...可以看到,结果正确执行。因为对于同一个实例对象,各线程之间访问其中的同步方法是互斥的。
方案二、在方法块中锁定类对象:package com.wuxianjiezh.demo.threadpool;
public class MainTest {
public static void main(String[] args) {
Bank xMBank = new Bank();
Bank xGBank = new Bank();
Bank xHBank = new Bank();
Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明");
Thread xGThread = new Thread(() -> xGBank.deposit(xGBank, 200), "小刚");
Thread xHThread = new Thread(() -> xHBank.deposit(xHBank, 200), "小红");
xMThread.start();
xGThread.start();
xHThread.start();
}
}
class Bank {
private int money = 1000;
public void deposit(Bank bank, int money) {
synchronized (Bank.class) { // 全局锁
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "--当前银行余额为:" + this.money);
this.money += money;
System.out.println(threadName + "--存入后银行余额为:" + this.money);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
小明--当前银行余额为:1000 小明--存入后银行余额为:1200 小红--当前银行余额为:1000 小红--存入后银行余额为:1200 小刚--当前银行余额为:1000 小刚--存入后银行余额为:1200思考:从结果中我们发现,线程是同步操作了,但为什么在我们的
money怎么才 1200 啊?要回答上面问题也很简单,首先线程是同步操作了,这个没有疑问,说明我们的全局锁生效了,那为什么钱少了,因为我们这里
mew了三个对象,三个对象都有各自的
money,他们并不共享,所以最后都是 1200,最终一共还是增加了 6000,钱一点没有少喔。那有没有办法,让这些线程间共享
money呢?方法很简单,只要设置
money为
static即可。
3.1.1 对同步代码块优化的思考
对于一个方法,可能包含多个操作部分,而每个操作部分的消耗各不相同,而且并不是所有的操作都是需要同步控制的,那么,是否可以将那些影响效率,又不需要同步操作的内容,提取到同步代码块外呢?请看以下示例:package com.wuxianjiezh.demo.threadpool;public class MainTest {
public static void main(String[] args) {
Bank xMBank = new Bank();
Bank xGBank = new Bank();
Bank xHBank = new Bank();
Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明");
Thread xGThread = new Thread(() -> xGBank.deposit(xGBank, 200), "小刚");
Thread xHThread = new Thread(() -> xHBank.deposit(xHBank, 200), "小红");
xMThread.start();
xGThread.start();
xHThread.start();
}
}
class Bank {
private int money = 1000;
public void deposit(Bank bank, int money) {
String threadName = Thread.currentThread().getName();
synchronized (Bank.class) { // 同步方法块(实例对象)
System.out.println(threadName + "--当前银行余额为:" + this.money);
this.money += money;
System.out.println(threadName + "--存入后银行余额为:" + this.money);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
// 假设这里是非常耗时,并且不需要同步控制的操作
Thread.sleep(2000);
System.out.println(threadName + "--和钱无关,不需要同步控制的操作");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
小明--当前银行余额为:1000 小明--存入后银行余额为:1200 小红--当前银行余额为:1000 小红--存入后银行余额为:1200 小刚--当前银行余额为:1000 小刚--存入后银行余额为:1200这时发现,各线程虽然都有自己的实例化对象,但其中操作
小明--和钱无关,不需要同步控制的操作
小红--和钱无关,不需要同步控制的操作
小刚--和钱无关,不需要同步控制的操作
money的部分是同步的,对于与
money无关的操作则又是异步的。结论:可以通过减少同步区域来优化同步代码块。
3.1.2 对同步代码块优化的思考(进阶)
我们知道同步的对象不是实例对象就是类对象。现在假设一个类有多个同步方法,那么当某个线程进入其中一个同步方法时,这个类的其它同步方法也会被锁住,造成其它与当前锁定操作的同步方法毫无关系的同步方法也被锁住,最后的结果就是影响了整个多线程执行的性能,使原本不需要互斥的方法也都进行了互斥操作。比如:package com.wuxianjiezh.demo.threadpool;
public class MainTest {
public static void main(String[] args) {
Bank xMBank = new Bank();
Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明");
Thread xGThread = new Thread(xMBank::showInfo, "小刚");
xMThread.start();
xGThread.start();
}
}
class Bank {
private int money = 1000;
public void deposit(Bank bank, int money) {
long begin = System.currentTimeMillis();
String threadName = Thread.currentThread().getName();
synchronized (this) { // 同步方法块(实例对象)
this.money += money;
try {
System.out.println(threadName + "--当前银行余额为:" + this.money);
// 模拟一个非常耗时的操作
Thread.sleep(5000);
System.out.println(threadName + "--存入后银行余额为:" + this.money);
long end = System.currentTimeMillis();
System.out.println(threadName + "--存入耗时:" + (end - begin));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 一个与资金操作没有任务关系的同步方法
*/
public void showInfo() {
long begin = System.currentTimeMillis();
String threadName = Thread.currentThread().getName();
synchronized (this) {
try {
System.out.println(threadName + "--开始查看银行信息");
Thread.sleep(5000);
System.out.println(threadName + "--银行详细信息...");
long end = System.currentTimeMillis();
System.out.println(threadName + "--查看耗时:" + (end - begin));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
小明--当前银行余额为:1200 小明--存入后银行余额为:1200 小明--存入耗时:5000 小刚--开始查看银行信息 小刚--银行详细信息... 小刚--查看耗时:10000从运行结果中,我们看到小刚这个线程平白无故多等了 5 秒钟,严重影响了线程性能。
针对上面的情况,我们可以采用多个实例对象锁的方案解决,比如:package com.wuxianjiezh.demo.threadpool;
public class MainTest {
public static void main(String[] args) {
Bank xMBank = new Bank();
Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明");
Thread xGThread = new Thread(xMBank::showInfo, "小刚");
xMThread.start();
xGThread.start();
}
}
class Bank {
private int money = 1000;
private final Object syncDeposit = new Object(); // 同步锁
private final Object syncShowInfo = new Object(); // 同步锁
public void deposit(Bank bank, int money) {
long begin = System.currentTimeMillis();
String threadName = Thread.currentThread().getName();
synchronized (this.syncDeposit) { // 同步方法块(实例对象)
this.money += money;
try {
System.out.println(threadName + "--当前银行余额为:" + this.money);
// 模拟一个非常耗时的操作
Thread.sleep(5000);
System.out.println(threadName + "--存入后银行余额为:" + this.money);
long end = System.currentTimeMillis();
System.out.println(threadName + "--存入耗时:" + (end - begin));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 一个与资金操作没有任务关系的同步方法
*/
public void showInfo() {
long begin = System.currentTimeMillis();
String threadName = Thread.currentThread().getName();
synchronized (this.syncShowInfo) {
try {
System.out.println(threadName + "--开始查看银行信息");
Thread.sleep(5000);
System.out.println(threadName + "--银行详细信息...");
long end = System.currentTimeMillis();
System.out.println(threadName + "--查看耗时:" + (end - begin));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
小刚--开始查看银行信息 小明--当前银行余额为:1200 小刚--银行详细信息... 小明--存入后银行余额为:1200 小明--存入耗时:5000 小刚--查看耗时:5000我们发现,两个线程间同步被取消了,性能问题也解决了。总结:可以创建不同同步方法的不同同步锁(减小锁的范围)来优化同步代码块。
3.2 同步静态方法
同步静态方法,锁的是类对象而不是某个实例对象,所以可以理解为对于静态方法的锁是全局的锁,同步也是全局的同步。package com.wuxianjiezh.demo.threadpool;public class MainTest {
public static void main(String[] args) {
Bank xMBank = new Bank();
Bank xGBank = new Bank();
Bank xHBank = new Bank();
Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明");
Thread xGThread = new Thread(() -> xGBank.deposit(xGBank, 200), "小刚");
Thread xHThread = new Thread(() -> xHBank.deposit(xHBank, 200), "小红");
xMThread.start();
xGThread.start();
xHThread.start();
}
}
class Bank {
private static int money = 1000;
public synchronized static void deposit(Bank bank, int money) {
// synchronized (Bank.class) { // 全局锁
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "--当前银行余额为:" + Bank.money);
Bank.money += money;
System.out.println(threadName + "--存入后银行余额为:" + Bank.money);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// }
}
}
运行结果:
小明--当前银行余额为:1000 小明--存入后银行余额为:1200 小红--当前银行余额为:1200 小红--存入后银行余额为:1400 小刚--当前银行余额为:1400 小刚--存入后银行余额为:1600
相关文章推荐
- Java中利用synchronized关键字实现多线程同步问题 .
- java多线程-多线程的安全问题-多线程同步代码块-锁Synchronized
- Java【多线程知识总结(7)】多线程同步问题-关于synchronized代码块和synchronized方法的应用
- Java中利用synchronized关键字实现多线程同步问题
- Java【多线程知识总结(7)】多线程同步问题-关于synchronized代码块和synchronized方法的应用
- Java多线程同步Synchronized详解
- 详解Java中synchronized关键字的死锁和内存占用问题
- Java中文问题详解
- Java编码问题详解
- Java中文问题详解
- 详解java源文件涉及到的package问题
- java实现 生产者和消费者问题 多线程同步示例
- JAVA基础-关于Java中中文问题详解
- Java编程十大典型问题详解收藏
- Java中文问题详解(转载与java-cn.net)
- java中文问题详解
- 学习总结:java面试方法重写详解,静态代码块和构造方法执行顺序问题
- Java中文问题详解
- java中文问题详解
- java/jsp中 中文问题详解