1. 概念
1.1 线程与进程
- 进程:系统进行资源分配和调度的基本单位,可以看作一个程序的实体,表示一个执行单元;一般包含多个线程;多个进程拥有互相独立的内存单元,数据是独立不共享的
- 线程:系统能够进行运算调度的最小单位;线程不具备任何的系统资源,它在同一个进程里与其他线程共享全部资源
1.2 并发与并行
- 并发:指两个或多个事件在同一时段内发生,即
交替做不同事的能力,多线程是并发的一种形式。例如垃圾回收时,用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个CPU上
- 并行:同一时刻多个程序在运行(
多个或多核CPU才存在并行),多个任务间不会互相抢占资源
1.3 线程的生命周期
- 初始状态(NEW):线程被创建,但是还没有调用 start() 方法
- 运行状态(RUNNABLE):将操作系统的就绪和运行状态称为 - 运行中
- 阻塞状态(BLOCKED):线程阻塞于锁
- 等待状态(WAITING):当前线程需要等待其他线程作出一些特定动作(通知或中断)
- 超时等待(TIME_WAITING):指定时间自行返回
- 终止状态(TERMINATED):线程已经执行完毕
2. 多线程的创建方式
2.1 继承Thread类
static class MyThread extends Thread {
@Override
public void run() {
super.run();
System.out.println("继承Thread类的方式创建线程:" + Thread.currentThread().getName());
}
}
public static void main(String[] args) {
System.out.println("当前线程:" + Thread.currentThread().getName());
MyThread myThread = new MyThread();
myThread.start();
}
运行结果:
当前线程:main
继承Thread类的方式创建线程:Thread-0
2.2 实现Runable接口
static class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("实现Runnable接口的方式创建线程:" + Thread.currentThread().getName());
}
}
public static void main(String[] args) {
System.out.println("当前线程:" + Thread.currentThread().getName());
MyRunnable myRunnable = new MyRunnable();
new Thread(myRunnable).start();
}
运行结果:
当前线程:main
继承Thread类的方式创建线程:Thread-0
2.3 实现Callable接口
static class MyCallable implements Callable<String> {
@Override
public String call() {
System.out.println("实现Callable接口的方式创建线程:" + Thread.currentThread().getName());
return "hello,Callable";
}
}
public static void main(String[] args) {
System.out.println("当前线程:" + Thread.currentThread().getName());
MyCallable myCallable = new MyCallable();
FutureTask<String> futureTask = new FutureTask<String>(myCallable) {
@Override
protected void done() {
System.out.println("线程执行前");
super.done();
System.out.println("线程执行后");
try {
System.out.println("done获取Call方法的返回值:" + get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
};
new Thread(futureTask).start();
try {
System.out.println("外部获取Call方法的返回值"+futureTask.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
当前线程:main
实现Callable接口的方式创建线程:Thread-0
线程执行前
外部获取Call方法的返回值hello,Callable
线程执行后
done获取Call方法的返回值:hello,Callable
2.4 三种方式比较
- 继承Thread需要手动重写run方法,启动直接使用该对象调用start方法即可
- 实现Runnable也是重写run方法,启动时需要将该对象当参数传入Thread构造,再使用该Thread对象调用start方法启动
- 实现Callable<*>方式需要重写call方法,可以获取call方法的返回值,启动时需要借助FutureTask对象,将Callable实现类对象作为参数传入FutureTask构造器,最终将该FutureTask对象传入Thread对象,调用Thread对象的start方法即可启动
2.5 Thread.start与Thread.run的区别
- Thread.start会启动线程并调用Thread.run方法,而Thread.run只会回调run方法
3. 线程中断
- 当开启的线程运行的代码没有无限循环代码时,代码运行结束线程即可自动终止
- 假如是无限循环时,常见的中断方式有
Thread.Interrupt配合isInterrupted和设置停止的标识符(Thread.stop()不安全,可能导致线程持有的锁突然释放而不受控制,已被废弃)
3.1 Thread.Interrupt配合isInterrupted
- 线程运行后,调用Thread.Interrupt()中断线程,while的循环条件必须判断当前线程是否被中断
public class StopThread {
private static int mCount = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyRunnable());
thread.start();
Thread.sleep(3000);
thread.interrupt();
}
static class MyRunnable implements Runnable {
@Override
public void run() {
while (Thread.currentThread().isInterrupted()) {
mCount++;
System.out.println("当前count值:" + mCount);
}
System.out.println("线程执行已结束");
}
}
}
- 注意:当循环的过程中添加了
Thread.sleep时,由于sleep方法会捕获中断信号,抛出InterruptedException,并且将中断信号改为false,最终导致中断失败,所以需在catch中再次请求中断即可
static class MyRunnable implements Runnable {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
mCount++;
System.out.println("当前count值:" + mCount);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
System.out.println("线程执行已结束");
}
}
3.2 修改标志位状态退出
- 添加全局标志位变量,设置为循环条件,修改该标志位即可完成中断
public class StopThread {
private static int mCount = 0;
private static boolean mRecycler = true;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyRunnable());
thread.start();
Thread.sleep(3000);
mRecycler = false;
}
static class MyRunnable implements Runnable {
@Override
public void run() {
while (mRecycler) {
mCount++;
System.out.println("当前count值:" + mCount);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程执行已结束");
}
}
}
运行结果:
当前count值:1
当前count值:2
当前count值:3
当前count值:4
当前count值:5
当前count值:6
线程执行已结束
4. 线程串行Thread.join()
- 多线程执行的先后顺序是由CPU调度决定的,所以每次运行可能先后顺序都不同
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "执行开始");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行结束");
}, "线程1");
Thread thread2 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "执行开始");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行结束");
}, "线程2");
Thread thread3 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "执行开始");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行结束");
}, "线程3");
thread1.start();
thread2.start();
thread3.start();
}
- 开启后调用Thread.join(),会将线程串行化依次执行,
必须等上一个线程执行完毕才会执行,类似同步执行的效果
...略...
thread1.start();
thread1.join();
thread2.start();
thread2.join();
thread3.start();
运行结果:
线程1执行开始
线程1执行结束
线程2执行开始
线程2执行结束
线程3执行开始
线程3执行结束
5. 线程礼让Thread.yield()
- 当调用
Thread.yield()方法时,会给线程调度器一个当前线程愿意让出CPU使用的暗示,但是线程调度器可能会忽略这个暗示,也就是说:最终抢占到CPU的还是取决于线程调度器,只是其他线程抢到CPU的概率更大
public static void main(String[] args) {
Runnable runnable = () -> {
for (int i = 0
System.out.println(Thread.currentThread().getName() + "执行到:" + i)
if (i == 2) {
Thread.yield()
}
}
}
Thread thread1 = new Thread(runnable,"线程1")
Thread thread2 = new Thread(runnable,"线程2")
Thread thread3 = new Thread(runnable,"线程3")
thread1.start()
thread2.start()
thread3.start()
}
执行结果:
线程1执行到:0
线程2执行到:0
线程1执行到:1
线程1执行到:2
线程3执行到:0
线程3执行到:1
线程3执行到:2
线程2执行到:1
线程2执行到:2
线程3执行到:3
线程2执行到:3
线程1执行到:3
- 实测多次运行虽然每次都不一样,但基本一个线程执行到2时就换到了另一个线程,而不用Thread.yield()时则线程完全没有在执行到2时切换到另一个线程的现象