目录
1、Java多线程概念
1.1 并发和并行
1.1.1 并发
1.1.2 并行
1.2 进程&线程&管程
1.2.1 进程
1.2.2 线程
1.2.3 管程
2、用户线程&守护线程
2.1 用户线程
2.2 守护线程
3、线程.start() 解读
4、实现多线程的三种方式(这里只说普通方式)
4.1 继承Thread类,重写run方法
4.2 实现Runnable接口,重写run方法
4.3 实现Callable接口,重写call方法
1、Java多线程概念
1.1 并发和并行
1.1.1 并发
1.1.1 并发
是在同一实体上的多个事件,是在同一台处理器上“同时”处理多个任务,同一时刻,其实是只有一个事件在发生。用生活的例子表达:我们泡面,先做水,然后撕泡面的调料,然后用水泡面。这些事(做水、撕调料包、泡面)其实都是一个人干的。
1.1.2 并行
是在不同实体上的多个事件,是在多台处理器上处理多个任务,同一时刻,大家都真的在做事情,你做你的,我做我的。比如一个货运站,两个司机,将货物送往不同的目的地,他们之间没有任务直接关系。
1.2 进程&线程&管程
1.2.1 进程
系统中运行一个应用程序就是一个进程,每一个进程都有它自己的内存空间和系统资源。
1.2.2 线程
轻量级进程,在同一个进程内基本会有一个或多个线程,是大多数操作系统进行调度的基本单元。
1.2.3 管程
Monitor(监视器),也就是我们平常说的锁。
Monitor其实是一种同步机制,他的义务是保证(同一时间)只有一个线程可以访问被保护的数据和代码。
JVM中同步时基于进入和退出监视器对象来实现的,每个对象的对象头中标记。
注:Monitor后续会着重去研究,到时候会将连接放到这里。(2022/070/25)
2、用户线程&守护线程
Java线程分为用户线程和守护线程
2.1 用户线程
是系统的工作线程,它会完成这个程序需要完成的业务操作。如果用户线程不完成,则程序会一直执行下去。(注意:这里一定要用main方法入口,不要用@Test)
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"t 开始运行,"+(Thread.currentThread().isDaemon() ? "守护线程":"用户线程"));
for(;;){
}
},"自定义线程");
// 默认是 false
t.setDaemon(false);
t.start();
System.out.println("----------main线程运行完毕");
}
这里可以看出程序无法退出,因为自定义线程是用户线程,内部是死循环,一直处于运行状态,无法结束。
2.2 守护线程
是一种特殊的线程,为其他线程服务的,在后台默默完成一些系统性的服务,比如垃圾回收线程。当系统只身下守护线程的时候,java虚拟机会自动退出。
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"t 开始运行,"+(Thread.currentThread().isDaemon() ? "守护线程":"用户线程"));
for(;;){
}
},"自定义线程");
// 默认是 false
t.setDaemon(true);
t.start();
System.out.println("----------main线程运行完毕");
}
- 守护线程作为一个服务线程,没有服务对象(用户线程)就没有必要继续运行了,如果用户线程全部结束了,意味着程序可以已经完成业务操作,系统可以退出。系统只剩下守护线程时,Java虚拟机会自动退出。
- SetDaemon(true)方法必须在start()之前设置,否则报IlleaglThreadStateException异常。
3、线程.start() 解读
public static void main(String[] args) {
Thread t1 = new Thread(() ->{
},"t1");
t1.start();
}
.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) {
}
}
}
// start0是一个native方法 private native void start0();
我们可以看到start0方法被native修饰着。(native关键字告诉编译器-JVM调用该方法在外部定义,这里指的是C),我们可以通过查看OpenJDK查看源码:OpenJDK Mercurial Repositories
Thread.c源码位置
我们看到 start0 其实就是JVM_StartThread。
jvm.cpp源码位置
jvm.cpp位置
jvm.cpp源码
thread.cpp源码位置
这里可以看到动用了操作系统的线程启动,os::start_thread(thread)
4、实现多线程的三种方式(这里只说普通方式)
- 继承Thread类,重写run方法,无返回值;
- 实现Runnable接口,重写run方法,无返回值;
- 实现Callable接口,重写call方法,有返回值;
4.1 继承Thread类,重写run方法
public class ExtendsThreadTest extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 执行");
}
public static void main(String[] args) {
Thread t1 = new ExtendsThreadTest();
Thread t2 = new ExtendsThreadTest();
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
4.2 实现Runnable接口,重写run方法
public class ExtendsThreadTest extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 执行");
}
public static void main(String[] args) {
Thread t1 = new ExtendsThreadTest();
Thread t2 = new ExtendsThreadTest();
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
4.2 实现Runnable接口,重写run方法
这样可以使用Runnable实现类中的成员变量
public class RunnableThreadTest implements Runnable {
private Integer threadNum = 0;
@Override
public void run() {
threadNum++;
System.out.println(Thread.currentThread().getName() + " 执行,当前有" + threadNum + "线程已执行过了");
}
@SneakyThrows
public static void main(String[] args) {
RunnableThreadTest runnableThread = new RunnableThreadTest();
Thread t1 = new Thread(runnableThread);
t1.setName("线程1");
Thread t2 = new Thread(runnableThread);
t2.setName("线程2");
t1.start();
// 用来放慢线程执行速度
Thread.sleep(1000);
t2.start();
}
}



