扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
多线程概念
创新互联公司是一家专业提供南关企业网站建设,专注与网站设计、成都网站设计、H5场景定制、小程序制作等业务。10年已为南关众多企业、政府机构等服务。创新互联专业网站制作公司优惠进行中。
在我们的程序层面来说,多线程
通常是在每个进程
中执行的,相应的附和我们常说的线程与进程
之间的关系。线程与进程的关系:线程可以说是进程的儿子,一个进程可以有多个线程
。但是对于线程来说,只属于一个进程。再说说进程,每个进程的有一个主线程
作为入口,也有自己的唯一标识PID
,它的PID也就是这个主线程的线程ID
。
对于我们的计算机硬件来说,线程是进程
中的一部分,也是进程的的实际运作单位,它也是操作系统中的最小运算调度单位。多线程可以提高CPU的处理速度。当然除了单核CPU,因为单核心CPU同一时间只能处理一个线程。在多线程环境下,对于单核CP来说,并不能提高响应速度,而且还会因为频繁切换线程上下文导致性能降低。多核心CPU具有同时并行执行线程的能力,因此我们需要注意使用环境。线程数超出核心数时也会引起线程切换,并且操作系统对我们线程切换是随机的。
引入
共享资源
,从而实现一些特殊任务。上面说了,多线程在进行切换时CPU随机调度
的,假如我们直接运行多个线程操作共享资源的话,势必会引起一些不可控错误因素。为了解决多线程对同一共享变量的争夺
。Java 线程通信的方式
主要介绍wait/notify,也有ReentrantLock的Condition条件变量的await/signal,LockSupport的park/unpark方法,也能实现线程之间的通信。主要是阻塞/唤醒通信模式。
首先说明这种方法一般都是作用于调用方法的所在线程。比如在主线程执行wait方法,就是将主线程阻塞了。
wait/notify机制
await/signal
park/unpark
如果一个线程先于被通知线程调用wait()前调用了notify(),等待的线程将错过这个信号。
flag=FALSE
表示还没wait,在wait之前将设置flag=TRUE
,在notify之后设置flag=FALSE
。每次notify唤醒之前都判断flag=true
是否已经wait,在wait中判断flag=false
是否已经notify。核心代码演示
// 线程一使用LOCK1对象调用wait方法阻塞自己
executor.execute(new ThreadTest("线程一",LOCK1,LOCK2));
synchronized (LOCK1) {
System.out.println("main执行notify方法让线程一醒过来");
LOCK1.notify();
}
但是他很有可能醒不来,因为主线程调用LOCK1对象的notify方法,可能主线程已经执行完了,上面线程还没创建完成,也就是没有进入wait状态。就醒不来了。
解决方式:使用信号量标志进行判断是否已经进入wait
synchronized (LOCK1) {
while (true) {
if (FLAG.getFlag()) {
System.out.println("main马上执行notify方法让线程一醒过来" + "flag = " + FLAG.getFlag());
LOCK1.notify();
// 将标志位变为FALSE
FLAG.setFlag(Constants.WaitOrNoWait.NO_WAIT.getFlag());
System.out.println("main执行notify方法完毕" + "flag = " + FLAG.getFlag());
break;
}
}
}
由于莫名其妙的原因,线程有可能在没有调用过notify()和notifyAll()的情况下醒来。
synchronized (waitName) {
while (!flag.getFlag()) {
try {
// 将标志位设置为TRUE
flag.setFlag(Constants.WaitOrNoWait.WAIT.getFlag());
System.out.println("name;"+name+" 我睡着了进入阻塞状态" + "flag = " + flag.getFlag());
waitName.wait();
System.out.println("name;"+name+" 我醒来了" + "flag = " + flag.getFlag());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private final static Object LOCK1 = new Object();
private final static Object LOCK2 = new Object();
private final static Constants.WaitStatus FLAG = new Constants.WaitStatus(false);
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 5, 1, TimeUnit.DAYS, new ArrayBlockingQueue<>(4), new ThreadPoolExecutor.AbortPolicy());
executor.execute(new ThreadTest("线程一",LOCK1,LOCK2, FLAG));
// ···唤醒
}
class ThreadTest implements Runnable { //阻塞··· }
完整代码可以看这[Gitee仓库完整代码][https://gitee.com/malongfeistudy/javabase/tree/master/Java多线程_Study/src/main/java/com/mlf/thread/demo_wait_notify]
首先复习一下创建线程的几种方式和其的优缺点:
使用线程池的步骤
在主线程自定义线程池使用实例,这里需要根据实际情况定义锁对象,因为我们需要使用这些锁对象控制多线程之间的运行顺序以及线程之间的通信。在Java中每个对象都会在初始化的时候拥有一个监视器,我们需要利用好他进行并发编程。这种创建线程池的方法也是阿里巴巴推荐的方式,想想以阿里的体量多年总结出来的总没有错,大家还是提前约束自己的编码习惯等。安装一个阿里代码规范的插件对自己的程序员道路是比较nice的。
/**
* 每个使用对应唯一的对象作为监视器对象锁。
*/
public static final Object A_O = new Object();
public static final Object B_O = new Object();
/** 参数:
* int corePoolSize, 核心线程数
* int maximumPoolSize, 最大线程数
* long keepAliveTime, 救急存活时间
* TimeUnit unit, 单时间位
* BlockingQueue workQueue, 阻塞队列
* RejectedExecutionHandler handler 拒绝策略
**/
// 使用阿里巴巴推荐的创建线程池的方式
// 通过ThreadPoolExecutor构造函数自定义参数创建
ThreadPoolExecutor executor = new ThreadPoolExecutor(
3,
5,
1,
TimeUnit.DAYS,
new ArrayBlockingQueue<>(2),
new ThreadPoolExecutor.AbortPolicy());
class ThreadDiy implements Runnable {
private final String name;
/**
* 阻塞锁对象 等待标记
**/
private final Object waitFor;
/**
* 执行锁对象 下一个标记
**/
private final Object next;
public AlternateThread(String name, Object waitFor, Object next) {
}
@Override
public void run() {
// 线程的代码逻辑···
}
}
题目:现在有两个线程,不论线程的启动顺序,我需要指定线程一先执行,然后线程二再执行。
初始化两个对象锁作为线程监视器。
private final static Object ONE_LOCK = new Object();
private final static Object TWO_LOCK = new Object();
接下来初始化线程池,上面有具体的介绍,在这就不多说了
使用线程池去执行我们的两个线程,在这里我们需要分析的是
// 使用线程池创建线程
executor.execute(new DiyThread(1, ONE_LOCK, TWO_LOCK));
executor.execute(new DiyThread(2, TWO_LOCK, ONE_LOCK));
synchronized (ONE_LOCK) {
ONE_LOCK.notify();
}
创建线程类
我们使用继承Runnable的方式去创建线程对象,需要在这个类中实现每个线程执行的逻辑,我们根据题目可以得出,我们要控制每个线程的执行顺序,怎么办?那么就要实现所有线程之间的通信,通信方式采用wait-notify的方式即可。我们使用wait-notify的时候必须结合synchronized,那么就需要控制两个对象锁。因为我们不光是控制自己,还有另一个线程。
我们再分析一下题意,首先需要指定先后执行的顺序,那么就需要实现两个线程之间的通信。其次呢,我们得控制两个线程,那么就需要两个监视器去监视这两个线程。
我们定义这两个监视器对象为own和other。然后再新增一个属性threadId来标识自己。
private final int threadId;
private final Object own;
private final Object other;
接下来就是编写Run方法了
每个线程首先需要阻塞自己,等待唤醒。然后唤醒之后,再去唤醒另外一个线程。这样就实现了自定义顺序。至于先唤醒哪个线程,交给我们的主线程去完成。
这里需要注意的是,如果我们只是单纯地执行了多个线程对象,但是主线程没有主动去唤醒其中一个,这样就会形成类似于死锁的循环等待。你需要我唤醒,我需要你唤醒。这个时候需要主线程去插手唤醒其中的任意一个线程。
第一步阻塞自己own
synchronized (own) {
try {
own.wait();
System.out.println(num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
第二步唤醒other
synchronized (other) {
other.notify();
}
题目需求:现在需要使用三个线程轮流打印输出。说白了也就是多线程轮流执行罢了,和问题一控制两个线程打印顺序没什么区别
/**
* 阻塞锁对象 等待标记
**/
private final Object waitFor;
/**
* 唤醒锁对象 下一个标记
**/
private final Object next;
run方法的逻辑和上面的基本一样。 一个线程一旦调用了任意对象的wait()方法,它就释放了所持有的监视器对象上的锁,并转为非运行状态。
每个线程首先会调用 waitFor对象的 wait()方法,随后该线程进入阻塞状态,等待其他线程执行自己引用的该 waitFor对象的 notify()方法即可。
while (true) {
synchronized (waitFor) {
try {
waitFor.wait();
System.out.println(name + " 开始执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (next) {
next.notify();
}
}
主线程需要初始化线程池、执行三个线程,并且最后需要打破僵局,因为此时每个线程都是阻塞状态,他们没法阻塞/唤醒循环下去。
synchronized (A_O) {
A_O.notify();
}
模拟执行流程
/**
* 模拟执行流程
* 打印名(name) 等待标记(waitFor) 下一个标记(next)
* 1 A B
* 2 B C
* 3 C A
*
* 像不像Spring的循环依赖:确实很像,Spring中的循环依赖就是 BeanA 依赖 BeanB,BeanB 依赖 BeanA;
* 他们实例化过程中都需要先属性注入对方的实例,倘若刚开始的时候都没有实例化,初始化就会死等。类似于死锁。
**/
使用多线程轮流打印 01234····
具体代码请移步到Gitee仓库:[顺序打印自增变量][https://gitee.com/malongfeistudy/javabase/blob/master/Java多线程_Study/src/main/java/com/mlf/thread/print/AddNumberPrint2.java]
条件变量Condition的使用
如有问题,请留言评论。
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流