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 ...
随机推荐
- eclipse打包jar文件(含外部jar包)的方法
在项目发布前,使用eclipse导出普通的jar包时,如果配置不好,在运行命令Java -jar /test.jar 时可能会出现如下三类错误信息: 1.no main manifest attrib ...
- CentOS 6.0最小化编译安装Nginx+MySQL+PHP+Zend
http://www.osyunwei.com/archives/235.html 引言: 操作系统:CentOS 6.0 32位 下载地址:http://mirrors.163.co ...
- java hashtable
java hashtable Hashtables提供了一个很有用的方法可以使应用程序的性能达到最佳. Hashtables(哈希表)在计算机领域中已不 是一个新概念了.它们是用来加快计算机的处理速度 ...
- TX Textcontrol 使用总结五——添加图片
实现如图所示效果: 实现代码如下所示: 注意,此处不做代码格式化处理... using System;using System.Collections.Generic;using System.Dra ...
- iphone Dev 开发实例8: Parsing an RSS Feed Using NSXMLParser
From : http://useyourloaf.com/blog/2010/10/16/parsing-an-rss-feed-using-nsxmlparser.html Structure o ...
- 最简单的ioc容器代码(低仿Spring )
Spring 的一大核心就是IOC,控制反转(依赖注入). 对象交由容器去控制,降低耦合性. Spring 的ioc实现原理其实很简单,容器启动后读取并解析配置文件,根据配置文件中<bean&g ...
- Eclipse中Maven+Spring3.2.8+SpringMVC HelloWorld项目
本文适合有一定spring和springmvc基础,并想使用Maven管理项目的人. 源码打包:http://pan.baidu.com/s/1hqurUcs 转载请声明出处(http://www.c ...
- [实变函数]5.6 Lebesgue 积分的几何意义 $\bullet$ Fubini 定理
1 本节推广数学分析中的 Fubini 定理. 为此, 先引入 (1)(从低到高) 对 $A\subset \bbR^p, B\subset\bbR^q$, $$\bex A\times B=\sed ...
- 张恭庆编《泛函分析讲义》第二章第2节 $Riesz$ 定理及其应用习题解答
在本节中, $\scrH$ 均指 $Hilbert$ 空间. 1.在极大闭子空间的交的最佳逼近元 设 $f_1,f_2,\cdots,f_n$ 是 $\scrH$ 上的一组线性有界泛函, $$\bex ...
- A^B问题
题目:输入包括两个正整数 A 和 B(1 <= A, B <= 10000),A 和 B 之前用空格隔开 ★数据输出 输出一行,若 A ^ B < 10000,则直接输出结果(不包含 ...