Java学习笔记5
以下内容是从任小龙讲师课堂笔记中整理。
线程通信
线程通信:不同的线程执行不同的任务,如果这些任务有某种关系,线程之间必须能够通信,协调完成工作。
经典的生产者和消费者案例(Producer/Consumer):
分析案例:
1.生产者和消费者应该操作共享的资源(实现方式来做)
2.使用一个或多个线程表示消费者(consumer)
3.使用一个或多个线程表示生产者(producer)

为什么生产者不直接把数据给消费者,而是把数据存储到共享资源,然后消费者再从共享资源中取出数据,再消费?
在这里体现了面向对象的设计理念:低耦合
高(紧)耦合:直接使用生产者把包子给消费者,那么生产者中得存在消费者的引用,同理,消费者要消费生产者生产的包子,消费者中也得存在生产者对象的引用。
public class Producer{
private Consumer con;
}
public class Consumer{
private Producer pro;
}
低(松)耦合:使用一个中间对象,屏蔽了生产者和消费者的直接数据交互。
//生产者
public class Producer{
private ShareResource resouce;
}
//消费者
public class Consumer{
private ShareResource resouce;
}
public class Producer implements Runnable { private ShareResource resource = null; public Producer(ShareResource resource) { this.resource = resource; } @Override public void run() { for(int i = 0; i < 50; i++){ if(i%2 == 0){ resource.push("春哥","男"); }else{ resource.push("凤姐","女"); } } } }
public class ShareResource { private String name; private String gender; public void push(String name, String gender){ this.name = name; this.gender = gender; } public void popup(){ System.out.println(this.name + "-" + this.gender); } }
public class Consumer implements Runnable { private ShareResource resource = null; public Consumer(ShareResource resource) { this.resource = resource; } @Override public void run() { for(int i = 0; i < 50; i++ ){ resource.popup(); } } }
public class test{ public static void main(String[] args){ ShareResource resource = new ShareResource(); new Thread(new Producer(resource)).start(); new Thread(new Producer(resource)).start(); new Thread(new Consumer(resource)).start(); new Thread(new Consumer(resource)).start(); } }
如果将ShareResource作如下修改
public class ShareResource { private String name; private String gender; public void push(String name, String gender){ this.name = name; try{ Thread.sleep(10L); }catch(Exception e){ System.out.println(e.getStackTrace()); } this.gender = gender; } public void popup(){ try{ Thread.sleep(10L); }catch(Exception e){ System.out.println(e.getStackTrace()); } System.out.println(this.name + "-" + this.gender); } }
生产者和消费者案例存在的问题:
使用sleep(10)使问题更明显
问题1:性别紊乱现象
生产者先生产出春哥-男,此时消费者还没来得及消费,生产者继续生产出凤姐,性别还没来得及赋值,此时消费者开始消费了。
解决方案:只要保证生产姓名和性别过程同步,中间不能被消费者线程进来取走数据消费。
可以使用同步代码块/同步方法/Lock机制保持同步性
问题2:应该出现生产一个数据,消费一个数据,即春哥-男->凤姐-女交替出现
重复出现凤姐-男->凤姐女的原因:
当出现问题1之后,生产者继续赋值,将性别赋值女后,消费者继续消费凤姐-女
解决方案:得使用等待和唤醒机制
同步锁池:
同步锁必须选择多线程共同的资源对象。
当前生产者在生产数据的时候先拥有同步锁,其他线程就在锁池中等待获取锁。
当线程执行完同步代码块的时候,就会释放同步锁,其他线程开始抢锁的使用权。
public class ShareResource { private String name; private String gender; synchronized public void push(String name, String gender){ this.name = name; try{ Thread.sleep(10L); }catch(Exception e){ System.out.println(e.getStackTrace()); } this.gender = gender; } synchronized public void popup(){ try{ Thread.sleep(10L); }catch(Exception e){ System.out.println(e.getStackTrace()); } System.out.println(this.name + "-" + this.gender); } }
wait和notify
wait和notify方法介绍:
java.lang.Object类提供了两类用于操作线程通信的方法。
wait():执行该方法的线程对象释放同步锁,JVM把该线程存放到等待池中,等待其他线程唤醒该线程。
notify():执行该方法的线程唤醒在等待池中等待的任意一个线程,把线程转到锁池中等待。
notifyAll():执行该方法的线程唤醒在等待池中等待的所有线程,把线程转到锁池中等待。
注意:上述方法只能在被同步监听锁对象来调用,否则报错IllegalMonitorStateException.
假设A线程和B线程共同操作一个x对象,A,B线程可以通过对象的wait和notify方法来进行通信,流程如下:
1.当A线程执行x对象的同步方法时,A线程持有x对象的锁,B线程在x对象的锁池中等待。
2.A线程在同步方法中执行x.wait()方法时,A线程释放x对象的锁,进入x对象的锁池中等待。
3.在x对象的锁池中等待的B线程获取x对象的锁,执行x的另一个同步方法。
4.B线程在同步方法中执行x,notify()方法时,JVM把A线程从对象x的等待池中移动到x对象的锁池中,等待获取锁。
5.B线程执行完同步方法,释放锁。A线程获得锁,继续执行同步方法。
//解决重复消费的问题 public class ShareResource { private String name; private String gender; private boolean isEmpty = true; synchronized public void push(String name, String gender){ try { while(!isEmpty){ this.wait(); } this.name = name; Thread.sleep(10L); this.gender = gender; isEmpty = false; this.notify(); }catch(Exception e){ System.out.println(e.getStackTrace()); } } synchronized public void popup(){ try{ while(isEmpty){ this.wait(); } Thread.sleep(10L); System.out.println(this.name + "-" + this.gender); isEmpty = true; tHis.notify(); }catch(Exception e){ System.out.println(e.getStackTrace()); } } }
使用Lock和Condition接口:
wait和notify方法只能被同步监听锁对象来调用,否则报错IllegalMonitorStateException.
Lock机制根本没有同步锁,也就没有自动获取锁和自动释放锁的概念。
因为没有同步锁,所以Lock就不能调用wait和notify方法。
解决方案:Java5中提供Lock机制的同时,提供了处理Lock机制的通信控制Condition接口。
从java5开始:
1.可以使用Lock机制取代synchronized代码块和synchronized方法
2.使用Condition接口对象的await,signal,signalAll方法代替wait,notify,notifyAll方法。
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ShareResource { private String name; private String gender; private boolean isEmpty = true; private final Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void push(String name, String gender){ lock.lock(); try { while(!isEmpty){ condition.await(); } this.name = name; Thread.sleep(10L); this.gender = gender; isEmpty = false; condition.signal(); }catch(Exception e){ System.out.println(e.getStackTrace()); }finally { lock.unlock(); } } public void popup(){ lock.lock(); try{ while(isEmpty){ condition.await(); } Thread.sleep(10L); System.out.println(this.name + "-" + this.gender); isEmpty = true; condition.signal(); }catch(Exception e){ System.out.println(e.getStackTrace()); }finally { lock.unlock(); } } }
多线程通信的时候很容易造成死锁,死锁无法解决,只能避免:
当A线程等待由B线程持有的锁,而B线程正在等待A线程持有的锁时,发生死锁现象。JVM不检测也不试图避免这种情况,所以程序员必须保证不导致死锁。
避免死锁法则:当多个线程都要访问共享的资源A,B,C时,保证每一个线程都按照相同的顺序去访问它们,比如都先访问A,接着访问B,最后是C。
Thread类中过时的方法:
suspend():使正在运行的线程放弃CPU,暂停运行
resume():使暂停的线程恢复运行
注意:因为容易导致死锁,所以已经被废弃了。
死锁情况:
A线程获得对象所,正在执行一个同步方法,如果B线程调用A线程的suspend方法,此时A线程暂停运行,此时A线程放弃CPU,但是不会放弃占用的资源。
线程在生命周期中的状态

1.新建状态(new):使用new关键字创建一个线程对象,仅仅在堆中分配内存空间,在调用start之前
新建状态下,线程压根就没有启动,仅仅只是存在一个线程对象而已。
Thread t = new Thread();
2.可运行状态(runnable):分成两种状态,ready和running。分别表示就绪状态和运行状态。当新建状态下的线程对象调用start方法,此时从新建状态进入可运行状态。
就绪状态:线程对象调用start方法之后,等待JVM的调度。
运行状态:线程对象获得JVM调度,如果存在多个CPU,那么允许多个线程并行运行。
线程对象的start方法只能调用一次。
3.阻塞状态(blocked):正在运行的线程,因为某些原因放弃CPU,暂时停止运行,就会进入阻塞状态。
此时JVM不会给线程分配CPU,直到线程重新进入就绪状态才有机会转到运行状态。
阻塞状态只能先进入就绪状态,不能直接进入运行状态。
1).当线程A处于运行状态时,试图获取同步锁时,却被B线程获取,此时JVM把当前A线程存到对象的锁池中,A线程进入阻塞状态。.
2).当线程处于运行状态时,发出了IO请求,此时进入阻塞状态。
4.等待状态(waiting)
当线程处于运行状态,调用了wait()方法,此时JVM把当前线程存在对象等待池中
当线程执行了sleep()方法
5.计时等待状态(timed waiting)
当线程处于运行状态时,调用了wait(long time)方法,此时JVM把当前线程存在对象等待池中
当前线程执行了sleep(long time)方法。
6.终止状态(terminated)
正常执行完run方法而退出
因为出现异常而退出

浙公网安备 33010602011771号