文章目录
线程基础知识复习
JUC是什么?
java.util.concurrent在并发编程中使用的工具包
为什么学习并用好多线程极其重要?
硬件方面
摩尔定律:
它是由英特尔创始人之一Gordon Moore(戈登·摩尔)提出来的。其内容为:
当价格不变时,集成电路上可容纳的元器件的数目约每隔18-24个月便会增加一倍,性能也将提升一倍。
换言之,每一美元所能买到的电脑性能,将每隔18-24个月翻一倍以上。这一定律揭示了信息技术进步的速度。
可是从2003年开始CPU主频已经不再翻倍,而是采用多核而不是更快的主频。
摩尔定律失效了。。。
在主频不再提高且核数在不断增加的情况下,要想让程序更快就要用到并行或并发编程。
软件方面
面试B格可以高一点点
充分利用多核处理器
提高程序性能,高并发系统
提高程序吞吐量,异步+回调等生产需求
弊端及问题
线程安全性问题,例如:i++是非原子操作、集合类安全否
线程锁问题,例如:synchronized锁,但是是重量级锁
线程性能问题,例如:了解偏向锁、轻量级锁
start线程开启源码分析
package com.fastech.juc;
public class ThreadBaseDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {}, "t1");
t1.start();
}
}
查询start()方法源码
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
// native表示JNI本地接口调用,调的是第三方C语言编写的底层函数,或者是操作系统的底层代码
// JNI代表Java native interface,Java本地接口
private native void start0();
Java语言本身底层就是C++语言,OpenJDK就是指C++源语级别对Java语言的描述,OpenJDK源码网址:https://openjdk.org/
start0()方法具体做了哪些操作,其实就是OpenJDK源码里面的openjdk8\jdk\src\share\native\java\lang\thread.c、openjdk8\hotspot\src\share\vm\prims\jvm.cpp、openjdk8\hotspot\src\share\vm\runtime\thread.cpp三个类
OpenJDK和JNI一般是一一对应的,Thread.java对应就是thread.c,start0其实就是JVM_StartThread。此时查看源代码可以看到在jvm.h中找到了声明,jvm.cpp中有实现
thread.c
jvm.cpp
thread.cpp
如上图,可以看到start0()这个方法实际上是通过jvm配合操作系统分配线程帮助我们启动线程,os::start_thread(thread);代表由操作系统分配原生的基础线程
Java多线程相关概念
1把锁
synchronized(后面细讲)
2个并
- 并发(concurrent):是在同一实体上的多个事件,是在一台机器上“同时”处理多个任务,同一时刻,其实是只有一个事情在发生。
- 例如:抢票、秒杀,这种同一时刻多个请求线程对同一份资源发起集火攻击称为并发
- 并行(parallel):是在不同实体上的多个事件,是在多台处理器上同时处理多个任务,同一时刻,大家都在做事情,你做你的,我做我的,各干各的。
- 例如:泡面,买包泡面回家,一边用热水器烧水,一边撕开调料包泡面,这种同一时刻,大家都在做事情,你做你的,我做我的,各干各的称为并行
如图,两个队列一台咖啡机即并发,两个队列两台咖啡机即并行
3个程
-
进程:简单的说,在系统中运行的一个应用程序就是一个进程,每个进程都有它自己的内存空间和系统资源
-
线程:也被称为轻量级进程,在同一个进程内会有1个或多个线程,是大多数操作系统进行时序调度的基本单元。
-
管程:
- Monitor(监视器),也就是我们平时所说的锁。
-
Monitor其实是一种同步机制,它的义务是保证(同一时间)只有一个线程可以访问被保护的数据和代码。
-
JVM中同步是基于进入和退出监视器(Monitor管程对象)来实现的,每个对象实例都会有一个Monitor对象。即每个对象都可以作为Monitor(监视器)、都可以作为管程、都可以作为锁
-
Monitor对象和Java对象一同创建并销毁,底层由C++语言实现。
Object o = new Object();
new Thread(() -> {
synchronized (o) // 此时o这个对象作为Monitor(监视器),即锁
{
}
}, "t2").start();
ps:管程的概念来自于书籍——JVM第3版
用户守护线程理论
线程分类
Java线程分为用户线程和守护线程,一般不做特别说明配置,默认都是用户线程
- 用户线程(User Thread):是系统的工作线程,它会完成这个程序需要完成的业务操作。
- 守护线程(Daemon Thread):
- 是一种特殊的线程为其他线程服务的,在后台默默地完成一些系统性的任务,比如垃圾回收线程就是最典型的例子。
- 守护线程作为一个服务线程,没有服务对象就没有必要继续运行了,如果用户线程全部结束了,意味着程序需要完成的业务操作已经结束了,系统可以退出了。所以假如当系统只剩下守护线程的时候,守护线程伴随着JVM一同结束工作。
线程的daemon属性
源码解读
/**
* 返回值:true表示守护线程;false表示用户线程
*/
public final boolean isDaemon() {
return daemon;
}
/**
* 将此线程标记为守护线程或用户线程,当所有运行的线程都是守护线程时,Java虚拟机退出
* 参数:true表示将此线程标记为守护线程;false表示将此线程标记为用户线程
*/
public final void setDaemon(boolean on) {
checkAccess();
if (isAlive()) {
throw new IllegalThreadStateException();
}
daemon = on;
}
代码演示
package com.fastech.juc;
import java.util.concurrent.TimeUnit;
public class DaemonDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 开始运行," +
(Thread.currentThread().isDaemon() ? "守护线程" : "用户线程"));
while (true) {
}
}, "t1");
t1.start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t ---end 主线程");
}
}
输出结果:
t1 开始运行,用户线程
main ---end 主线程
... // 程序未停止
结论
1、一般不做特别说明配置,默认都是用户线程
2、main线程结束不代表t1线程结束,这两个线程都是用户线程,相互独立
代码修改1
package com.fastech.juc;
import java.util.concurrent.TimeUnit;
public class DaemonDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 开始运行," +
(Thread.currentThread().isDaemon() ? "守护线程" : "用户线程"));
while (true) {
}
}, "t1");
t1.setDaemon(true);
t1.start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t ---end 主线程");
}
}
输出结果:
t1 开始运行,守护线程
main ---end 主线程
结论
1、t1作为守护线程默默守护着用户线程,用户线程结束守护线程也就结束了
代码修改2
package com.fastech.juc;
import java.util.concurrent.TimeUnit;
public class DaemonDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 开始运行," +
(Thread.currentThread().isDaemon() ? "守护线程" : "用户线程"));
while (true) {
}
}, "t1");
t1.start();
t1.setDaemon(true);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t ---end 主线程");
}
}
输出结果:
t1 开始运行,用户线程
Exception in thread "main" java.lang.IllegalThreadStateException
at java.lang.Thread.setDaemon(Thread.java:1359)
at com.fastech.juc.DaemonDemo.main(DaemonDemo.java:24)
结论
1、setDaemon(true)方法必须在start()之前设置,否则报IllegalThreadStateException异常
理由
因为你不能把正在运行的常规线程设置为守护线程
小总结
如果用户线程全部结束意味着程序需要完成的业务操作已经结束了,守护线程随着JVM一同结束工作