一、多线程通信
1、多线程间通信的应用:要实现多线程间的协调运作,就需要依赖它们之间的通信。
2、线程通信需要用到的方法:
wait():线程对外发布所持有的锁,此方法导致当前线程释放所持有的锁并重新等待获取该锁。
notify():唤醒在此对象监视器上等待的单个线程,如果所有线程都在此对象上等待,则会选择唤醒其中一个线程,选择是任意性的,并在对实现做出决定时发生,通常唤醒死第一个等待的线程。
notifyAll():唤醒在此对象监视器上等待的所有线程,直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程,被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争。
这些方法只能使用在同步中,因为这些方法在操作同步中线程时,都必须要标识它们所操作线程持有的锁,而只有同步才具锁,并且只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。
3、下面是一个多线程通信的简单的实例,要实现对一个车装了煤并指定发往地点,以装一辆走一辆的方式实现一对一交替执行
class Target
{
private String state;
private String address;
private boolean flag = true;
public synchronized void set(String state,String address)
{
if(!flag)
try{this.wait();}catch(Exception e){} //等待并释所持有的放锁
this.state = state;
this.address = address;
flag = false;
this.notify(); //唤醒等待线程
}
public synchronized void get()
{
if(flag)
try{this.wait();}catch(Exception e){} //等待并释所持有的放锁
System.out.println(state+"--------->"+address);
flag = true;
this.notify(); //唤醒等待线程
}
}
class FillThread implements Runnable //装煤操作
{
private Target target;
Input(Target target)
{
this.target = target;
}
public void run()
{
int x = 0;
while(true)
{
if(x==0)
target.set("发往北京的列车装满","北京");
else
target.set("发往上海的列车装满","上海");
x = (x+1)%2;
}
}
}
class StartOffThread implements Runnable //发车操作
{
private Target target;
Output(Target target)
{
this.target = target;
}
public void run()
{
while(true)
{
target.get();
}
}
}
public class MulThreadComm
{
public static void main(String[] args)
{
Target target = new Target();
new Thread(new FillThread(target)).start(); //启动装煤线程
new Thread(new StartOffThread(target)).start(); //发车发车线程
}
}
当取消该程序的同步机制,同时取消其通信机制(因为通信机制只能用在同步中)时,运行结果为:
当仅取消该程序的通信机制时,运行结果为:
当该程序使用了通信机制时,运行结果为:
可以看到,通过多线程之间的通信机制,可以消除线程执行的随机性所带来的无序与不合常理的的地方。
二、多线程通信的安全分析
下面是一个生产者与消费者的例子,要求在同时有多个生产者与消费者的情况下能确保生产一个消费一个,看下面代码
class Resource
{
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void set(String name)
{
while(flag)
//① if(flag) //⑤
try{wait();}catch(Exception e){} //⑥
this.name = name+"--"+count++;
System.out.println("生产者.."+this.name);
flag = true;
notifyAll();
//③ notify();
}
public synchronized void out()
{
while(!flag)
//② if(!flag) //⑦
try{wait();}catch(Exception e){} //⑧
System.out.println("消费者........."+this.name);
flag = false;
notifyAll();
//④ notify();
}
}
class Producer implements Runnable
{
private Resource res;
Producer(Resource res)
{
this.res = res;
}
public void run()
{
while(true)
{
res.set("商品");
}
}
}
class Consumer implements Runnable
{
private Resource res;
Consumer(Resource res)
{
this.res = res;
}
public void run()
{
while(true)
{
res.out();
}
}
}
public class ProducerConsumerDemo
{
public static void main(String[] args)
{
Resource r = new Resource();
Producer pro = new Producer(r); //生产者
Consumer con = new Consumer(r); //消费者
Thread t1 = new Thread(pro); //生产者线程
Thread t2 = new Thread(pro); //生产者线程
Thread t3 = new Thread(con); //消费者线程
Thread t4 = new Thread(con); //消费者线程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
执行程序,结果如下:
此处有两个特殊情况需要具体分析一下:
(1)、当把注释①②分别移到对应的while()语句时,执行结果如下:
结果一
结果二
对结果一进行分析,结果二原理相同,假设生产者分别为p1、p2,对应于Resource的set()方法,消费者分别为c1、c2,对应于Resource的out()方法。
**让p1先去执行,执行到⑤一判断,flag为false,接着对name赋值"商品--1",此时count变为了2,并设置flag为true,目前执行权还在p1,经过run方法中的while(true)循环再次执行到⑤ 一判断,flag为true,于是执行⑥放弃执行权等待;
**接着假设p2获取了执行权,同样执行了⑤⑥后放弃执行权等待;
**然后假设 c1获得执行权,执行到⑦时一判断,!flag为false,便输出消费内容并设置flag为false,同时唤醒了p1(通常唤醒第一个等待的线程),但目前执行权还在c1,经过run方法中的while(true)循环再次执行到⑦一判断,!flag为true,于是执行⑧放弃执行权等待;
**接着假设c2获得执行权,同样在执行了⑦⑧后放弃执行权等待;
**现在只有p1处于激活状态,于是它再次获得执行权,此时p1将不再进行⑤处的判断而是从⑥直接向下执行,对name赋值"商品--2",此时count变为了3,并设置flag为true,同时唤醒了p2(通常唤醒第一个等待的线程),但目前执行权还在p1,经过run方法中的while(true)循环再次执行到⑤一判断,flag为true,于是执行⑥放弃执行权等待;
**接着notify()(这里是关键点)唤醒了等待中的p2(唤醒了本方)使其获取了执行权,p2将同样不再进行⑤处的判断而是从⑥直接向下执行,对name赋值"商品--3",覆盖了上次未消费的的"商品--2"。
(2)、当把注释③④分别移到对应的notifyAll()语句时,执行结果如下:
循着上面的思路分析,假设p1先获得执行权,然后放弃执行权等待;接着p2获得执行权,然后判断后等待;接着c1获得执行权,然后唤醒p1放并弃执行权等待,p1激活但未获得执行权;接着c2获得执行权,然后判断等待;最后只有p1处于激活状态,p1唤醒p2并放弃执行权等待,p2会经while(true)循环在⑥处等待,至此,全部线程都处于等待状态,造成程序冻结。
(3)、分析总结:
对于多个生产者和消费者,要定义while判断标记,让被唤醒的线程再一次判断标记,要使用定义notifyAll,因为需要
唤醒对方线程,而使用notify,容易出现只唤醒本方线程的情况,导致程序中的所有线程都等待。
三、停止线程
1、stop方法已经过时,只有等待run方法结束才可以停止线程,但是开启多线程时,运行代码通常是循环结构,只要控制住循环,就可以让run方法结束,也就可以让线程结束。不过有一种特殊情况:在每一个线程内部都有一个布尔型的中断状态标志, 个线程都会时不时的检查这个标志的状态,已确认该线程是否应该被中断,但当线程处于了冻结状态,就不会读取到内部的中断状态标志,那么线程就不会结束,这时要对冻结状态进行清除,就可以使用interrupt()方法,让线程恢复到运行状态中来:
class StopThread implements Runnable
{
private boolean flag =true;
public synchronized void run()
{
while(flag)
{
try{
wait(); //①
}catch(InterruptedException e){
flag = false; //②
}
System.out.println(Thread.currentThread().getName()+"....run");
}
}
public void changeFlag()
{
flag = false;
}
}
class StopThreadDemo
{
public static void main(String[] args)
{
StopThread st = new StopThread();
Thread t = new Thread(st);
t.start();
int num = 0;
while(true)
{
if(num++ == 60)
{
// t1.interrupt();
st.changeFlag();
break;
}
System.out.println(Thread.currentThread().getName()+"......."+num);
}
System.out.println("over");
}
}
分析:当执行上述代码是,如果注释掉t1.interrupt()语句,那么,该程序将不会停止,因为当t执行到①时就会进入冻结状态且没有回复至运行状态的任何途径,如果取消对t1.interrupt()语句的注释,则①处就会抛出InterruptedException异常,从而执行②处的异常处理代码并让程序恢复到运行状态结束循环,进而结束线程。
四、守护线程&jion()&优先级&yield()
1、守护线程即后台线程,可以通过Thread类的实例方法setDaemon(boolean on)将一个线程设置为后台线程。该方法必须在线程 对象调用start()方法之前调用。当所有前台线程执行完毕后后台线程自动结束,当正在运行的线程都是后台线程时,Java 虚拟机退出。
2、jion()方法用于让该方法所在上下文线程等待调用该发方法的线程执行完毕。也就是说,在那个线程中调用该方法,哪个线程阻塞,哪个线程对象调用的该方法,被阻塞线程就等待那个线程对象执行完毕。
3、每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程。可以通过方法setPriority(int newPriority)设置线程的优先级,通过getPriority()方法获取线程优先级,优于优先级是平台相关的,所以为了更好地实现程序的跨平台性,最好使用Thread类的静态优先级字段MAX_PRIORITY、NORM_PRIORITY、MIN_PRIORITY来设置线程优先级。
4、yield()方法的作用是暂停当前正在执行的线程对象,并执行其他线程。但是如果被暂停的线程拥有最高优先级,它会再次获取资源运行。
五、Condition
Condition将Object监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意Lock实现组合使用,为每个对象提供多个等待set(wait-set)。其中,Lock 替代了synchronized方法和语句的使用,Condition替代了 Object监视器方法的使用。 条件(也称为条件队列或条件变量)为线程提供了一个含义,以便在某个状态条件现在可能为true的另一个线程通知它之前,一直挂起该线程(即让其“等待”)。因为访问此共享状态信息发生在不同的线程中,所以它必须受保护,因此要将某种形式的锁与该条件相关联。等待提供一个条件的主要属性是:以原子方式释放相关的锁,并挂起当前线程,就像 Object.wait 做的那样。Condition 实例实质上被绑定到一个锁上。要为特定Lock实例获得Condition实例,使用其newCondition()方法。
1、使用Condition对生产者的例子中Resource的代码稍加改造即可实现同样的功能,看如下代码:
class Resource
{
Lock lock = new ReentrantLock(); //创建锁
Condition condition = lock.newCondition(); //获取条件对象
private String name;
private int count = 1;
private boolean flag = false;
public void set(String name) throws InterruptedException
{
lock.lock(); //获取锁
try{
while(flag) //条件对象的await()代替同步监视器的wait()
condition.await();
this.name = name+"--"+count++;
System.out.println("生产者.."+this.name);
flag = true;
condition.signalAll(); //条件唤醒代替同步监视器的notifyAll();
}finally{
lock.unlock(); //释放锁,一定要放在finally块中
}
}
public void out() throws InterruptedException
{
lock.lock(); //获取锁
try{
while(!flag) //条件对象的await()代替同步监视器的wait()
condition.await();
System.out.println("消费者........."+this.name);
flag = false;
condition.signalAll(); //条件唤醒代替同步监视器的notifyAll();
}finally{
lock.unlock(); //释放锁,一定要放在finally块中
}
}
}
2、使用Condition的一种更灵活的方式对Resource的代码进行改造也可实现同样的功能,看如下代码:
class Resource
{
Lock lock = new ReentrantLock(); //创建锁对象
Condition setCondition = lock.newCondition(); //创建set方法的条件对象
Condition outCondition = lock.newCondition(); //创建out方法的条件对象
private String name;
private int count = 1;
private boolean flag = false;
public void set(String name) throws InterruptedException
{
lock.lock(); //获取锁
try{
while(flag) //调用setCondition的等待方法
setCondition.await();
this.name = name+"--"+count++;
System.out.println("生产者.."+this.name);
flag = true;
outCondition.signal(); //唤醒outCondition的等待
}finally{
lock.unlock(); //释放锁,一定要放在finally块中
}
}
public void out() throws InterruptedException
{
lock.lock(); //获取锁
try{
while(!flag) //调用outCondition的等待方法
outCondition.await();
System.out.println("消费者........."+this.name);
flag = false;
setCondition.signal(); //唤醒setCondition的等待
}finally{
lock.unlock(); //释放锁,一定要放在finally块中
}
}
}