jdk线程的同步问题
一、银行取款引出的问题
模拟银行取钱的例子:
public class ThreadDemo06 {
public static void main(String[] args) {
Bank bank = new Bank();
Runnable runnable = new MoneyThread(bank);
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
thread2.start();
}
}
class Bank {
private int money = 1000;
public int get(int number) {
if (number < 0) {
return -1;
} else if (number > money) {
return -2;
} else if (money < 0) {
return -3;
} else {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
money -= number;
System.err.println("还剩:" + money);
return number;
}
}
}
class MoneyThread implements Runnable {
private Bank bank;
public MoneyThread(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
bank.get(800);
}
}
运行可能的结果:
还剩:200
还剩:-600
造成此类问题的根本原因在于,多个线程在操作共享的数据或者操作共享数据的线程代码有多行,当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程的安全问题的产生。
二、问题的解决方案
在线程使用一个资源时为其加锁即可。访问资源的第一个线程为其加上锁以后,其他线程就不能再使用那个资源,除非被解锁。同一时间段内只能有一个线程进行,其他线程要等待此线程完成之后才可以继续执行。
将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码时其他线程是不可以参与运算的,必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算;
保证取钱和修改余额同时完成:
1)使用同步代码块,synchronized(obj){},还需要一个同步监听对象;
2)使用同步方法,使用synchronized去修饰需要同步的方法;
方法一:同步方法
在Java中通过synchronized关键字来完成对对象的加锁。
上述代码加锁的解决方案如下:
class Bank {
private int money = 1000;
public synchronized int get(int number) {
if (number < 0) {
return -1;
} else if (number > money) {
return -2;
} else if (money < 0) {
return -3;
} else {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
money -= number;
System.err.println("还剩:" + money);
return number;
}
}
}
方法二:同步代码块
synchronized块写法:
synchronized(object){
} //表示线程在执行的时候会对object对象上锁
这里的object可以是任意对象,但必须保证多个线程持有的这个object是同一个对象;
synchronized方法是一种粗粒度的并发控制,某一时刻,只能有一个线程执行该synchronized方法;synchronized块则是一种细粒度的并发控制,只有将块中的代码同步,位于方法内、synchronized块之外的代码是可以被多个线程同时访问到的。
三、线程同步的关键知识点
1)Java中的每个对象都有一个锁(lock)或者叫做监视器(monitor),当访问某个对象的synchronized方法时,表示将该对象上锁,此时其他任何线程都无法再去访问该对象的synchronized方法了,直到之前的那个线程执行方法完毕后(或者是抛出了异常),那么将该对象的锁释放掉,其他线程才可能再去访问该对象的其他synchronized方法;
public class ThreadDemo07 {
public static void main(String[] args) {
Example example = new Example();
Runnable runnable = new TheThread(example); //同一对象
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
thread2.start();
}
}
class Example {
public synchronized void execute() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("execute :" + i);
}
}
}
class TheThread implements Runnable {
private Example example;
public TheThread(Example example) {
this.example = example;
}
public void run() {
example.execute();
}
}
上述代码的执行:由于是对同一对象产生的线程,当两个不同线程进行访问的时候,谁先进入synchronized方法就将该example对象上锁了,其他线程就没有办法再进入该对象的任何同步方法了,所以只有当一个线程执行完毕或者抛出异常后第二个线程才能进行访问。
public class ThreadDemo08 {
public static void main(String[] args) {
Runnable runnable = new TheThread(new Example());
Runnable runnable2 = new TheThread(new Example());
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable2);
thread1.start();
thread2.start();
}
}
class Example {
public synchronized void execute() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("execute :" + i);
}
}
}
class TheThread implements Runnable {
private Example example;
public TheThread(Example example) {
this.example = example;
}
public void run() {
example.execute();
}
}
上述代码的执行:由于是两个线程对两个不同对象进行访问操作。那么这2个线程就没有任何关联,各自访问各自的对象,互不干扰。
public class ThreadDemo09 {
public static void main(String[] args) {
Example example = new Example();
Runnable runnable = new TheThread(example);//同一对象
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
thread2.start();
}
}
class Example {
public synchronized void execute() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("execute :" + i);
}
}
public synchronized void execute2() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("execute2 :" + i);
}
}
}
class TheThread implements Runnable {
private Example example;
public TheThread(Example example) {
this.example = example;
}
public void run() {
example.execute();
}
}
class TheThread2 implements Runnable {
private Example example;
public TheThread2(Example example) {
this.example = example;
}
public void run() {
example.execute2();
}
}
上述代码的执行结果:是由同一对象生成的两个不同的线程,当两个不同的线程访问同一对象不同的synchronized方法时,谁先进入第一个synchronized方法,那么该线程就将该对象上锁了,其他线程是没有办法再对该对象的任何synchronized方法进行访问。
public class ThreadDemo10 {
public static void main(String[] args) {
Example example = new Example();
Runnable runnable = new TheThread(example);
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
thread2.start();
}
}
class Example {
public synchronized static void execute() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("execute :" + i);
}
}
public synchronized static void execute2() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("execute2 :" + i);
}
}
}
上述代码的执行结果:由于静态方法是属于类级别的,当一个方法锁住后,只有等第一个线程执行完毕以后第二个线程才能进入。
2)synchronized方法使用了static关键字进行修饰,表示将该对象的Class对象加锁
public class ThreadDemo11 {
public static void main(String[] args) {
Runnable runnable = new TheThread(new Example());
Runnable runnable2 = new TheThread(new Example());
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable2);
thread1.start();
thread2.start();
}
}
class Example {
public synchronized static void execute() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("execute :" + i);
}
}
public synchronized static void execute2() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("execute2 :" + i);
}
}
}
上述代码的执行结果:虽然是针对两个不同对象生成的不同的线程,但是由于synchronized方法使用了static关键字进行修饰,表示将该对象的Class对象加锁。所以只有等一个线程执行完毕后,其他线程才能进入访问。
3) 如果一个对象有多个synchronized方法,某一时刻某个线程已经进入到了某个synchronized方法,那么在该方法没有执行完毕前,其他线程是无法访问该对象的任何synchronized方法的。
4)如果某个synchronized方法是static的,那么当线程访问该方法时,它的锁并不是synchronized方法所在的对象,而是synchronized方法所在的对象所对应的Class对象,因为Java中无论一个类有多少个对象,这些对象会对应唯一一个Class对象,因此当线程分别访问同一个类的两个对象的两个static, synchronized方法时,他们的执行顺序也是有顺序的,也就是说一个线程先去执行方法,执行完毕后另一个线程才开始执行。
public class ThreadDemo02 {
public static void main(String[] args) {
C c = new C();
Thread t1 = new T1(c);
Thread t2 = new T2(c);
t1.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
class C {
public synchronized static void hello() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello");
}
public synchronized void world() {
System.out.println("world");
}
}
class T1 extends Thread {
private C c;
public T1(C c) {
this.c = c;
}
@Override
public void run() {
c.hello();
}
}
class T2 extends Thread {
private C c;
public T2(C c) {
this.c = c;
}
@Override
public void run() {
c.world();
}
}
执行结果:先执行world,然后才输出hello,原因是static是给当前对象的Class对象上锁,而没有static的是给当前对象上锁,两把锁锁的对象不同,所以并没有影响。
四、线程同步总结
synchronized修饰方法
1)非静态方法:默认的同步监听器对象是this;
2))静态方法:默认的同步监听器对象是该方法所在类的Class对象。
若线程是实现方式
1)同步代码块:同步监听对象可以选this、这个方法所在类的Class对象、任一不变对象;
2)同步方法:此时可以使用synchronized直接修饰run方法,因为同步监听器是this。
若线程是继承方式
1)同步代码块:同步监听器可以选用该方法所在类的Class对象、任一不变对象;
2)同步方法:此时不能使用synchronized直接修饰run方法;
3)总结:只要是继承方式,不论是同步代码块还是同步方法均不能使用this。
同步的利弊
1)好处:解决了线程的安全问题;
2)弊端:相对降低了效率,因为同步外的线程的都会判断同步锁;
3)前提:同步中必须有多个线程并使用同一个锁。
jdk线程的同步问题的更多相关文章
- jdk线程池主要原理
本文转自:http://blog.csdn.net/linchengzhi/article/details/7567397 正常创建一个线程的时候,我们是这样的:new thread(Runnable ...
- 牛客网Java刷题知识点之为什么HashMap不支持线程的同步,不是线程安全的?如何实现HashMap的同步?
不多说,直接上干货! 这篇我是从整体出发去写的. 牛客网Java刷题知识点之Java 集合框架的构成.集合框架中的迭代器Iterator.集合框架中的集合接口Collection(List和Set). ...
- java之线程(线程的创建方式、java中的Thread类、线程的同步、线程的生命周期、线程之间的通信)
CPU:10核 主频100MHz 1核 主频 3GHz 那么哪一个CPU比较好呢? CPU核不是越多越好吗?并不一定.主频用于衡量GPU处理速度的快慢,举个例子10头牛运送货物快还是1架飞机运 ...
- jdk线程池ThreadPoolExecutor工作原理解析(自己动手实现线程池)(一)
jdk线程池ThreadPoolExecutor工作原理解析(自己动手实现线程池)(一) 线程池介绍 在日常开发中经常会遇到需要使用其它线程将大量任务异步处理的场景(异步化以及提升系统的吞吐量),而在 ...
- Java线程:线程的同步-同步方法
Java线程:线程的同步-同步方法 线程的同步是保证多线程安全访问竞争资源的一种手段. 线程的同步是Java多线程编程的难点,往往开发者搞不清楚什么是竞争资源.什么时候需要考虑同步,怎么同步等等问 ...
- Java线程:线程的同步与锁
一.同步问题提出 线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏. 例如:两个线程ThreadA.ThreadB都操作同一个对象Foo对象,并修改Foo对象上的数据. public ...
- java 线程数据同步
java 线程数据同步 由买票实例 //java线程实例 //线程数据同步 //卖票问题 //避免重复卖票 //线程 class xc1 implements Runnable{ //定义为静态,可以 ...
- Java多线程-线程的同步与锁
一.同步问题提出 线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏.例如:两个线程ThreadA.ThreadB都操作同一个对象Foo对象,并修改Foo对象上的数据. package ...
- C++ 11 线程的同步与互斥
这次写的线程的同步与互斥,不依赖于任何系统,完全使用了C++11标准的新特性来写的,就连线程函数都用了C++11标准的lambda表达式. /* * thread_test.cpp * * Copyr ...
随机推荐
- HDP2.4安装(一):Centos7安装
在进入大数据领域来,一直使用微软的Azure HDInsight,当前在中国区仅支持在Windows系统上部署集群,虽然创建的过程很简单,但对于开发人员来说,就是一个黑盒子,在更深入的研究和开发扩展的 ...
- S盒
在密码学中,一个S盒(Substitution-box,置换盒)是对称密钥算法执行置换计算的基本结构.在块密码中,它们通常用于模糊密钥和密文之间的关系——香农的混淆理论.[1] 通常,S-Box接受特 ...
- 【jmeter】逻辑控制器
JMeter中的Logic Controller用于为Test Plan中的节点添加逻辑控制器. JMeter中的Logic Controller分为两类:一类用来控制Test Plan执行过程中节点 ...
- nokia5230 出厂设置
你手机sim卡里的电话没事,还有储存卡里都没事,这个只是针对手机内存,如果不放心,拿你不用的手机卡,拔了内存卡格式化你好,我来具体说说吧首先如果你要单一恢复出厂设置,代码是输入*#7780#密码没改是 ...
- cowboy使用restful的例子
直接贴代码,一切尽在不言中 %% cowboy的restful的文档,一定要好好阅读http://ninenines.eu/docs/en/cowboy/HEAD/manual/cowboy_rest ...
- SQLServer数据库字典维护方法
启用SQLServer启用管理器,以2008为例 1.设置表信息描述 选中要设置的表,右键点击“属性” . 选择扩展属性 填写要求: 名称:MS_Description 值: 模块名称-表名称 修改语 ...
- [系统集成] Android 自动构建系统
一.简介 android app 自动构建服务器用于自动下载app代码.自动打包.发布,要建立这样的服务器,关键要解决以下几个问题: 1. android app 自动化打包android 的打包一般 ...
- 黄聪:wordpress如何扩展TinyMCE编辑器,添加自定义按钮及功能
在functions.php文件里面添加: add_action( 'admin_init', 'my_tinymce_button' ); function my_tinymce_button() ...
- NeHe OpenGL教程 第四十课:绳子的模拟
转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...
- Python 图形 GUI 库 pyqtgraph
原文 Python 图形 GUI 库 pyqtgraph pyqtgraph 是纯 Python 图形 GUI 库,基于PyQT4 /pyside和NumPy.它主要目的用于在数学/科学/工程中.M ...