前言
今天小咸儿来讲解一个好玩的事,那就是线程之间该如何通信,线程通信之后又会出现什么问题?
叙述
宝图
先来一张导图来看看线程通讯的分布?
Join 线程
疑问:如果想要线程按照用户自定义的顺序执行的话,那该如何操作呢?
思考:如果能够让线程等待先执行的线程执行完,再执行不就能达到效果了吗!?
果然出现问题之后,就会有对应的解决之法,接下来先看一个简单而且有趣的方法——join
Thread 提供了让一个线程等待另一个线程完成的方法——join()方法。
代码展示:
package com.practice.demo.thread;
/**
* 线程的第三个实例,继承Thread类
* 测试join()方法
* @author Phyllis
* @date 2019年7月2日17:26:23
*/
public class JoinThread extends Thread{
/**
* 提供一个有参数的构造器,用于设置该线程的名字
* @param name 线程的名字
*/
public JoinThread(String name){
super(name);
}
/**
* 重写run()方法,定义线程执行体
*/
@Override
public void run() {
for (int i = 0; i<100; i++){
System.out.println(getName()+" "+ i);
}
}
public static void main(String[] args) throws Exception{
// 启动子线程
new JoinThread("新线程").start();
for (int i = 0; i<100; i++){
if (i == 20){
JoinThread jt = new JoinThread("被Join的线程");
jt.start();
// main线程调用了jt线程的join()方法,main线程
// 必须等待jt执行结束才会向下执行
jt.join();
}
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
打印结果:
结果: 当i=20时,被join的线程开始执行,这时,main线程一直处于阻塞的状态,所以一直在等待,等到join的线程结束后,则开始执行。
sleep
这个方法看到名字就会很清楚是干什么用的了,就是表面意思,让线程睡一会。
synchronized (res){
try {
if (!res.flag){
res.wait();
}
Thread.sleep(1000);
}catch (Exception e){
}
System.out.println(res.name + "," + res.sex);
res.flag = false;
res.notify();
}
如果需要让当前正在执行的线程暂停一段时间,并进入阻塞状态,则可以通过Thread类的静态sleep()方法来实现。
与之类似的方法还有一个yield()方法
yield
yield()静态方法和sleep()方法类似,它也可以让当前正在执行的线程暂停,但它不会阻塞该线程,它只是将该线程转入就绪状态。
synchronized (res){
try {
if (!res.flag){
res.wait();
}
Thread.yield();
}catch (Exception e){
}
System.out.println(res.name + "," + res.sex);
res.flag = false;
res.notify();
}
缺点: yield方法只是让当前线程暂停一下,让系统的线程调度器重新调度一次,完全可能的情况是:当某个线程调用了yield方法暂停之后,线程调度器又再次调度到了该线程。
sleep方法和yield方法的区别:
- sleep方法暂停当前线程后,会给其他线程执行的机会,不会理会其他线程的优先级;但是yield方法只会给优先级相同或者更高级别的线程机会。(看来yield还看高低区分的哼)
- sleep方法会将线程转入阻塞状态,直到经过阻塞时间才会转入就绪状态;而yield不会将线程转入阻塞状态,它只是强制当前线程进入就绪状态。因此有可能在暂停后,因获得处理器资源而再次执行。
- sleep方法声明抛出了InterruptException异常,所以调用sleep方法时要么捕捉该异常,要不显示声明抛出该异常;而yield方法则没有声明抛出任何异常。
- sleep方法比yield方法有更好的可移植性。
等待唤醒机制
重点来了,等待唤醒机制是什么,在多线程执行过程中,在A线程对共享变量,进行操作时,B线程要等待。A线程结束操作时,则唤醒B线程
示例:生产与消费
package com.practice.demo.thread;
/**
* 共享对象
* @author Phyllis
* @date 2019年7月12日09:15:26
*/
class Res{
/**
* 姓名
*/
public String name;
/**
* 性别
*/
public String sex;
/**
* 为true的情况下,允许读,不能写
* 为false的情况下,允许写,不能读
*/
public boolean flag = false;
}
/**
* 生产这线程
* @author Phyllis
* @date 2019年7月12日09:16:55
*/
class InThread extends Thread{
public Res res;
public InThread(Res res){
this.res = res;
}
@Override
public void run() {
// count 为0 或者 1
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 = "男";
}
// 0 1 0 1 0 1 0 1
count = (count + 1) % 2;
// 标记当前线程为等待
res.flag = true;
// 唤醒被等待的线程
res.notify();
}
}
}
}
/**
* 消费这线程,读
* @author Phyllis
* @date 2019年7月12日09:16:55
*/
class OutThread extends Thread {
public Res res;
public OutThread(Res res){
this.res = res;
}
@Override
public void run() {
while (true){
synchronized (res){
try {
if (!res.flag){
res.wait();
}
Thread.sleep(1000);
}catch (Exception e){
}
System.out.println(res.name + "," + res.sex);
res.flag = false;
res.notify();
}
}
}
}
/**
* 客户端
* @author Phyllis
* @date 2019年7月12日09:16:55
*/
public class ThirdThread{
public synchronized static void main(String[] args) throws InterruptedException {
Res res = new Res();
InThread inThread = new InThread(res);
OutThread outThread = new OutThread(res);
inThread.start();
outThread.start();
}
}
打印结果:
所以使用等待唤醒机制来保证生产一个,消费一个。
注意: wait和notify方法,一定要在synchronized中进行,持有同一把锁。
区别
wait和join的区别:
wait需要被唤醒。
wait和sleep的区别:
sleep不会释放锁。
问题
在宝图中也可以明显的看出,小咸儿在通讯那里打了两个问号,也就是说通讯有可能会导致线程安全的问题,那么线程安全是什么呢?又该如何解决呢?且听小咸儿下次分享。
总结
多线程是十分实用并且常用的内容,接下来小咸儿还会继续深入学习多线程,更多的内容等待更新。
感谢您的阅读~~