Java线程:线程的同步-同步方法
来源:互联网 发布:mac 输入法顺序 编辑:程序博客网 时间:2024/05/21 17:27
源作者地址:http://www.cnblogs.com/riskyer/p/3263032.html
Java线程:线程的同步-同步方法
线程的同步是保证多线程安全访问竞争资源的一种手段。
线程的同步是Java多线程编程的难点,往往开发者搞不清楚什么是竞争资源、什么时候需要考虑同步,怎么同步等等问题,当然,这些问题没有很明确的答案,但有些原则问题需要考虑,是否有竞争资源被同时改动的问题?
在本文之前,请参阅《Java线程:线程的同步与锁》,本文是在此基础上所写的。
对于同步,在具体的Java代码中需要完成一下两个操作:
把竞争访问的资源标识为private;
同步哪些修改变量的代码,使用synchronized关键字同步方法或代码。
当然这不是唯一控制并发安全的途径。
synchronized关键字使用说明
synchronized只能标记非抽象的方法,不能标识成员变量。
为了演示同步方法的使用,构建了一个信用卡账户,起初信用额为100w,然后模拟透支、存款等多个操作。显然银行账户User对象是个竞争资源,而多个并发操作的是账户方法oper(int x),当然应该在此方法上加上同步,并将账户的余额设为私有变量,禁止直接访问。
/** * Java线程:线程的同步 * * @author leizhimin 2009-11-4 11:23:32 */ publicclass Test { publicstaticvoid main(String[] args) { User u = new User("张三", 100); MyThread t1 = new MyThread("线程A", u, 20); MyThread t2 = new MyThread("线程B", u, -60); MyThread t3 = new MyThread("线程C", u, -80); MyThread t4 = new MyThread("线程D", u, -30); MyThread t5 = new MyThread("线程E", u, 32); MyThread t6 = new MyThread("线程F", u, 21); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); t6.start(); } } class MyThread extends Thread { private User u; privateint y = 0; MyThread(String name, User u, int y) { super(name); this.u = u; this.y = y; } publicvoid run() { u.oper(y); } } class User { private String code; privateint cash; User(String code, int cash) { this.code = code; this.cash = cash; } public String getCode() { return code; } publicvoid setCode(String code) { this.code = code; } /** * 业务方法 * @param x 添加x万元 */ publicsynchronizedvoid oper(int x) { try { Thread.sleep(10L); this.cash += x; System.out.println(Thread.currentThread().getName() + "运行结束,增加“" + x +"”,当前用户账户余额为:" + cash); Thread.sleep(10L); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public String toString() { return"User{" + "code='" + code + '\'' + ", cash=" + cash + '}'; } }
输出结果:线程A运行结束,增加“20”,当前用户账户余额为:120线程F运行结束,增加“21”,当前用户账户余额为:141线程E运行结束,增加“32”,当前用户账户余额为:173线程C运行结束,增加“-80”,当前用户账户余额为:93线程B运行结束,增加“-60”,当前用户账户余额为:33线程D运行结束,增加“-30”,当前用户账户余额为:3Process finished with exit code 0
反面教材,不同步的情况,也就是去掉oper(int x)方法的synchronized修饰符,然后运行程序,结果如下:线程A运行结束,增加“20”,当前用户账户余额为:61线程D运行结束,增加“-30”,当前用户账户余额为:63线程B运行结束,增加“-60”,当前用户账户余额为:3线程F运行结束,增加“21”,当前用户账户余额为:61线程E运行结束,增加“32”,当前用户账户余额为:93线程C运行结束,增加“-80”,当前用户账户余额为:61Process finished with exit code 0
注意:通过前文可知,线程退出同步方法时将释放掉方法所属对象的锁,但还应该注意的是,同步方法中还可以使用特定的方法对线程进行调度。这些方法来自于java.lang.Object类。 void notify() 唤醒在此对象监视器上等待的单个线程。 void notifyAll() 唤醒在此对象监视器上等待的所有线程。 void wait() 导致当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll()方法。 void wait(long timeout) 导致当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll()方法,或者超过指定的时间量。 void wait(long timeout,int nanos) 导致当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll()方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。
结合以上方法,处理多线程同步与互斥问题非常重要,著名的生产者-消费者例子就是一个经典的例子,任何语言多线程必学的例子。
Java线程:线程的同步-同步块
对于同步,除了同步方法外,还可以使用同步代码块,有时候同步代码块会带来比同步方法更好的效果。
追其同步的根本的目的,是控制竞争资源的正确的访问,因此只要在访问竞争资源的时候保证同一时刻只能一个线程访问即可,因此Java引入了同步代码快的策略,以提高性能。
在上个例子的基础上,对oper方法做了改动,由同步方法改为同步代码块模式,程序的执行逻辑并没有问题。
/** * Java线程:线程的同步-同步代码块* * @author leizhimin 2009-11-4 11:23:32 */ publicclass Test { publicstaticvoid main(String[] args) { User u = new User("张三", 100); MyThread t1 = new MyThread("线程A", u, 20); MyThread t2 = new MyThread("线程B", u, -60); MyThread t3 = new MyThread("线程C", u, -80); MyThread t4 = new MyThread("线程D", u, -30); MyThread t5 = new MyThread("线程E", u, 32); MyThread t6 = new MyThread("线程F", u, 21); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); t6.start(); } } class MyThread extends Thread { private User u; privateint y = 0; MyThread(String name, User u, int y) { super(name); this.u = u; this.y = y; } publicvoid run() { u.oper(y); } } class User { private String code; privateint cash; User(String code, int cash) { this.code = code; this.cash = cash; } public String getCode() { return code; } publicvoid setCode(String code) { this.code = code; } /** * 业务方法 * * @param x 添加x万元 */ publicvoid oper(int x) { try { Thread.sleep(10L); synchronized (this) { this.cash += x; System.out.println(Thread.currentThread().getName() +"运行结束,增加“" + x +"”,当前用户账户余额为:" + cash); } Thread.sleep(10L); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public String toString() { return"User{" + "code='" + code + '\'' + ", cash=" + cash + '}'; } }
线程E运行结束,增加“32”,当前用户账户余额为:132线程B运行结束,增加“-60”,当前用户账户余额为:72线程D运行结束,增加“-30”,当前用户账户余额为:42线程F运行结束,增加“21”,当前用户账户余额为:63线程C运行结束,增加“-80”,当前用户账户余额为:-17线程A运行结束,增加“20”,当前用户账户余额为:3Process finished with exit code 0
注意:
在使用synchronized关键字时候,应该尽可能避免在synchronized方法或synchronized块中使用sleep或者yield方法,因为synchronized程序块占有着对象锁,你休息那么其他的线程只能一边等着你醒来执行完了才能执行。不但严重影响效率,也不合逻辑。
同样,在同步程序块内调用yeild方法让出CPU资源也没有意义,因为你占用着锁,其他互斥线程还是无法访问同步程序块。当然与同步程序块无关的线程可以获得更多的执行时间。
Java线程:并发协作-生产者消费者模型
对于多线程程序来说,不管任何编程语言,生产者和消费者模型都是最经典的。就像学习每一门编程语言一样,Hello World!都是最经典的例子。
实际上,准确说应该是“生产者-消费者-仓储”模型,离开了仓储,生产者消费者模型就显得没有说服力了。
对于此模型,应该明确一下几点:
1、生产者仅仅在仓储未满时候生产,仓满则停止生产。
2、消费者仅仅在仓储有产品时候才能消费,仓空则等待。
3、当消费者发现仓储没产品可消费时候会通知生产者生产。
4、生产者在生产出可消费产品时候,应该通知等待的消费者去消费。
此模型将要结合java.lang.Object的wait与notify、notifyAll方法来实现以上的需求。这是非常重要的。
/** * Java线程:并发协作-生产者消费者模型* * @author leizhimin 2009-11-4 14:54:36 */ publicclass Test { publicstaticvoid main(String[] args) { Godown godown = new Godown(30); Consumer c1 = new Consumer(50, godown); Consumer c2 = new Consumer(20, godown); Consumer c3 = new Consumer(30, godown); Producer p1 = new Producer(10, godown); Producer p2 = new Producer(10, godown); Producer p3 = new Producer(10, godown); Producer p4 = new Producer(10, godown); Producer p5 = new Producer(10, godown); Producer p6 = new Producer(10, godown); Producer p7 = new Producer(80, godown); c1.start(); c2.start(); c3.start(); p1.start(); p2.start(); p3.start(); p4.start(); p5.start(); p6.start(); p7.start(); } } /** * 仓库 */ class Godown { publicstaticfinalint max_size = 100;//最大库存量 publicint curnum; //当前库存量 Godown() { } Godown(int curnum) { this.curnum = curnum; } /** * 生产指定数量的产品 * * @param neednum */ publicsynchronizedvoid produce(int neednum) { //测试是否需要生产 while (neednum + curnum > max_size) { System.out.println("要生产的产品数量" + neednum +"超过剩余库存量" + (max_size - curnum) +",暂时不能执行生产任务!"); try { //当前的生产线程等待 wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //满足生产条件,则进行生产,这里简单的更改当前库存量 curnum += neednum; System.out.println("已经生产了" + neednum +"个产品,现仓储量为" + curnum); //唤醒在此对象监视器上等待的所有线程 notifyAll(); } /** * 消费指定数量的产品 * * @param neednum */ publicsynchronizedvoid consume(int neednum) { //测试是否可消费 while (curnum < neednum) { try { //当前的生产线程等待 wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //满足消费条件,则进行消费,这里简单的更改当前库存量 curnum -= neednum; System.out.println("已经消费了" + neednum +"个产品,现仓储量为" + curnum); //唤醒在此对象监视器上等待的所有线程 notifyAll(); } } /** * 生产者 */ class Producer extends Thread { privateint neednum; //生产产品的数量 private Godown godown; //仓库 Producer(int neednum, Godown godown) { this.neednum = neednum; this.godown = godown; } publicvoid run() { //生产指定数量的产品 godown.produce(neednum); } } /** * 消费者 */ class Consumer extends Thread { privateint neednum; //生产产品的数量 private Godown godown; //仓库 Consumer(int neednum, Godown godown) { this.neednum = neednum; this.godown = godown; } publicvoid run() { //消费指定数量的产品 godown.consume(neednum); } }
已经生产了10个产品,现仓储量为40已经生产了10个产品,现仓储量为50已经消费了50个产品,现仓储量为0已经生产了80个产品,现仓储量为80已经消费了30个产品,现仓储量为50已经生产了10个产品,现仓储量为60已经消费了20个产品,现仓储量为40已经生产了10个产品,现仓储量为50已经生产了10个产品,现仓储量为60已经生产了10个产品,现仓储量为70Process finished with exit code 0
说明:
对于本例,要说明的是当发现不能满足生产或者消费条件的时候,调用对象的wait方法,wait方法的作用是释放当前线程的所获得的锁,并调用对象的notifyAll()方法,通知(唤醒)该对象上其他等待线程,使得其继续执行。这样,整个生产者、消费者线程得以正确的协作执行。
notifyAll() 方法,起到的是一个通知作用,不释放锁,也不获取锁。只是告诉该对象上等待的线程“可以竞争执行了,都醒来去执行吧”。
本例仅仅是生产者消费者模型中最简单的一种表示,本例中,如果消费者消费的仓储量达不到满足,而又没有生产者,则程序会一直处于等待状态,这当然是不对的。实际上可以将此例进行修改,修改为,根据消费驱动生产,同时生产兼顾仓库,如果仓不满就生产,并对每次最大消费量做个限制,这样就不存在此问题了,当然这样的例子更复杂,更难以说明这样一个简单模型。
我喜欢简单的例子
- Java线程:线程的同步-同步方法
- Java线程:线程的同步-同步方法
- Java线程:线程的同步-同步方法
- Java线程:线程的同步-同步方法
- Java线程:线程的同步-同步方法
- Java线程:线程的同步-同步方法
- Java线程:线程的同步-同步方法
- Java线程:线程的同步-同步方法
- Java线程:线程的同步-同步方法
- Java线程:线程的同步-同步方法
- Java线程:线程的同步-同步方法
- java线程:线程的同步-同步方法
- Java线程:线程的同步-同步方法
- Java线程:线程的同步---同步方法
- Java线程:线程的同步-同步方法
- Java线程:线程的同步-同步方法
- Java线程:线程的同步-同步方法
- Java线程:线程的同步-同步方法
- 关于Umeng分享 报错原因
- linux shell tomcat 一键重启
- 【第七章】 对JDBC的支持 之 7.5 集成Spring JDBC及最佳实践 ——跟我学spring3
- 13. 远程仓库
- myeclipse开发web时SQL CON报错常见问题
- Java线程:线程的同步-同步方法
- 多线程之-NSThread
- GRE填空解法——因果关系
- Android ListView使用BaseAdapter与ListView的优化
- 安装apache2.4.x指南
- 【第八章】 对ORM的支持 之 8.1 概述 ——跟我学spring3
- MQTT IM开发
- cocos2d-X 棋牌手机游戏《萌妹斗地主》网络版完整源码下载
- OVS : 构建网桥和网桥相连的网络