-
Java高并发5-守护线程、ThreadLocal和死锁四个必要条件
一、复习
-
void interrupt()、boolean isInterrupted()、boolean interrupted() -
interrupt()方法只是为了做一个“中断”的标记,而不会真的停止该线程、isInterrupted是返回线程的中断状态、interrupted()方法也是一样的,但是这是一个static方法,可以直接调用而且根据源码,是所在的线程的中断状态,而且如果结果为true,它会立即消除“中断”状态。
二、上下文切换
-
定义:CPU会给各个线程分配时间片,当一个线程的时间片使用结束后,它会处于就绪状态,此时CPU会切换到另外一个线程,这就是上下文切换。 -
上下文切换的时机有两个:(1)线程使用完时间片完全处于就绪状态时;(2)线程被中断;
三、死锁
-
定义:两个或者两个以上的线程,当他们进行争夺资源用于执行的时候,形成一种互相等待的局面,就是死锁。
1.死锁产生的四个必要条件:
-
(1)互斥条件:线程对已经获得的资源进行排他性使用,也就是资源被占用,只能由一个线程占用,如果其他线程想要获得该资源,就必须等待,除非该线程释放了资源。 -
(2)请求并持有条件:一个线程占有了资源,并且还想要其他资源,但是此时其他资源被其他线程占用了,那么此时在等待资源释放,并且并不会释放自己已经持有的资源 -
(3)不可剥夺条件:一个线程占用了资源,直到它使用完才会释放,在此期间如果由其他线程想要该资源,是不能抢占的 -
(4)环路等待条件,也就是有一个线程集合{t1,t2,t3,......tn},t1想要t2的资源,t2想要t3的资源......直到tn想要t1的资源,形成一个环路 -
举个死锁的例子
package com.ruigege.threadFoundation1;
public class DeadLockExample {
//创建资源
private static Object object1 = new Object();
private static Object object2 = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized(object1) {
System.out.println("线程1获得了资源1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized(object2) {
System.out.println("线程1获得了资源2");
}
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized(object2) {
System.out.println("线程2获得了资源2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized(object1) {
System.out.println("线程2获得了资源1");
}
}
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
}
}
-
两者互相等待上了,死锁的四个条件都满足了,形成了死锁
2.如何避免死锁
-
至少需要破坏一个必要条件,学过操作系统的小伙伴知道,目前只用请求并持有条件和环路等待条件是可以破坏的。上面的代码稍微改动,把第二个线程的object1和object2,位置互换,也就是和线程1的顺序一致。运行:
四、守护线程
-
在Java中有两种线程一种是用户线程,一种是守护线程。我们使用的main函数就是一个用户线程,垃圾回收器就是一个守护线程。守护线程的描述就是JVM的退出并不取决于守护线程是否还在运行,也就是说,当用户线程结束的时候,退出JVM,此时是否有守护线程都无所谓,依然会退出。
1.创建一个守护线程
线程.setDaemon(true);
线程.start();
-
调用setDaemon方法即可。我们来测试一下,上面描述的特性是否成立
package com.ruigege.threadFoundation1;
public class DaemonThreadTest {
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
while(true) {
//我们这里搞一个死循环也就是让子线程不停
}
}
});
thread1.setDaemon(true);
thread1.start();
System.out.println("主线程已经结束了,看一看后面还有没有光标在动");
}
}
-
第一个没设置为守护线程,可以看到虽然主线程已经结束了,但是子线程没有结束,程序仍然在运行;第二个就是设置了,但是主线程一结束,守护线程也结束了。
linux命令 ps -ef | grep java 这个Linux命令代表什么 ps代表的是显示某个进程;grep全称为global regular expression print代表是查找的意思,它的后面可以接正则表达式,这个grep java就是代表查找出所有带有java的进程然后显示出来; |是管道符合,这里代表的意思就是前后两个命令同时执行; ps后面跟一些参数,-e代表所有的进程;-f代表全称线程。还有其他参数,百度了一下。-h : 不显示标题;-l : 长格式-w : 宽输出;a :显示终端上的所有进程,包括其他用户的进程;r :只显示正在运行的进程;u :以用户为主的格式来显示程序状况;x :显示所有程序,不以终端机来区分。
-
总结:如果你想要主线程结束之后马上退出JVM,那么就可以把子线程设置为守护线程。反之,就把子线程设置为用户线程。
五、ThreadLocal解析
-
多线程访问同一个共享对象的时候,往往会有并发问题,那么我们可以通过加锁的方式来保证对象的内容统一,但是这种效率很低,于是Java的JDK提供了一个类ThreadLocal,该共享变量的工作原理是:一旦创建了一个共享变量之后,它会在每个线程中创建一个副本,用于本线程使用,实际操作的是自己线程独有的副本,保证线程安全,不会和其他线程混淆 -
我们接下来举个例子:
package com.ruigege.threadFoundation1;
public class ThreadLocalTest {
public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void printContentOfThreadLocal(ThreadLocal threadLocal) {
System.out.println(threadLocal.get());
threadLocal.remove();
}
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
threadLocal.set("共享变量1");
System.out.println("线程1的共享变量是:");
printContentOfThreadLocal(threadLocal);
System.out.println("线程1去除共享变量后的值为:" + threadLocal.get());
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
threadLocal.set("共享变量2");
System.out.println("线程2的共享变量是:");
printContentOfThreadLocal(threadLocal);
System.out.println("线程2去除共享变量后的值为:" + threadLocal.get());
}
});
thread1.start();
try {
Thread.sleep(100);
}catch(InterruptedException e) {
e.printStackTrace();
}
thread2.start();
}
}
-
第一个是把语句threadLocal.remove注释掉的结果,第二个是没有注释的结果。
六、源码:
-
所在包:com.ruigege.ThreadFoundation1 -
https://github.com/ruigege66/ConcurrentJava
-
CSDN:https://blog.csdn.net/weixin_44630050 -
博客园:https://www.cnblogs.com/ruigege0000/
出 处:https://www.cnblogs.com/ruigege0000/p/13934755.html
最新更新
python爬虫及其可视化
使用python爬取豆瓣电影短评评论内容
nodejs爬虫
Python正则表达式完全指南
爬取豆瓣Top250图书数据
shp 地图文件批量添加字段
爬虫小试牛刀(爬取学校通知公告)
【python基础】函数-初识函数
【python基础】函数-返回值
HTTP请求:requests模块基础使用必知必会
SQL SERVER中递归
2个场景实例讲解GaussDB(DWS)基表统计信息估
常用的 SQL Server 关键字及其含义
动手分析SQL Server中的事务中使用的锁
openGauss内核分析:SQL by pass & 经典执行
一招教你如何高效批量导入与更新数据
天天写SQL,这些神奇的特性你知道吗?
openGauss内核分析:执行计划生成
[IM002]Navicat ODBC驱动器管理器 未发现数据
初入Sql Server 之 存储过程的简单使用
uniapp/H5 获取手机桌面壁纸 (静态壁纸)
[前端] DNS解析与优化
为什么在js中需要添加addEventListener()?
JS模块化系统
js通过Object.defineProperty() 定义和控制对象
这是目前我见过最好的跨域解决方案!
减少回流与重绘
减少回流与重绘
如何使用KrpanoToolJS在浏览器切图
performance.now() 与 Date.now() 对比