这一部分是多线程基础中非常基础的部分。
- 等待 / 通知机制
- join的作用
- ThreadLocal的使用
- InheritableThreadLocal的使用
1.等待 / 通知机制
等待通知 / 通知的机制模型如下所示:
设定线程A为等待线程,线程B为通知线程,交互过程如下:
第一步.线程A请求异步服务,异步服务的结果由线程B运行提供,现在线程A发起服务请求后,进入等待状态
第二步:线程B由请求唤醒,进入工作状态,完成计算
第三步:线程B返回计算结果,并通知线程A
第四步:线程A开始工作,获得工作结果,进行后续处理
等待 / 通知机制的实现
wait()方法的作用是使当前执行代码的线程进入等待状态,这个方法属于Object类的方法,它是用来将当前线程置入预执行队列中,并且在wait()所在的代码行处停止执行,直到接到通知或被中断为止。在调用wait()之前,线程必须获得该对象的锁,即只能在同步方法和同步块中调用wait()。在执行wait()之后,当前线程会释放锁。在wait()返回前,线程与其他线程竞争重新获得锁。如果调用wait()时没有持有适当的锁,则抛出运行时异常。
notify()也是在同步方法或同步块中调用,调用之前线程必须获得该对象的对象级别锁。如果调用notify()时,没有持有适当的锁,则抛出运行时异常。该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则随机对一个处于wait状态的线程发送notify,并使它等待获取该对象的对象锁。只有notify()所处的同步块执行完成之后释放对象锁,等待状态的线程才会假如竞争对象的对象锁的队列中。
总之:wait使线程停止运行,notify事停止的线程继续运行。在使用这种机制的时候,要特别注意synchronized(x){}当中x,它是对象级别的锁,这个锁是进行等待和被通知的对象,一般来说,这是一个非常关键的地方,这个锁是被共享给等待方和通知方。示例代码如下所示:
业务bean:
public class BusinessBean {
private float price=10.00f;
private Object obj=new Object();
volatile private int flag;
public void cook() {
System.out.println("I am cooking");
flag=flag+1;
System.out.println("flag is "+flag);
synchronized(obj) {
System.out.println("price is "+price + " thread is " +Thread.currentThread().getName());
price+=1.00f;
System.out.println("price is "+price + " thread is " +Thread.currentThread().getName());
obj.notify();
}
System.out.println("thread name is "+Thread.currentThread().getName());
}
public void book(){
System.out.println("I am booking");
synchronized(obj) {
System.out.println("I will wait");
try {
obj.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("waiting over");
}
System.out.println("I complete booking");
}
}
等待线程的定义:
public class RunThread implements Runnable{
private BusinessBean bb;
public RunThread(BusinessBean bb) {
this.bb=bb;
}
@Override
public void run(){
Thread.currentThread().setName("I am Farmer");
bb.book();
System.out.println("It's done");
}
}
通知线程的定义:
public class MyThread extends Thread{
private BusinessBean bb;
public MyThread(BusinessBean bb) {
this.bb=bb;
}
@Override
public void run(){
Thread.currentThread().setName("I am Fisher");
bb.cook();
System.out.println("It's done");
}
}
测试bean:
public class ThreadSample {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());
BusinessBean bb=new BusinessBean();
Thread rnth=new Thread(new RunThread(bb));
rnth.start();
MyThread mt=new MyThread(bb);
mt.start();
}
}
当线程处于wait状态时,线程对象调用interrupt()方法时会抛出InterruptedException异常。
notifyAll()方法会唤醒全部的等待线程。一般在等待 / 通知机制中,多使用notifyAll()进行通知。
wait(long)方法是等待某一时间内时候有线程对锁进行唤醒,超过这个时间则自动唤醒。在这种等待 / 通知机制中,在实际运行的时候,要将发生等待的线程运行时间设定为早于通知线程的运行时间,否则容易使发生等待的线程无限等待。
等待 / 通知模型的最经典的例子是 生产者/消费者的案例。下面将准备多对多的生产者 / 消费者 示例代码,这个示例的运行会揭示消费者和生产者处理速度不一致的思路:
业务的共享资源bean:
public class BusinessBean {
private Object lock=new Object();
private int currentResource=50;
private static final int MAXLIMIT=100;
private static final int MINLIMIT=1;
public void produce() {
synchronized(lock) {
if(currentResource>=MAXLIMIT) {
System.out.println("In producing "+Thread.currentThread().getName()+", current resource is "+currentResource +" , touch MAX , waiting");
try {
lock.wait();
} catch (InterruptedException e) {
System.out.println("producing exception now");
}
}else {
System.out.println("In producing "+Thread.currentThread().getName()+", current resource is "+currentResource +" , untouch MAX , producing");
currentResource=currentResource+1;
lock.notifyAll();
}
}
}
public void consume(){
synchronized(lock) {
if(currentResource<=MINLIMIT) {
System.out.println("In consuming "+Thread.currentThread().getName()+", current resource is "+currentResource +" , touch MIN , waiting");
try {
lock.wait();
} catch (InterruptedException e) {
System.out.println("consuming exception now");
}
}else {
System.out.println("In consuming "+Thread.currentThread().getName()+", current resource is "+currentResource +" ,untouch MIN , consuming");
currentResource=currentResource-1;
lock.notifyAll();
}
}
}
}
消费者线程:
public class RunThread implements Runnable{
private BusinessBean bb;
public RunThread(BusinessBean bb) {
this.bb=bb;
}
@Override
public void run(){
while(true) {
bb.consume();
}
}
}
生产者线程:
public class MyThread implements Runnable{
private BusinessBean bb;
public MyThread(BusinessBean bb) {
this.bb=bb;
}
@Override
public void run(){
while(true) {
bb.produce();
}
}
}
测试bean:
public class ThreadSample {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());
BusinessBean bb=new BusinessBean();
ArrayList<Thread> list=new ArrayList<Thread>();
for(int i=0;i<2;i++) {
Thread producer=new Thread(new RunThread(bb));
Thread consumer=new Thread(new MyThread(bb));
list.add(producer);
list.add(consumer);
}
for(int i=0;i<list.size();i++) {
list.get(i).start();
}
}
}
整个代码中最重要的是同步块中的代码结构,结构应如下所示:
synchronized(x){
if(status){
//check status, if true, then set x.wait();
}else{
//if false, then process business
// set x.notifyAll();
}
}
2.join的作用
join的作用是所属的线程对象x正常执行run()方法中的任务,使当前线程main进行无限期的阻塞,等待线程x销毁后,继续执行线程main后面的代码。在join的过程中,如果当前线程对象被中断,则当前线程抛出中断异常。join(long)方法中参数是设定的等待时间。join(long)是通过使用wait(long)实现的,具有释放锁的特点。sleep(long)不会释放锁。在实际使用过程中要测试join的效果,确保不会因为设定时间出现问题。
3.ThreadLocal的使用
ThreadLocal主要解决每个线程绑定自己的值,它可以存储每个线程的私有数据。它存储数值的是以键值对的方式存在,key=ThreadLocal对象,value=我们想保存的值。它有很好的数据隔离性,非常适合线程想要长时间保存的数据或状态。ThreadLocald的对象实例只能在某个线程的运行代码中存值和取值,两个不同的线程作用域内是不会交叉混杂的。下面是一个例子:
public class ThreadSample {
public static ThreadLocal TL=new ThreadLocal();
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());
TL.set("hello");
System.out.println(TL.get());
}
}
4.InheritableThreadLocal的使用
使用它可以让子线程从父线程中取得值,但是当子线程使用值的时候,父线程对其进行修改不会产生同步。子线程和父线程是什么关系呢?假设在线程A中调用了线程B,那么称线程A是线程B的父线程,称线程B是线程A的子线程。下面是一个使用示例:
public class MyThread implements Runnable{
@Override
public void run(){
System.out.println(ThreadSample.TL.get());
}
}
public class ThreadSample {
public static ThreadLocal TL=new InheritableThreadLocal();
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());
TL.set("hello");
ArrayList<Thread> list=new ArrayList<Thread>();
for(int i=0;i<2;i++) {
Thread consumer=new Thread(new MyThread(bb));
list.add(consumer);
}
for(int i=0;i<list.size();i++) {
list.get(i).start();
}
System.out.println(TL.get());
}
}
运行结果:
main
hello
hello
hello