多线程
多线程
MR.XSS多线程
并发:同一时刻,多个任务交替执行,造成一种貌似同时,的错觉,单核cpu实现多任务是并发
并行:同一时刻,多个任务同时执行,多核cpu可以实现并行。并发和并行可能同时存在
多线程实现方式(三种)
- 继承Thread类(实现简单,可以直接使用父类方法,可拓展性差,不能继承其他类)
- 实现Runnable接口(拓展性强,不能直接使用Thread类里面的方法)
- 使用FutureTask创建线程,可以获取多线程执行结果
线程常用的方法
interrupt:线程中断,打断线程执行
isInterrupted() :判断线程是否被打断
- 处于阻塞状态下面的线程(sleep , wait , join),此时被打断,打断标记会置为false
- 打断处于正常运行的线程,打断标记会置为true
- 还可以打断park状态下面的线程,park方法在JUC并发包LockSupport下面,被interrupt打断后,后续再使用park方法不在起作用,使用interrupted方法将打断标记置为假,才可以是park生效
1 | public class Interrupt { |
yield: 线程礼让,让出cpu,让其他的线程先执行,但是礼让的事件不确定,所以不一定成功
join:线程插队,插队的线程一旦插队成功,则肯定先执行完插入线程的所有的任务
1 | public class Thread_Join_Yield { |
有时效的join(Long n) 等待线程运行结束,最多等待n毫秒
会根据等待时间判断,如果等待时间小于线程执行时间,就不会再等待,如果等待时间大于线程执行时间,就会随着线程结束而停止等待
不推荐的方法
有一些不推荐使用的方法,这些方法已经过时,容易破坏同步代码块,造成线程死锁
stop ——停止线程运行
suspend ——-挂起(暂停)线程运行
resume ——-恢复线程运行
线程优先级
线程优先级会提示任务调度器优先调度该线程,但是仅仅是一个提示,调度器可以忽略它
如果cpu比较忙,那么优先级高的线程将会获得更多的时间片,cpu闲置时几乎没作用
yield和线程优先级都很难对线程进行控制,具体还是依赖于操作系统的任务调度器
守护线程
用户线程: 也叫工作线程,当线程的任务执行完或通知方式结束
setDaemon() 设置为true,该线程就变为守护线程,守护线程会随着所有用户线程的结束而结束
常见的守护线程:垃圾回收机器,GC
线程的生命周期
线程的五种状态
初始状态 —-未拿到系统资源
可运行状态 —-未获得系统时间片,当获取时间片时,转换为运行状态
运行状态 —-获得系统时间片,当时间片使用完毕,转换为可运行状态
终止状态 —–表示线程执行完毕,生命周期结束
阻塞状态 —–调用阻塞API,放弃CPU使用权,启用上下文切换
线程的六种状态
按照java中枚举类来说的,线程有六种状态
synchronized解决方案
应用互斥
为了避免临界区竞态条件发生,有多种手段可以达到目的
阻塞式解决方案:synchronized,lock
非阻塞式解决方案:原子变量
synchronized俗称对象锁,它采用互斥方式让同一时刻,只能由一个线程持有对象锁,其他线程想要获取这个锁只能阻塞,这样就可以保证拥有锁的线程可以安全执行临界区代码,不需要担心上下文切换
注意
虽然java中互斥和同步都可以使用synchronized关键字完成,但还是有区别的
- 互斥是保证临界区的竞态条件发生,同一时刻只能有一个线程执行临界区代码
- 同步是由于线程执行的先后,顺序不同,需要一个线程等待其他线程运行到每个点
synchronized实际上是保证了临界区代码的完整性,临界区代码对外是不可以分割的,不会被线程切换所打断
线程同步机制(synchronized)
- 在多线程编程,一些敏感数据不能被多个线程同时访问,此时就使用同步访问技术,在保证数据在任何时刻,最多有一个线程访问,保证数据的完整性
- 线程同步,当有一个线程对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到线程完成操作,其他线程才能对该内存地址进行操作
1 | public class Thread_Ticket { |
FutureTask
futuretask: futuretask里的get方法会造成线程阻塞,工作中不建议使用
改进后的get方法可以在get里面设置超时时间
1 | public class FutureTask_ { |
使用轮询方式来缓解线程阻塞
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public class FutureTask_ {
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
FutureTask<Integer> integerFutureTask = new FutureTask<>(() -> {
System.out.println(Thread.currentThread().getName() + "执行futureTask");
TimeUnit.SECONDS.sleep(5);
return 1024;
});
Thread a = new Thread(integerFutureTask, "a");
a.start();
while (true){
if (integerFutureTask.isDone()){
System.out.println("futuretask完毕");
break;
}else {
TimeUnit.SECONDS.sleep(2);
System.out.println("未执行完成");
}
}
System.out.println("执行主线程");
}
}
线程安全问题
由于分时系统造成的线程切换,导致的线程安全问题
变量的线程安全分析
成员变量和静态变量是否线程安全?
- 如果没有共享,则线程安全
- 如果被共享,根据状态是否被改变,又分为两种情况
- 如果只有读操作,线程安全
- 如果有写操作,这段代码是临界区,需要考虑线程安全
局部变量是否线程安全?
- 局部变量是线程安全的
- 但是局部变量引用的对象未必是安全的
- 如果该对象没有逃离方法的作用访问,他是线程安全的
- 如果该对象逃离方法的作用范围,则需要考虑线程安全
并发编程_模式
两阶段终止模式
错误思路
使用线程对象的stop方法停止线程
- stop方法会真正杀死线程,如果这时线程锁住了共享资源,那么杀死后就再也没有机会释放锁,其他线程将无法获取锁
使用System.exit(int)方法停止线程
- 目的仅是停止一个线程,但是这种方法会将程序终止,虚拟机直接退出
两阶段终止模式
1 | public class TwoPathTermination { |