线程间的通信:
其实就是多个线程在操作同一个资源,但是操作的功能不同
实例详解:
我们要用多线程实现两个对象,一个叫张三的男性和李四的女性一次交替打印出来,如下
张三---男
李四---女
张三---男
李四---女
张三---男
李四---女
如何实现:
利用多线程,第一个线程写入(input)用户,另一个线程(out)用户
解决的问题:
- 进出资源搭配不正确的问题,线程抢夺资源的问题---加锁
- 防止一个线程抢占的次数多的问题,出现输出和输入次数不匹配---wait(),notify(),notifyAll()
- 防止死锁,只唤醒对方,不唤醒本方---Lock
代码实现:
- 共享资源源实体类
class Res {
public String Sex;
public String Name;
}
- 输入线程资源
class IntThrad extends Thread {
private Res res; //不直接赋值,说明操作的同一对象
public IntThrad(Res res) {
this.res = res;
}
@Override
public void run() {
int count = 0;
while (true) {
if (count == 0) {
res.Name = "张三";
res.Sex = "男";
} else {
res.Name = "李四";
res.Sex = "女";
}
count = (count + 1) % 2;
}
}
}
- 输出线程
class OutThread extends Thread {
private Res res;
public OutThread(Res res) {
this.res = res;
}
@Override
public void run() {
while (true) {
System.out.println(res.Name + "---" + res.Sex);
}
}
}
- 运行代码
public class ThreadDemo01 {
public static void main(String[] args) {
Res res = new Res();
//进出线程传入的是同一对象
InputThread inputThread = new InputThread(res);
OutThrad outThrad = new OutThrad(res);
inputThread.start();
outThrad.start();
}
}
- 运行结果:
张三---男
张三---女
李四---女
张三---女
- 出现的问题:
人名和性别不匹配
- 为什么会出现这样的情况
数据发生错乱,造成线程安全问题(消费者可能还没读,生产者就已经修改了),两个run方法存放着代码,但是都在操作同一资源,在操作同一资源的时候,就有可能刚把张三输入,输出就获得了执行权,就输出了,还没有等男这个属性赋值就输出了,输出的是上一个性别属性,可能是男,可能是女
- 如何解决这样的情况
加锁(synchronized),锁的对象是谁(找唯一对象,intThread.class,outThread.class,Res也可以)
- 输入线程加上synchronized
class IntThrad extends Thread {
private Res res;
public IntThrad(Res res) {
this.res = res;
}
@Override
public void run() {
int count = 0;
while (true) {
synchronized (res) {
if (count == 0) {
res.Name = "张三";
res.Sex = "男";
} else {
res.Name = "李四";
res.Sex = "女";
}
count = (count + 1) % 2;
}
}
}
}
- 输出线程加上synchronized
class Res {
public String userName;
public String sex;
}
class InputThread extends Thread {
private Res res;
public InputThread(Res res) {
this.res = res;
}
@Override
public void run() {
int count = 0;
while (true) {
synchronized (res) {
if (count == 0) {
res.Name = "张三";
res.sex = "男";
} else {
res.Name = "李四";
res.sex = "女";
}
count = (count + 1) % 2;
}
}
}
}
class OutThrad extends Thread {
private Res res;
public OutThrad(Res res) {
this.res = res;
}
@Override
public void run() {
while (true) {
synchronized (res) {
System.out.println(res.Name + "---" + res.sex);
}
}
}
}
- 运行代码
public class ThreadDemo01 {
public static void main(String[] args) {
Res res = new Res();
InputThread inputThread = new InputThread(res);
OutThrad outThrad = new OutThrad(res);
inputThread.start();
outThrad.start();
}
}
- 输出结果:
张三---男
张三---男
李四---女
张三---男
- 出现的问题:
张三多次出现,李四出现的次数少,不是交替出现,,我们要实现一进一出
- 为什么会出现这样的问题:
因为input和output都有可能多次抢到执行权
- 解决方案:
添加标记,输入进去就唤醒输出的线程,wait()和notify(),notifyAll方法(一等一醒)---等待唤醒机制
wait()、notify、notifyAll()方法
- wait()、notify()、notifyAll()是三个定义在Object类里的方法,可以用来控制线程的状态。
- 这三个方法最终调用的都是jvm级的native方法,随着jvm运行平台的不同可能有些许差异。
- 如果对象调用了wait方法就会使持有该对象的线程把该对象的控制权交出去,然后处于等待状态。
- 如果对象调用了notify方法就会通知某个正在等待这个对象的控制权的线程可以继续运行。
- 如果对象调用了notifyAll方法就会通知所有等待这个对象控制权的线程继续运行。
- 注意:一定要在线程同步中使用,并且是同一个锁的资源。 因为wait(),notify(),notifyAll()都使用在同步中,因为要对持有监控器(锁)的线程操作,所以要使用在同步中,因为只有同步才具有锁
class Res {
public String Sex;
public String Name;
//线程通讯标识
public boolean flag = false;
}
class IntThrad extends Thread {
private Res res;
public IntThrad(Res res) {
this.res = res;
}
@Override
public void run() {
int count = 0;
while (true) {
synchronized (res) {
if (res.flag) {
try {
// 当前线程变为等待,但是可以释放锁
res.wait();
} catch (Exception e) {
}
}
if (count == 0) {
res.Name = "余胜军";
res.Sex = "男";
} else {
res.Name = "小紅";
res.Sex = "女";
}
count = (count + 1) % 2;
res.flag = true;
// 唤醒res线程池中的线程,默认是线程池中的第一个
res.notify();
}
}
}
}
class OutThread extends Thread {
private Res res;
public OutThread(Res res) {
this.res = res;
}
@Override
public void run() {
while (true) {
synchronized (res) {
if (!res.flag) {
try {
res.wait();
} catch (Exception e) {
// TODO: handle exception
}
}
System.out.println(res.Name + "--" + res.Sex);
res.flag = false;
res.notify();
}
}
}
}
public class ThreaCommun {
public static void main(String[] args) {
Res res = new Res();
IntThrad intThrad = new IntThrad(res);
OutThread outThread = new OutThread(res);
intThrad.start();
outThread.start();
}
}
- wait()方法会抛异常
- 为什么这些操作线程的方法要定义Object类中呢?
因为这些方法在操作同步中线程时,都必须要标识他们所操作线程持有的锁,只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒,不可以对不同锁中的线程进行唤醒,也就是说,等待和唤醒必须是同一个锁,而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中
- if和while的问题
生产者消费者问题
有多个线程生产和有多个线程消费的问题
出现的问题:两个生产,一个消费,或者一个生产,消费了两个的问题
错误原因:(用if判断标记)生产者把生产者唤醒了,而且不需要判断标记,不是把消费者唤醒,就会出现不对称的情况
解决办法:用while判断,但是又会发生死锁,在while判断的基础上,唤醒全部(notifyAll),既唤醒本方,又唤醒对方
对于多个生产者和消费者,为什么要定义while判断标记?
因为想让被唤醒的线程再一次判断标记,以防出现条件不满足就直接执行后面代码的情况(因为是多线程)
- 为什么定义notifyAll?
因为需要唤醒对方线程,因为只用notify,容易出现只唤醒本方线程的情况,导致程序中的所有线程都等待。发生死锁的问题
- 死锁怎么产生的?
由于资源有限,加入有两个资源只有一份,每一个线程都需要两个资源才能继续执行,但是在执行一个线程的时候,获取了第一个资源,还未获取第二个资源的时候,sleep了一下,就让第二个进行进来了,第二个进程获取了第二个资源(程序这个设置的,有意为之),但是获取不了第一个资源,一直在等着被释放,而这时第一个线程sleep时间到了之后,因为缺少第二个资源而无法继续进行
- 解决办法:
我们要只唤醒对方,不唤醒本方怎么办?
解决方案:Lock,JDk1.5中提供了多线程升级解决方案,将同步synchronize替换成现实Lock操作,将Object中的wait,notify,notifyAll,替换成了Condition对象,该对象可以Lock锁,进行获取
class Res {
public String Name;
public String sex;
public boolean flag = false;
Lock lock = new ReentrantLock();
}
class InputThread extends Thread {
private Res res;
Condition newCondition;
public InputThread(Res res, Condition newCondition) {
this.res = res;
this.newCondition=newCondition;
}
@Override
public void run() {
int count = 0;
while (true) {
// synchronized (res) {
try {
res.lock.lock();
if (res.flag) {
try {
// res.wait();
newCondition.await();
} catch (Exception e) {
// TODO: handle exception
}
}
if (count == 0) {
res.Name = "张三";
res.sex = "男";
} else {
res.Name = "李四";
res.sex = "女";
}
count = (count + 1) % 2;
res.flag = true;
// res.notify();
newCondition.signal();
} catch (Exception e) {
// TODO: handle exception
}finally {
res.lock.unlock();
}
}
// }
}
}
class OutThrad extends Thread {
private Res res;
private Condition newCondition;
public OutThrad(Res res,Condition newCondition) {
this.res = res;
this.newCondition=newCondition;
}
@Override
public void run() {
while (true) {
// synchronized (res) {
try {
res.lock.lock();
if (!res.flag) {
try {
// res.wait();
newCondition.await();
} catch (Exception e) {
// TODO: handle exception
}
}
System.out.println(res.userName + "," + res.sex);
res.flag = false;
// res.notify();
newCondition.signal();
} catch (Exception e) {
// TODO: handle exception
}finally {
res.lock.unlock();
}
// }
}
}
}
public class ThreadDemo01 {
public static void main(String[] args) {
Res res = new Res();
Condition newCondition = res.lock.newCondition();
InputThread inputThread = new InputThread(res,newCondition);
OutThrad outThrad = new OutThrad(res,newCondition);
inputThread.start();
outThrad.start();
}
}
- 停止线程
如何停止线程?
只要一种,run方法结束,开始多线程运行,运行代码通常是循环结构。
只要控制住循环,就可以让run方法结束,也就是线程结束(标记一下,符合标记,break)
特殊情况:
当线程处于冻结状态,就不会读取到标记,那么线程就不会结束(父线程结束了,但是子线程还处于冻结状态,就停止不掉)
解决方案:
当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除,强制让线程恢复到运行状态中来,这样就可以操作标记让线程结束
Thread类提供该方法:interrupt()
- 守护线程
setDaemon(boolean on):将该线程标记为守护线程或用户线程,当被守护的线程消失,那么守护线程也消失(后台线程--当前台线程结束,后台线程也自动结束)
- join()
当A线程执行到B线程的.join()方法时,A就会等B线程都执行完,A才会继续执行
可以用来临时加入线程执行
- yield()
暂停当前正在执行的线程对象,并执行其他线程
可以稍微减缓线程执行的频率