1,什么是多线程间通讯?
多线程通讯:多个线程在处理同一个资源,但是任务不同。
比如说:有一堆煤(资源),一辆卡车向里放煤(Input),一辆卡车向外取煤(output),放煤和取煤的任务不同,但操作的是同一个资源。
由于有两个任务,所以要定义两个run方法。
2,以下面代码说明。
定义name和sex变量,实现交替输出不同信息的功能。定义两个run方法,一个输入名字,另一个打印名字,因为name和sex是资源,将之封装成一个类,两个run方法分别创建一个类,代码如下:
//资源 class Resource { String name; String sex; } class Input implements Runnable { Resource r; //资源是一开始就有的,定义在构造器中 Input(Resource r) { this.r = r; } public void run() { int x = 0; while(true) { //这里会出现安全问题 synchronized(r) { if(x == 0) { r.name = "mike"; r.sex = "nan"; } else { r.name = "丽丽"; r.sex = "女女女女女"; } } //实现x在0和1之间的变化,保证两个赋值都能执行到 x = (x + 1) % 2; } } } class Output implements Runnable { Resource r; Output(Resource r) { this.r = r; } public void run() { while(true) { synchronized(r) { //这句是把赋的值输出出来,跟赋值有关,也应该放在同步代码块中 System.out.println(r.name + "..." + r.sex); } } } } class ResourceDemo { public static void main(String[] args) { //创建资源 /* 创建资源,并用Input和Output传入进去,保证两个run操作的是同一个Resource对象。 并且保证了两个线程用的是同一个锁。 */ Resource r = new Resource(); //创建任务 Input in = new Input(r); Output out = new Output(r); //创建线程 Thread t1 = new Thread(in); Thread t2 = new Thread(out); //开启线程 t1.start(); t2.start(); } }
说明:不加同步时可能会出现mike...女女女女女,或,丽丽...nan的情况,因为在进行完一次赋值后,切换到另一种赋值时,如赋了mike...nan,在要赋丽丽,女女女女女时,CPU在只赋了丽丽后,就切走了,这时sex还是nan,所以出现了t2输出丽丽...nan的情况,加入同步代码块就能解决。
锁的问题:如果两个线程不用同一个锁是不能解决问题的,所以在main中创建一个Resource对象,把r传入,这样两个线程就能用同一个锁了。
1,上一个例子中,我们希望,输出完mike...nan后就输出丽丽...女女女女女,再输出mike...nan这样。
2,等待,唤醒机制
涉及的方法:
(1)wait():让线程处于冻结状态,被wait的线程会被存储在线程池中。
(2)notify():唤醒线程池中的一个线程(任意)。
(3)notifyAll():唤醒线程池中的所有线程。
这些方法都必须被定义在同步中,因为这些方法是用于操作线程状态的方法,必须要明确操作的是哪个锁上的线程。
3,为什么操作线程的方法wait,notify,notifyAll定义在了Object中?
因为这些方法是监视器的方法(锁),监视器其实就是锁,锁可以是任意对象,任意对象调用的方法一定定义在Object中。
4,以代码说明14-1中程序实现1中的需求。
//资源 class Resource { String name; String sex; //用于判断name和sex的值是否为空 boolean flag = false; } class Input implements Runnable { Resource r; //资源是一开始就有的,定义在构造器中 Input(Resource r) { this.r = r; } public void run() { int x = 0; while(true) { //这里会出现安全问题 synchronized(r) { //第一次为false,不执行 if(r.flag) { try{ r.wait(); } catch(InterruptedException e) {} } if(x == 0) { r.name = "mike"; r.sex = "nan"; } else { r.name = "丽丽"; r.sex = "女女女女女"; } //name和sex已经赋值,不为空,置为true,t2可以从中取值输出 r.flag = true; //唤醒t2线程 r.notify(); } //实现x在0和1之间的变化,保证两个赋值都能执行到 x = (x + 1) % 2; } } } class Output implements Runnable { Resource r; Output(Resource r) { this.r = r; } public void run() { while(true) { synchronized(r) { //flag已经是true,!flag是false,第一次不执行 if(!r.flag) { try{ r.wait(); }catch(InterruptedException e) {} } //这句是把赋的值输出出来,跟赋值有关,也应该放在同步代码块中 System.out.println(r.name + "..." + r.sex); //输出一次把flag置为flase,防止继续输出mike...nan, //因为若t2继续拿着执行权,!flag为true,t2会被wait。 r.flag = false; r.notify(); } } } } class ResourceDemo { public static void main(String[] args) { //创建资源 /* 创建资源,并用Input和Output传入进去,保证两个run操作的是同一个Resource对象。 并且保证了两个线程用的是同一个锁。 */ Resource r = new Resource(); //创建任务 Input in = new Input(r); Output out = new Output(r); //创建线程 Thread t1 = new Thread(in); Thread t2 = new Thread(out); //开启线程 t1.start(); t2.start(); } }
说明:第一次执行:r.flag为false,不被wait,进行赋值,mike,nan,再把flag置为true,若此时t1继续拿着执行权,判断if(r.flag)为true,执行r.wait,t1被冻结,同时唤醒了t2(t2不被冻结也可以被唤醒),这时只能执行t2,这时if(!r.flag)为false,不执行,执行输出mike...nan,再把flag置为false,并唤醒t1,这时就算t2拿着执行权,if(!r.flag)为true,t2会被wait,只能执行了t1,t1这时x=1,赋值为丽丽,女女女女女,这时在重复上面的步骤,实现了1中的需求。
上例的代码优化:
class Resource { //为了保护成员变量,使其私有化,并提供public方法将其对外公开 private String name; private String sex; private boolean flag = false; //同步函数解决线程安全问题 public synchronized void set(String name,String sex) { if(flag) { try{ this.wait(); }catch(InterruptedException e) {} } //下面的程序调用此函数向里传值,进行赋值操作 this.name = name; this.sex = sex; flag = true; this.notify(); } public synchronized void out() { if(!flag) { try{ this.wait(); }catch(InterruptedException e) {} } System.out.println(name + "..." + sex); flag = false; notify(); } } class Input implements Runnable { Resource r; Input(Resource r) { this.r = r; } public void run() { int x = 0; while(true) { if(x == 0) { //调用set方法赋值 r.set("mike","nan") } else { r.set("丽丽","女女女女女"); } x = (x + 1) % 2; } } } class Output implements Runnable { Resource r; Output(Resource r) { this.r = r; } public void run() { while(true) { r.out(); } } } class ResourceDemo { public static void main(String[] args) { Resource r = new Resource(); Input in = new Input(r); Output out = new Output(r); Thread t1 = new Thread(in); Thread t2 = new Thread(out); t1.start(); t2.start(); } }
代码说明:
生产者,消费者,比如说生产烤鸭,消费烤鸭。
单一的生产者和消费者用上例的模式就可以解决,若是多个生产者和多个消费者,则要用下列程序解决。
/* if判断标记,只有一次,会导致不该运行的线程运行了,出现了数据错误的情况。 while判断标记,解决了线程获取执行权后,是否要运行。 notify只能唤醒一个线程,如果本方唤醒了本方,没有意义。而且while判断标记+notify会导致死锁。 notifyAll解决了本方线程一定会唤醒对方线程的问题。 */ /* 问题描述:有多个烤鸭店和多个消费者,任何一个烤鸭店生产好一只烤鸭,其中一个消费者就会消费烤鸭。 */ class Resource { private String name; private int count = 1; private boolean flag = false; public synchronized void set(String name) { /* 如果这里用if,t0如果挂在try上,再被唤醒将不再进行if判断,若为while,t0如果挂在try上, 再被唤醒将继续判断是否成立。 */ while (flag) { try { this.wait(); } catch(InterruptedException e) {} } this.name = name + count; count++; System.out.println(Thread.currentThread().getName() + "..生产者.." + this.name); flag = true; /* notify的情况下,若t1,t2,t3都挂了,t0有执行权,那么他可能唤醒t1,这时只有生产者没有消费者。 */ notifyAll(); } public synchronized void out() { while(!flag) { try { this.wait(); }catch(InterruptedException e) {} } System.out.println(Thread.currentThread().getName() + "..消费者.." + this.name); flag = false; notifyAll(); } } class Producer implements Runnable { private Resource r; Producer(Resource r) { this.r = r; } public void run() { while(true) { r.set("烤鸭"); } } } class Consumer implements Runnable { private Sesource r; Consumer(Resource r) { this.r = r; } public void run() { while(true) { r.out(); } } } class ProducerConsumerDemo { public static void main(String[] args) { Resource r = new Resource(); Producer pro = new Producer(r); Consumer con = new Consumer(r); Thread t0 = new Thread(pro); Thread t1 = new Thread(pro); Thread t2 = new Thread(con); Thread t3 = new Thread(con); t0.start(); t1.start(); t2.start(); t3.start(); } }
说明:
if和notify的情况:
to拿到执行权,if(flag)为假,生产烤鸭1,count为2,置flag为true,notify一次,若t0继续拿执行权,if(flag)为true,被wait,t1执行,if(flag)为true,被wait,t2执行,if(!flag)为假,消费了烤鸭1,置flag为false,唤醒t0,t1中的一个,假设t0被唤醒,t3执行,if(!flag)为true,被wait(),t2执行,if(!flag)为true,也被wait(),这时活着的线程只有t0,t0执行,此时不再判断if,生产了烤鸭2,count为3,flag为true,notify随机唤醒一个,若唤醒了t1,t1也不判断if,生产了烤鸭3,若唤醒t2,t2消费了烤鸭3,这时烤鸭2未被消费,此时烤鸭2没有消费者,就出现了生产出的烤鸭无人消费的问题。
结果打印如:
生产烤鸭1
...消费烤鸭1
生产烤鸭2 //烤鸭2没有被消费
生产烤鸭3
...消费烤鸭3
while和notify的情况:
继续上面的说法,此时t1,t2,t3被wait,只有t0活着,flag为false,t0被唤醒后,需要判断while(flag),为假,生产烤鸭4,置flag为true,t0唤醒t1,t0继续执行,while(flag)为true,t0被wait,t1被唤醒后也判断while(flag)为true,被wait,这时4个线程都被wait了,造成死锁。
While和notifyAll解决了问题,但效率降低了,为此,JDK1.5提供了解决方案。
同步代码块对于锁的操作时隐式的。
JDK1.5以后将同步和锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显示动作。
Lock接口在java.util.concurrent.locks包中,用Lock需要导入java.util.concurrent.locks.*;
标准写法:
//Lock是接口,ReentrantLock是Lock的子类,实现了Lock Lock lock = new ReentrantLock(); public void show() { lock.lock(); try { //代码中可能会抛出异常,用try处理,抛出异常后会导致程序跳转,这样就不能释放锁了 //释放锁是必须执行的,所以放在finally内。 code... }finally { //释放锁 lock.unlock(); } }
1,Lock接口:替代了同步代码或者同步函数,将同步的隐式锁操作变成显示所操作,同时更为灵活,可以在一个锁上加多组监视器。
lock()方法获取锁。
unlock()方法释放锁,通常定义在finally代码块中。
2,Condition接口:替代了Object中的wait(),notify(),notifyAll()方法,将这些监视器方法单独进行了封装,变成Condition监视器对象,可以与任意锁进行组合。
await():-->wait();
signal();-->notify();
signalAll();-->notifyAll();
3,实现机制对比:
旧方法:
class Object {
wait();
notify();
notifyAll();
}
class Demo extends Object {}
...
Demo d = new Demo();
synchronized(d){
d.wait();
}
一个锁只能实现一组wait,notify,notifyAll方法。
JDK1.5新特性:
interface Condition {
await();
signal();
signalAll();
}
Lock lock = new ReentrantLock();
Condition c1 = lock.newCondition();
Condition c2 = lock.newCondition();
可以创建多个Condition对象,实现多组Condition中的await,signal,signalAll。
4,14-4中的代码可以写为:
//创建一个锁对象 Lock lock = new ReentrantLock(); //通过已有的锁获取该锁上的监视器对象 Condition con = lock.newCondition(); public voidset(String name) { //获取锁 lock.lock(); try { while(true) { try { //调用监视器的await方法 con.awati(); }catch(InterrputedException e) {} } this.name = name + count; count++; System.out.println(Thread.currentThread().getName()+"生产者5.0"+this.name); flag = true; //调用监视器的signalAll方法 con.signalAll(); } finally { //释放锁 lock.unlock(); } }
1,在14-4的程序中的Resource类改为。
import java.util.concurrent.locks.*; class Resource { private String name; private int count = 1; private boolean flag = false; //创建锁对象 Lock lock = new ReentrantLock(); //通过已经有的锁获取两组监视器,一组监视生产者,一组监视消费者 Condition producer_con = lock.newCondition(); Condition consumer_con = lock.newCondition(); public void set(String name) { lock.lock(); try { while(true) { try { producer_con.await(); }catch(InterruputedException e) {} } this.name = name + count; count++; System.out.println(Thread.currentThread().getName()); flag = true; //用消费者监视器唤醒一个消费者线程 consumer_con.signal(); }finally { lock.unlock(); } } public void out() { lock.lock(); try{ while(!flag) { try { consumer_con.swait(); }catch(InterruptedException e) {} } flag = false; //用生产者监视器唤醒一个生产者线程 producer_con.signal(); }finally { lock.unlock(); } } }
2,图示
以前的锁:三个方法能操作线程池中的所有线程,但只有一组监听器。
现在的锁:三个方法分别操作两个监听器中的对象。
这是JDK API文档中Condition接口中给出的范例:
class BoundedBuffer { final Lock lock = new ReentrantLock(); final Condition notFull = lock.newCondition(); final Condition notEmpty = lock.newCondition(); final Object[] items = new Object[100]; int putptr, takeptr, count; public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) notFull.await(); items[putptr] = x; if (++putptr == items.length) putptr = 0; ++count; notEmpty.signal(); } finally { lock.unlock(); } } public Object take() throws InterruptedException { lock.lock(); try { while (count == 0) notEmpty.await(); Object x = items[takeptr]; if (++takeptr == items.length) takeptr = 0; --count; notFull.signal(); return x; } finally { lock.unlock(); } } }
1,wait和sleep的区别?
(1)wait可以指定时间也可以不指定。
sleep必须执行时间。
(2)在同步中时,对CPU的执行权和锁的处理不同。
wait:释放执行权,释放锁。
sleep:释放执行权,不释放锁。
2,示例:
class Demo { void show() { synchronized(this) { wait();//t0,t1,t2都挂在这里 } } void method() { synchronized(this) { //t4拿执行权 notifyAll();//唤醒全部,t0,t1,t2 }//t4结束 } }
这时t0,t1,t2都已经进入到同步内,t0,t1,t2都有执行资格,但t4释放锁后,只有一个线程拿到锁,所以还能保证同步性。
1,停止线程
(1)stop方法(已过时,不可用)
(2)run方法结束。
如何控制线程的任务结束呢?
任务中一般都会有循环结构,只要控制住循环就可以结束任务,控制循环结束通常以定义标记的形式完成。如:
class StopThread implements Runnable { private boolean flag = true; public void run() { while(flag) { System.out.println(Thread.currentThread().getName()+"...."); } } public void setFlag() { flag = false; } } class StopThreadDemo { public static void main(String[] args) { StopThread st = new StopThread(); Thread t1 = new Thread(st); Thread t2 = new Thread(st); t1.start(); t2.start(); int num = 1; for(;;) { //定义循环结束条件 if(++num == 50) { /* 如果num=50,则把flag置为false,并退出无限循环。 将flag置为false,则run方法中的while(flag)为假,不再执行。 实现了用标记结束run方法。 */ st.setFlag(); break; } System.out.println("main..." + num); } System.out.println("over"); } }
1,如果线程处于了冻结状态,无法读取标记,该如何结束呢?
可以使用Interrupt方法将线程从冻结状态强制恢复到运行状态中来,让线程具备CPU的执行资格。Interrupt方法为强制动作,会发生InterruptedException异常,记得要处理。
例如:
class StopThread implements Runnable { private boolean flag = true; public synchronized void run() { while(flag) { //产生InterruptException异常,用try-catch处理 try { //t1,t2进入后会被wait,用后面的interrupt方法中断wait, //是t1,t2可以继续执行输出,并把标记改为false,是线程结束。 wait(); }catch(InterruptedException e) { System.out.println(Thread.currentThread().getName()+"..."+e); flag = false; } System.out.println(Thread.currentThread().getName() + "....."); } } public void setFlag() { flag = false; } } class StopThreadDemo { public static void main(String[] args) { StopThread st = new StopThread(); Thread t1 = new Thread(st); Thread t2 = new Thread(st); t1.start(); t2.start(); int num = 1; for(;;) { if(++num == 50) { //interrupt方法中断t1,t2的wait状态。 t1.interrupt(); t2.interrupt(); break; } System.out.println("main ... " + num); } System.out.println("over"); } }
1,守护线程setDeamon:将该线程标记为守护或用户线程,当正在运行的线程都是守护线程时,Java虚拟机退出。
可将守护线程理解为后台线程,后台线程依附于前台线程,前台线程结束后,后台线程也自动结束。
2,如上例中,把t2.interrupt()注释掉,这样只有t1继续执行并且t1线程结束,t2结束不了,则整个进程结束不了,若在t2.start()上方加上一句:t2.setDeamon(),则t2变为后台线程,当主线程与t1线程都结束时,t2也会随之结束。
setDeamon方法必须在启动线程前调用。
1,join方法:等待该线程结束。
如:
t1.start();
t1.join(); //从main得到执行权,等到t1执行完,t2和main在执行。
t2.start();
若把t1.join()放在t2.start()下面,则main不执行,t2和t1随机执行,main只等t1结束后就开始执行,跟t2没有关系。
什么时候用join?
在临时加入一个线程运算时可以使用join方法。
2,优先级
Thread类中有toString()方法,返回线程名字,优先级和线程组。
线程的优先级是指线程被CPU执行的机率,值越高,机率越大,范围是1-10。
Thread中有三个字段:
staticint MAX_PRIORITY;值为10
staticint MIN_PRIORITY;值为1
staticint NORM_PRIORITY;值为5
如:
将t1的优先级设置为10可以这么写:
t1.setPriority(Thread.MAX_PRIORITY);
3,线程组:把线程进行组的划分。
若要对一组线程进行某种统一的操作,可将这组线程加入线程组(ThreadGroup)。
4,yield()方法,临时暂停线程使用。