本文将会介绍Java多线程中的重点知识,本文内容参考了网上的资料整理,主要为了自己看着方便,方便查找。
主要来源有:
- Guide哥
- 小林Coding
- 菜鸟教程
在Java中线程是以轻量的进程的方式实现的。
其他语言的实现方式可能不同,例如现在很火的Go语言,就是以协程的方式进行实现的。
进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。
在 Java 中,当我们启动 main 函数时其实就是启动了一个 JVM 的进程,而 main 函数所在的线程就是这个进程中的一个线程,也称主线程。
线程与进程相似,一个进程中的执行过程中会产生多个线程,线程是一个比进程更小的执行单位。
同类的多个线程共享进程的堆和方法区资源,每个线程有自己独立的程序计数器、虚拟机栈和本地栈。
系统中产生一个线程或者在多个线程之间切换工作时,消耗要比进程小的多,所以线程也被叫做轻量级进程。
程序计数器为什么是私有的?
程序计数器的私有主要是为了线程切换后能恢复到正确的执行位置。
虚拟机栈和本地方法栈为什么是私有的?
为了保证线程中的局部变量不被别的线程访问到,虚拟机栈和本地方法栈是线程私有的。
堆和方法区是所有线程共享的资源
Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态(图源《Java 并发编程艺术》4.1.4 节)
线程的的状态不是固定的,而是随着代码的执行在不同的状态之间切换。Java 线程状态变迁如下图所示(图源《Java 并发编程艺术》4.1.4 节):
进程的状态与线程的生命周期大同小异。
在一个进程活动期间,至少具备三种状态:运行状态、就绪状态以及阻塞状态。
上图中各状态的的意义:
状态转换 | 转换原因 |
---|---|
NULL -> 创建状态 | 一个新进程被创建时的第一个状态 |
创建状态 -> 就绪状态 | 当进程被创建完成并初始化后,一切就绪准备运行时,变为就绪状态,这个过程是很快的 |
就绪态 -> 运行状态 | 处于就绪状态的进程被操作系统的进程调度器选中后,就分配给 CPU 正式运行该进程 |
运行状态 -> 结束状态 | 当进程已经运行完成或出错时,会被操作系统作结束状态处理 |
运行状态 -> 就绪状态 | 处于运行状态的进程在运行过程中,由于分配给它的运行时间片用完,操作系统会把该进程变为就绪态,接着从就绪态选中另外一个进程运行 |
运行状态 -> 阻塞状态 | 当进程请求某个事件且必须等待时,例如请求 I/O 事件 |
阻塞状态 -> 就绪状态 | 当进程要等待的事件完成时,它从阻塞状态变到就绪状态 |
线程在执行过程中会有自己的运行条件和状态(也称上下文),比如上文所说到过的程序计数器,栈信息等。当出现如下情况的时候,线程会从占用 CPU 状态中退出。例如:
线程的创建在Java中有三种方式
class MyThread extends Thread{
@Override
public void run() {
System.out.println("my thread run");
}
}
Thread t = new MyThread();
t.start();
也可以使用匿名内部类的方式进行实现
Thread t2 = new Thread(){
@Override
public void run() {
System.out.println("匿名内部类的 run");
}
};
整体代码:
public class CreateThread1 {
public static void main(String[] args) {
Thread t = new MyThread();
t.start();
//匿名内部类重写run方法,本质还是继承Thread
Thread t2 = new Thread(){
@Override
public void run() {
System.out.println("匿名内部类的 run");
}
};
t2.start();
}
//继承Thread类重写run方法
private static class MyThread extends Thread{
@Override
public void run() {
System.out.println("my thread run");
}
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("my Runnable run");
}
}
Thread t = new Thread(new MyRunnable());
t.start();
同样可以使用匿名内部类的方式进行实现,也可使用lambda方法实现
//以匿名内部类的方式实现Runnable接口
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类 Runnable");
}
});
//使用lambda的方法实现
Thread t3 = new Thread(()->{
System.out.println("lambda方式实现");
});
整体代码:
public class CreateThread2 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
//以匿名内部类的方式实现Runnable接口
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类 Runnable");
}
});
t2.run();
//使用lambda的方法实现
Thread t3 = new Thread(()->{
System.out.println("lambda方式实现");
});
t3.run();
}
//实现Runnable接口实现线程
private static class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("my Runnable run");
}
}
}
Callable 是一个 interface。相当于把线程封装了一个 “返回值”。方便开发人员借助多线程的方式计算结果。
Callable 和 Runnable 相对, 都是描述一个 “任务”。Callable 描述的是带有返回值的任务, Runnable描述的是不带返回值的任务。
Callable 通常需要搭配 FutureTask 来使用。FutureTask 用来保存 Callable 的返回结果。因为Callable 往往是在另一个线程中执行的, 啥时候执行完并不确定。
FutureTask就负责这个等待结果出来的工作。
使用Callable可以简化代码,不需要再去写线程同步的代码了。
实现Callable接口创建线程:
public class CreateThread3 {
static int i = 0;
public static void main(String[] args) throws ExecutionException, InterruptedException {
//先实现callable
Callable<Integer> r = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
try {
Thread.sleep(1000);
//将值返回出去
return 1;
} catch (InterruptedException e) {
throw new RuntimeException("出错了");
}
}
};
//再创建futuretask
FutureTask<Integer> task = new FutureTask<>(r);
//通过futuretask获取到线程中的值
Thread t = new Thread(task);
t.start();
System.out.println(task.get());
}
}
例如:创建线程计算 1 + 2 + 3 + … + 1000
static class Result {
public int sum = 0;
public Object lock = new Object();
}
public static void main(String[] args) throws InterruptedException {
Result result = new Result();
Thread t = new Thread() {
@Override
public void run() {
int sum = 0;
for (int i = 1; i <= 1000; i++) {
sum += i;
}
synchronized (result.lock) {
result.sum = sum;
result.lock.notify();
}
}
};
t.start();
synchronized (result.lock) {
while (result.sum == 0) {
result.lock.wait();
}
System.out.println(result.sum);
}
}
futureTask.get()
能够阻塞等待新线程计算完毕. 并获取到 FutureTask 中的结果。Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 1000; i++) {
sum += i;
}
return sum;
}
};
FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread t = new Thread(futureTask);
t.start();
int result = futureTask.get();
System.out.println(result);
调用 start() 方法方可启动线程并使线程进入就绪状态,直接执行 run() 方法的话不会以多线程的方式执行。
详细: new 一个 Thread,线程进入了新建状态。调用 start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。 但是,直接执行 run() 方法,会把 run() 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。
方法 | 说明 |
---|---|
Thread() |
创建线程对象 |
Thread(Runnable target) |
使用 Runnable 对象创建线程对象 |
Thread(String name) |
创建线程对象,并命名 |
Thread(Runnable target, String name) |
使用 Runnable 对象创建线程对象,并命名 |
Thread(ThreadGroup group, Runnable target) |
线程可以被用来分组管理,分好的组即为线程组 |
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("Thread name");
Thread t4 = new Thread(new MyRunnable(), "Thread name");
属性 | 获取方法 |
---|---|
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否为后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
代码示例:
public class ThreadDemo {
public static void main(String[] args) {
//线程各种方法的使用
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
System.out.println(Thread.currentThread().getName()+":alive");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+":die");
}
},"线程1");
System.out.println(Thread.currentThread().getName()+"ID是:"+thread.getId());
System.out.println(Thread.currentThread().getName()+"名字是:"+thread.getName());
System.out.println(Thread.currentThread().getName()+"状态:"+thread.getState());
System.out.println(Thread.currentThread().getName()+"优先级:"+thread.getPriority());
System.out.println(Thread.currentThread().getName()+"后台线程:"+thread.isDaemon());
System.out.println(Thread.currentThread().getName()+"是否存活:"+thread.isAlive());
System.out.println(Thread.currentThread().getName()+"是否被中断:"+thread.isInterrupted());
thread.start();
while(thread.isAlive()){}
System.out.println(Thread.currentThread().getName()+"的状态:"+thread.getState());
}
}
只有调用了start方法,才是真正在操作系统底层创建出一个线程。
两种方式来中断线程
Thread.interrupted()
或者 Thread.currentThread().isInterrupted()
代替自定义标志位。方法 | 说明 |
---|---|
public void interrupt() |
中断对象关联的线程,如果线程正在阻塞,则以异常方式通知,否则设置标志位 |
public static boolean interrupted() |
判断当前线程的中断标志位是否设置,调用后清除标志位 |
public boolean isInterrupted() |
判断对象关联的线程的标志位是否设置,调用后不清除标志位 |
thread 收到通知的方式有两种:
示例代码:
public class ThreadInterrupt {
//线程中断
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
while (!Thread.currentThread().isInterrupted()){
Thread.sleep(100000);
System.out.println(Thread.currentThread().getName()+"run");
}
} catch (InterruptedException e) {
//是否中断,由线程决定
e.printStackTrace();
}
}
});
t.start();
Thread.sleep(10000);
t.interrupt();
}
}
方法 | 说明 |
---|---|
public void join() |
等待线程结束 |
public void join(long millis) |
等待线程结束,最多等 millis 毫秒 |
public void join(long millis, int nanos) |
与上相同,但可以有更高的精度 |
示例代码:
package Thread_api;
public class Join {
// 当前线程加入t线程,当前线程在此处等待
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName()+" run:"+i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
// t.join();
System.out.println(Thread.currentThread().getName()+"run");
}
}
使用public static Thread currentThread();
方法获取,它的作用是返回当前线程对象的引用。
方法 | 说明 |
---|---|
public static void sleep(long millis) throws InterruptedException |
休眠当前线程 millis 毫 秒 |
`public static void sleep(long millis, int nanos) throws | |
InterruptedException` | 可以更高精度的休眠 |