Java基础14–多线程 – xzm

14-1,多线程-线程间通信示例

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传入,这样两个线程就能用同一个锁了。

14-2,线程间通信,等待唤醒机制

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中的需求。

14-3,线程间通信-等待唤醒机制-代码优化

上例的代码优化:

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();}}

14-4,线程间通信-多生产者多消费者问题

代码说明:

生产者,消费者,比如说生产烤鸭,消费烤鸭。

单一的生产者和消费者用上例的模式就可以解决,若是多个生产者和多个消费者,则要用下列程序解决。

/*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提供了解决方案。

14-5,多生产者多消费者问题-JDK1.5性特性-Lock

同步代码块对于锁的操作时隐式的。

JDK1.5以后将同步和锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显示动作。

Lock接口在java.util.concurrent.locks包中,用Lock需要导入java.util.concurrent.locks.*;

标准写法:

//Lock是接口,ReentrantLock是Lock的子类,实现了LockLock lock = new ReentrantLock();public void show() {lock.lock();try {//代码中可能会抛出异常,用try处理,抛出异常后会导致程序跳转,这样就不能释放锁了//释放锁是必须执行的,所以放在finally内。code...}finally {//释放锁lock.unlock();}}

14-6,JDK1.5新特性-Condition

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();}}

14-7,解决方案

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,图示

以前的锁:三个方法能操作线程池中的所有线程,但只有一组监听器。

现在的锁:三个方法分别操作两个监听器中的对象。

14-8,JDK1.5解决方法—范例

这是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();     }   } }

14-9,多线程-wait和sleep的区别

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释放锁后,只有一个线程拿到锁,所以还能保证同步性。

14-10,停止线程的方式-定义标记

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");}}

14-11,停止线程的方式-Interrupt

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");}}

14-12,守护线程-setDeamon

1,守护线程setDeamon:将该线程标记为守护或用户线程,当正在运行的线程都是守护线程时,Java虚拟机退出。

可将守护线程理解为后台线程,后台线程依附于前台线程,前台线程结束后,后台线程也自动结束。

2,如上例中,把t2.interrupt()注释掉,这样只有t1继续执行并且t1线程结束,t2结束不了,则整个进程结束不了,若在t2.start()上方加上一句:t2.setDeamon(),则t2变为后台线程,当主线程与t1线程都结束时,t2也会随之结束。

setDeamon方法必须在启动线程前调用。

14-13,其他方法-join等

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()方法,临时暂停线程使用。

“人无完人金无足赤”,只要是人就不会是完美的,

Java基础14–多线程 – xzm

相关文章:

你感兴趣的文章:

标签云: