线程间通讯:
其实就是多个线程在操作同一个资源,但是操作的动作不同
等待唤醒机制:
wait:
notify:
notifyAll;
都是用在同步中,因为要对持有监视器(锁)的线程操作。
所以要是用在同步中,因为只有同步才具有锁。
等待唤醒必须是同一个锁,而锁可以是任意对象,所以可以被任意对象调用的方法定义在Object类中。
生产者消费者实例(多个生产者,多个消费者):
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();
}
}
class Resource{
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void set(String name){
//对线程只判断了一次,如果线程被释放,再获得了执行权,则不需要判断就可通过了,这是一个问题
if(flag)
try{this.wait();}catch(Exception e){}
this.name = name+"--"+count++;
System.out.println(Thread.currentThread().getName()+"....生产者..."+this.name);
flag = true;
this.notify();
}
public synchronized void out(){
if(!flag)
try{this.wait();}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
flag = false;
this.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();
}
}
}
此程序容易出现多次生产,单次消费。或单词生产多次消费的问题。
问题产生原因:
以上程序块中
设生产者:Thread-0,Thread-1 消费者:Thread-2,Thread-3
0线程先进入,flag=true,1线程进入,因flag=true,等待
0线程再次进入,因flag=true,等待。
2线程进入,flag=false,并唤醒了0线程,0,1线程已经路过判断关卡,因此直接执行代码,
flag=true,并随机唤醒了1线程;1线程已经经过判断关卡,因此直接执行代码,所以会出现两次生产
一次消费的问题。
如果改用while(flag)循环判断,则解决了线程只判断一次便通过的问题,但容易出现无限等待的问题,
就是几个线程互相等待。如果不懂可以让代码跑起来试试。
问题产生原因:
flag=false,3线程进入,等待。
0线程进入,flag=true,1线程进入,因flag=true,等待
0线程再次进入,因flag=true,等待。
2线程进入,flag=false,随机释放3线程,2线程继续运行,因flag=false,结果处于等待。
因flag=false,3线程也处于等待,因此所有的线程全部等待,无人激活
解决问题:
既不能让同步代码块只判断一次便通过,又不能让程序进入无线等待中。
综上,采用notifyAll()方法,将所有的线程全部唤醒,而不是仅仅只唤醒一个。这样一来
既可以无限判断,不至于有漏网之鱼,又免去无限等待的痛苦。
修改如下:
public synchronized void set(String name){
while(flag)
try{this.wait();}catch(Exception e){}
this.name = name+"--"+count++;
System.out.println(Thread.currentThread().getName()+"....生产者..."+this.name);
flag = true;
this.notifyAll();
}
public synchronized void out(){
while(!flag)
try{this.wait();}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
flag = false;
this.notifyAll();
}
JDK5.0升级以后的多线程通信
其中用到了 Lock 类, Condition 类;以下代码与Lock类api中示例代码相似
private Lock lock = new Lock();
Condition condition_pro = lock.newCondition();
Condition condition_con = lock.newCondition();
//生产者生产方法
public void set(String name)throws InterruptedException{
//获取锁
lock.lock();
try{
while(flag)
condition_pro.await();//生产者等待
this.name = name+"--"+count++;
System.out.println(Thread.currentThread().getName()+"....生产者..."+this.name);
flag = true;
//唤醒消费者(唤醒对方)
condition_con.signal();
}finally{
//释放锁
lock.unlock();
}
}
//消费者消费方法
public void out()throws InterruptedException{
lock.lock();
try{
while(!flag)
condition_con.await();//消费者等待
System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
flag = false;
//唤醒生产者(唤醒对方)
condition_pro.signal();
}finally{
lock.unlock();
}
}
JDK5.0升级后的新特性:
jdk1.5中提供了多线程升级解决方案。
将同步synchronized替换成了显示的lock操作。
将Object中的wait,notify,notifyAll,替换成了Condition对象,该对象可以通过Lock锁进行获取。
如何让线程停止:
原理:只有一种,run方法结束。
开启多线程运行,运行代码通常是循环结构。
只要控制住循环,就可以让run方法结束,也就是线程结束。
特殊情况:
当线程处于了冻结状态,就不会读取到结束标记,线程就不会结束。
此时只能强制让线程恢复到运行状态中来,interrupt()就是具有这样功能的方法。
Thread类中的一些常用方法:
interrupt()中断方法:该方法是结束线程的冻结状态,使线程回到运行状态来。
setDaemon(boolean on):将该线程标记为守护线程或用户线程(后台线程)。此方法必须在开启线程之前调用,具体可以写实例试试
join(): 该方法用于线程获得cpu(执行权),直到该线程终止。
join例子:
Demo d = new Demo();//创建一个Runnable子类对象
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
//此处t1线程调用join方法,获得main线程的执行权,main线程暂停服务,直到t1线程执行结束
//由于main线程失去了执行权,所以t2线程还没有被开启。
t1.join();
t2.start();
优先级问题:
Demo d = new Demo();//创建一个Runnable子类对象
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
//设置线程优先级
t1.setPriority(Thread.MAX_PRIORITY);
t2.start();
yield() :暂停当前正在执行的线程对象,并执行其他线程。
稍微暂停当前线程,并执行其他线程,放在run函数中,有使得线程交叉运行的效果;