线程间的通信使单个独立的线程得到有效组织,提升了CPU利用率。
1、等待/通知机制
wait方法是使当前执行代码的线程进入等待状态,是Object类的方法,作用是将当前线程置入预执行队列并且在wait方法所在的代码行处停止执行,直至接到通知或被中断为止。在调用wait方法时,线程必须获得该对象的对象级别锁。在执行wait方法之后,当前线程释放锁。在线程重新获得锁之后,会在wait方法后继续执行,直至退出同步区域。wait(long)是在long的时间段内是否有线程进行唤醒,如果没有,超过这个时间之后自动唤醒。
notify方法需要在同步区域中调用,在调用时必须获得该对象的对象级别锁,所用是通知哪些等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑选出其中一个呈现wait状态的线程,对其发出notify,使它获得该对象的对象锁。在执行notify之后,线程不会马上释放锁,wait状态的线程也不能马上获得对象锁,需要等到notify方法的线程推出同步区域后,当前线程才会释放锁,wait状态的线程才可以获得对象锁。 当线程呈现wait状态,调用线程对象的interrupt方法会抛出InterruptedException异常。
notify方法一次只随机通知一个线程进行唤醒。唤醒所有线程可以使用notifyAll方法。
synchronized关键字提供了一种排他式的数据同步机制,这种同步机制有两个缺陷:1、无法控制阻塞时长;2、阻塞不可被中断。
注意: 等待/通知机制是需要多个线程相互配合的。如果通知过早,会打乱程序的正常运行逻辑。另外,当wait的条件发生变化,也容易造成程序的逻辑混乱。
生产者/消费者模式是基于wait/notify原理的线程间通信模型。
多生产者对多消费者模型示例代码:
public class Resource {
private static int resource=0;
private static int top=10;
private static int low=0;
public static final Object rLock=new Object();
public static int getResource() {
return resource;
}
public static void setResource(int resource) {
Resource.resource = resource;
}
public static int getLow() {
return low;
}
public static void setLow(int low) {
Resource.low = low;
}
public static int getTop() {
return top;
}
public static void setTop(int top) {
Resource.top = top;
}
}
public class Producer implements Runnable{
@Override
public void run() {
synchronized(Resource.rLock) {
if(Resource.getResource()>=Resource.getTop()) {
System.out.println(Thread.currentThread().getName()+" producer thread is waiting");
try {
Resource.rLock.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(Resource.getResource()<Resource.getTop()) {
Resource.setResource(Resource.getResource()+1);
System.out.println(Thread.currentThread().getName()+" producer thread is working");
Resource.rLock.notifyAll();
}
}
}
}
public class Consumer implements Runnable{
@Override
public void run() {
synchronized(Resource.rLock) {
if(Resource.getResource()<=Resource.getLow()) {
System.out.println(Thread.currentThread().getName()+" comsumer thread is no resource to buy, so notify all thread");
try {
Resource.rLock.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(Resource.getResource()>Resource.getLow()) {
Resource.setResource(Resource.getResource()-1);
System.out.println(Thread.currentThread().getName()+" comsumer thread is buying");
Resource.rLock.notifyAll();
}
}
}
}
import java.util.ArrayList;
public class CPTest {
public static void main(String[] args) {
Consumer c=new Consumer();
Producer p=new Producer();
ArrayList<Thread> list =new ArrayList<Thread>();
for(int i=0;i<30;i++) {
list.add(new Thread(p));
list.add(new Thread(c));
}
for(int i=0;i<list.size();i++) {
list.get(i).start();
}
}
}
代码编写经验:
1、对象锁最好是常量对象
2、共享资源尽量是静态的
3、synchronized同步区域内代码要写处理wait部分,然后再处理正常部分。这样重新获得锁后会做业务上的正常处理
4、读线程和写线程混杂在一起,notifyAll的效率不高
5、如果唤醒使用notify方法,则在同步区域中,临界条件判断将if替换为while
2、join的使用
join方法的作用是使所属的线程对象正常执行run方法中的任务,而使当前线程进行无限期阻塞,等join方法所属线程销毁后,再执行当前线程后面的代码。join方法具有使线程排队运行的作用,类似于同步的运行效果。在join过程中,如果当前线程对象被中断,则当前线程出现异常。join(long)的功能是在内部使用wait(long)方法实现的,当前线程释放锁,其他线程可以获得锁执行同步任务,具有释放锁的特点。sleep(long)方法是不释放锁的。由于join方法的机制是当前线程先释放锁,join所属线程再竞争锁,当join所属线程的同步任务与其他线程存在竞争锁的时候,有可能会发生意外情况。
示例代码:
public class JoinThread extends Thread {
public void run() {
System.out.println(Thread.currentThread().getName()+" is running");
}
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+" is beginning");
Thread jh=new JoinThread();
jh.start();
try {
jh.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" is ending");
}
}
3、ThreadLocal和InheritableThreadLocal的使用
变量值的共享可以使用public static变量形式,所有线程都使用同一个public static变量。ThreadLocal主要解决每个线程使用的私有数据。
InheritableThreadLocal可以让子线程中取得父线程继承下来的值。InheritableThreadLocal类保存的值不具备可见性。
由此可知,ThreadLocal是在每一线程初始化时设定的。 InheritableThreadLocal是在设计父子线程时设定的。
示例代码:
在多生产者/多消费者模型的Resource类中加入以下属性
public static ThreadLocal<String> tl=new ThreadLocal<String>();
在生产者和消费者的同步区域内设置tl的值,然后使用。当线程全部运行结束时移除。