多线程专题 - 线程间的通信

这一部分是多线程基础中非常基础的部分。

 

  1. 等待 / 通知机制
  2. join的作用
  3. ThreadLocal的使用
  4. 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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值