黑马程序员 Java学习总结之同步、线程间通信
来源:互联网 发布:淘宝男模特红人 编辑:程序博客网 时间:2024/05/01 08:10
------- android培训、java培训、期待与您交流! ----------
多线程里数据的安全问题地处理是很可以显示一个Java程序员的功力的。但其实也没有什么,Java是很好学的东西,因为早有大师把问题的解决办法封装成一套机制还有几个API了。所以我们解决问题的难度就小很多了。
闲话少说。多线程安全问题的原因在于几个线程操作共享数据,一个线程对这个数据操作的语句还没有执行完,另一个线程获取CPU执行权中断上个线程对这个数据的完整操作。如果在操作共享数据数据时,只允许让一个线程都执行完后,才可以让其它线程操作共享数据,问题就可以解决了。而Java就是这么解决多线程操作共享数据安全问题的。
这个解决办法的体现方式就是同步(Synchronization),包括同步方法和同步代码块以及静态同步方法。
同步方法示例:
<pre name="code" class="java">public synchronized void foo1() { System.out.println("synchronized methoed");}同步代码块示例:
public void foo2() { synchronized (myObj) { System.out.println("synchronized methoed"); }}静态同步方法示例:
public static synchronized void foo1() { System.out.println("synchronized methoed");}
在使用同步时请记住下面的基本规则:
第一条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的该“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
第二条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程仍然可以访问“该对象”的非同步代码块。
第三条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的其他的“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
要想彻底理解同步就要清楚一个概念——同步锁。在java中,每一个对象有且仅有一个同步锁。对象可以作为同步锁,同样类也可以作为同步锁。上面的1、2示例就是用一个对象作为同步锁。其中示例1作为同步锁的对象就是this,也就是调用这个函数的那个对象。示例2是一个其它什么对象。上面说过每一个Java对象有且仅有一个同步锁,所以不仅限this,理论上每个对象都可以作为同步锁,只要希望同步的线程使用的是同一个同步锁也就是同一个对象就行。示例3是示例1的特殊情况,它的锁就是这个函数所在的类,当然类始终只有一份了。
下面给出一个多线程操作共享数据的经典例子——生产者和消费者。这个经典的问题还涉及到线程的等待唤醒机制,所以还得再介绍几个概念:
notify() -- 唤醒在此对象监视器上等待的单个线程。
notifyAll() -- 唤醒在此对象监视器上等待的所有线程。
wait() -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout) -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout, int nanos) -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)。
生产者消费者问题源代码:
// Demo1.java// 仓库class Depot { private int capacity; // 仓库的容量 private int size; // 仓库的实际数量 public Depot(int capacity) { this.capacity = capacity; this.size = 0; } public synchronized void produce(int val) { try { // left 表示“想要生产的数量”(有可能生产量太多,需多此生产) int left = val; while (left > 0) { // 库存已满时,等待“消费者”消费产品。 while (size >= capacity) wait(); // 获取“实际生产的数量”(即库存中新增的数量) // 如果“库存”+“想要生产的数量”>“总的容量”,则“实际增量”=“总的容量”-“当前容量”。(此时填满仓库) // 否则“实际增量”=“想要生产的数量” int inc = (size+left)>capacity ? (capacity-size) : left; size += inc; left -= inc; System.out.printf("%s produce(%3d) --> left=%3d, inc=%3d, size=%3d\n", Thread.currentThread().getName(), val, left, inc, size); // 通知“消费者”可以消费了。 notifyAll(); } } catch (InterruptedException e) { } } public synchronized void consume(int val) { try { // left 表示“客户要消费数量”(有可能消费量太大,库存不够,需多此消费) int left = val; while (left > 0) { // 库存为0时,等待“生产者”生产产品。 while (size <= 0) wait(); // 获取“实际消费的数量”(即库存中实际减少的数量) // 如果“库存”<“客户要消费的数量”,则“实际消费量”=“库存”; // 否则,“实际消费量”=“客户要消费的数量”。 int dec = (size<left) ? size : left; size -= dec; left -= dec; System.out.printf("%s consume(%3d) <-- left=%3d, dec=%3d, size=%3d\n", Thread.currentThread().getName(), val, left, dec, size); notifyAll(); } } catch (InterruptedException e) { } } public String toString() { return "capacity:"+capacity+", actual size:"+size; }} // 生产者class Producer { private Depot depot; public Producer(Depot depot) { this.depot = depot; } // 消费产品:新建一个线程向仓库中生产产品。 public void produce(final int val) { new Thread() { public void run() { depot.produce(val); } }.start(); }}// 消费者class Customer { private Depot depot; public Customer(Depot depot) { this.depot = depot; } // 消费产品:新建一个线程从仓库中消费产品。 public void consume(final int val) { new Thread() { public void run() { depot.consume(val); } }.start(); }}public class Demo1 { public static void main(String[] args) { Depot mDepot = new Depot(100); Producer mPro = new Producer(mDepot); Customer mCus = new Customer(mDepot); mPro.produce(60); mPro.produce(120); mCus.consume(90); mCus.consume(150); mPro.produce(110); }}
最后,一个问得比较多的问题是为什么notify(), wait()等函数定义在Object中,而不是Thread中:
Object中的wait(), notify()等函数,和synchronized一样,会对“对象的同步锁”进行操作。
wait()会使“当前线程”等待,因为线程进入等待状态,所以线程应该释放它锁持有的“同步锁”,否则其它线程获取不到该“同步锁”而无法运行!
线程调用wait()之后,会释放它锁持有的“同步锁”;而且,根据前面的介绍,我们知道:等待线程可以被notify()或notifyAll()唤醒。现在,请思考一个问题:notify()是依据什么唤醒等待线程的?或者说,wait()等待线程和notify()之间是通过什么关联起来的?答案是:依据“对象的同步锁”。
负责唤醒等待线程的那个线程(我们称为“唤醒线程”),它只有在获取“该对象的同步锁”(这里的同步锁必须和等待线程的同步锁是同一个),并且调用notify()或notifyAll()方法之后,才能唤醒等待线程。虽然,等待线程被唤醒;但是,它不能立刻执行,因为唤醒线程还持有“该对象的同步锁”。必须等到唤醒线程释放了“对象的同步锁”之后,等待线程才能获取到“对象的同步锁”进而继续运行。
总之,notify(), wait()依赖于“同步锁”,而“同步锁”是对象锁持有,并且每个对象有且仅有一个!这就是为什么notify(), wait()等函数定义在Object类,而不是Thread类中的原因。
- 黑马程序员 Java学习总结之同步、线程间通信
- 黑马程序员之java线程通信学习
- 黑马程序员:Java基础总结----线程间通信
- 黑马程序员 知识点总结-Java线程间通信
- 黑马程序员-多线程(创建线程、方法、同步、通信)总结
- 黑马程序员---Java中传统线程同步通信技术
- 黑马程序员:JAVA线程间的通信
- 黑马程序员-----java线程学习与总结
- 黑马程序员——java基础拾遗之多线程(二) 线程同步、线程通信
- 黑马程序员Java基础第八章------线程以及线程同步,线程通信
- 黑马程序员-java学习之线程
- java多线程之线程间同步通信
- 黑马程序员——学习日记12 java线程同步
- 黑马程序员——JAVA基础——线程---概述,创建、生命周期,控制,同步,线程通信
- 黑马程序员 ---- 线程间通信
- 黑马程序员--java学习之io总结
- 黑马程序员--java学习之反射总结
- 黑马程序员 Java学习总结之String
- Android 自定义控件属性,自定义Dialog定位
- Java中动态代理
- jquery select 获取下拉列表对象 radio 获取单选对象
- 在iOS的XCode工程配置中为什么要用-all_load&-ObjC
- mysql 的load data infile
- 黑马程序员 Java学习总结之同步、线程间通信
- App Store审核指南中文版(2014.10.11更新)
- 关于Photoshop的网格对不齐的解决办法
- BZOJ 2141 排队 分块+树状数组
- MySQL执行计划解读
- Active Server Pages 错误 ‘ASP 0201′
- arm-linux-gdb和gdbserver调试交叉编译的程序
- android 4.0特性
- form_tag (ActionView::Helpers::FormTagHelper)