Java线程的实现方式
使用Thread类或继承Thread类
实现Runnable接口配合Thread
使用有返回值的Callable
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<Object> future = executorService.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
//TODO 线程业务代码
return null;
}
});
使用lambda表达式
new Thread(() -> System.out.println(Thread.currentThread().getName())).start();
本质上Java中实现线程只有一种方式,都是通过new Thread()创建线程,调用Thread#start启动线程最终都会调用Thread#run方法。
线程创建和启动流程
1)使用new Thread()创建一个线程,然后调用start()方法进行java层面的线程启动。
2)调用本地方法start0(),去调用JVM_StartThread方法进行线程创建和启动。
3)调用new JavaThread(&thread_entry,sz)进行线程的创建,并根据不同的操作系统平台调用对应的os::create_thread方法进行线程创建。
4)创建的线程状态为Initialized,调用sync->wait()的方法进行等待,等到被唤醒才继续执行thread ->run()。
5)调用Thread::start(native_thread)方法进行线程启动,此时将线程状态设置为RUNNABLE,接着调用os::start_thread(thread),根据不同的操作系统选择不同的线程启动方式。
6)线程启动之后的状态设置为RUNNABLE,并唤醒第4步种等待的线程,接着执行thread->run()方法。
7) JavaThread::run()方法会回调第1步new thread中复写的run()方法。
Java线程属于内核
内核级线程(Kernel Level Thread ,KLT):它们是依赖于内核的,即无论是用户进程中的线程,还是系统进程中的线程,它们的创建、撤销、切换都是由内核实现。
用户级线程(User Level Thread,ULT):操作系统内核不知道应用线程的存在。
Java线程的生命周期
Java语言中,线程共有6种状态:
NEW:初始化状态
RUNNABLE:可运行状态+运行状态
BLOCKED:阻塞状态
WAITING:无时限等待状态
TIMED_WAITING:有时限等待状态
TERMINATED:终止状态
在操作系统层面,Java线程中的BLOCKED、WAITING、TIMED_WAITING是一种状态(休眠状态),只要Java线程处于这三种状态之一,那么这个线程就永远没有CPU的使用权。
Java线程的调度机制
线程的调度机制是操作系统为线程分配处理器(CPU)使用权的过程,主要调度方式分为两种,分别是协同式线程调度和抢占式线程调度。
协同式线程调度:线程执行时间由线程本身来控制,线程把自己的工作执行完成之后,要主动的通知操作系统切换到另一个线程上。
抢占式线程调度:每个线程将由操作系统来分配执行时间,线程的切换不由线程本身来决定(Java中,Thread.yield()可以让出执行时间,但无法获取执行时间)。
Java线程的调度机制就是抢占式线程调度。Java语言一共10个级别的线程优先级(Thread.MIN_PRIORITY至Thread.MAX_PRIORITY),在两线程同时处于ready状态时,优先级越高的线程越容易被系统选择执行。
优先级并不是很靠谱,因为Java线程是通过映射到系统的原生线程上来实现的,所以线程调度最终还是取决于操作系统。
package com.warrior.juc.threadbase;
/**
* @Author warrior
* 模拟抢票案例,Java线程的抢占式调度方式
*/
public class SellTicketDemo implements Runnable {
//票数
private int ticket;
public SellTicketDemo(int ticket) {
this.ticket = ticket;
}
@Override
public void run() {
while (ticket > 0) {
synchronized (this) {
if (ticket > 0) {
//模拟抢票操作,耗时2毫秒
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":正在执行操作,余票:" + ticket--);
}
}
//让出线程执行时间
Thread.yield();
}
}
public static void main(String[] args) {
SellTicketDemo sellTicketDemo = new SellTicketDemo(1000);
Thread thread1 = new Thread(sellTicketDemo,"thread1");
Thread thread2 = new Thread(sellTicketDemo,"thread2");
Thread thread3 = new Thread(sellTicketDemo,"thread3");
Thread thread4 = new Thread(sellTicketDemo,"thread4");
//设置线程优先级,默认是5,最低1,最好10
thread1.setPriority(Thread.MAX_PRIORITY);
thread2.setPriority(Thread.MAX_PRIORITY);
thread3.setPriority(Thread.MIN_PRIORITY);
thread4.setPriority(Thread.MIN_PRIORITY);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
Thread常用方法
sleep方法
调用sleep方法会让当前线程从Running状态进入到TIMED_WAITING状态,不会释放对象锁。
其他线程可以使用interrupt方法打断正在睡眠的线程,这时sleep方法会抛出InterruptException,并且会清除中断标志。
睡眠结束后的线程未必会立刻得到执行。
sleep当传入的参数为0时,和yield相同。
yield方法
yield会释放CPU资源,让当前线程从Running状态进入Runnable状态,让优先级更高(至少是相同)的线程获得执行的机会,不会释放对象锁。
假设当前进程只有main线程,当调用yield之后,main线程会继续运行,因为没有比它优先级更高的线程。
具体的实现依赖于操作系统的任务调度器。
join方法
等待调用join方法的线程结束之后,程序再继续执行,一般用于等待异步线程执行完成结果之后才能继续运行的场景。
package com.warrior.juc.threadbase;
/**
* @Author warrior
* 线程join调用案例
*/
public class ThreadJoinDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread begin...");
try {
//模拟业务操作,耗时5s
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread finish ...");
}
});
long start = System.currentTimeMillis();
//启动线程
thread.start();
//主线程等待子线程执行完成
thread.join();
System.out.println("执行时间:" + (System.currentTimeMillis() - start));
System.out.println("main finished");
}
}
stop方法
stop()方法已经被jdk废弃,原因就是stop()方法太过于暴力,强行把执行到一半的线程终止,stop会释放对象锁,可能会造成数据不一致。
package com.warrior.juc.threadbase;
/**
* @Author warrior
* 线程调用stop方法案例
*/
public class ThreadStopDemo {
private static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " 获取锁");
//等待60s
try {
Thread.sleep(600000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " 释放锁");
}
}, "thread1");
thread1.start();
//主线程休眠2s,让线程1获得cpu分配执行
Thread.sleep(2000);
//停止thread1,并释放对象锁
thread1.stop();
//开启线程2获取对现实
new Thread(() ->{
System.out.println(Thread.currentThread().getName()+" 等待获取锁");
synchronized (lock){
System.out.println(Thread.currentThread().getName()+" 获取锁");
}
},"thread2").start();
}
}
Java线程的中断机制
Java没有提供一种安全、直接的方法来停止某个线程,而是提供了中断机制。中断机制:是一种协作机制,即通过中断机制并不能直接终止另一个线程,而需要被中断的线程自己处理。被中断的线程拥有完全的自主权,它既可以选择立即停止,也可以选择一段时间后停止,也可以选择压根不停止。
API的使用
interrupt():将线程的中断标志位设置为true,不会停止线程。
isInterrupt():判断当前线程的中断标志位是否为true,不会清除中断标志位。
Thread.interrupted():判断当前线程的中断标志位是否为true,并清除中段标志位,重置为false。
package com.warrior.juc.threadbase;
/**
* @Author warrior
* 线程中断机制案例
*/
public class ThreadInterruptDemo {
private static int i = 0;
public static void main(String[] args) throws InterruptedException {
//创建一个线程
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println(i++);
//Thread.interrupted() 清除中断标志位
//Thread.currentThread().isInterrupted() 不会清除中断标志位
if(Thread.currentThread().isInterrupted()){
System.out.println("===================");
}
if(i == 10){
break;
}
}
}
});
thread.start();
//不会停止线程thread,只会设置一个中断标志位flag=true
thread.interrupt();
}
}
利用中断机制优雅停止线程
package com.warrior.juc.threadbase;
/**
* @Author warrior
* 利用线程中断机制,优雅停止线程
*/
public class StopThreadDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
int count = 0;
@Override
public void run() {
while (!Thread.currentThread().isInterrupted() && count < 10000) {
System.out.println("count = " + count++);
}
System.out.println("====线程停止=======");
}
});
thread.start();
Thread.sleep(5);
thread.interrupt();
}
}
sleep期间能否感受到中断
package com.warrior.juc.threadbase;
/**
* @Author warrior
* 利用线程中断机制,优雅停止线程
*/
public class StopThreadDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
int count = 0;
@Override
public void run() {
while (!Thread.currentThread().isInterrupted() && count < 1000) {
System.out.println("count = " + count++);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
System.out.println(e);
e.printStackTrace();
}
}
System.out.println("====线程停止=======");
}
});
thread.start();
Thread.sleep(5);
thread.interrupt();
}
}
执行结果:
count = 998
count = 999
====线程停止=======
处于休眠中的线程被中断,线程是可以感受到中断信号的,并且会抛出一个InterruptedException 异常,同时清除中断信号,将中断标记位设置成 false。这样就会导致while条件Thread.currentThread().isInterrupted()为false,程序会在不满足count < 1000这个条件时退出。如果不在catch中重新手动添加中断信号,不做任何处理,就会屏蔽中断请求,有可能导致线程无法正确停止。
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
//重新设置线程中断状态为true
Thread.currentThread().interrupt();
}
Java线程间通讯
volatile 可见性
volatile有两大特性,一是可见性,二是有序性,禁止指令重排序,其中可见性就是可以让线程之间进行通信。
package com.warrior.juc.threadbase;
/**
* @Author warrior
* volatile可见性实现线程间通讯
*/
public class VolatileDemo {
private static volatile boolean flag = true;
public static void main(String[] args) {
new Thread(() -> {
while (true) {
if (flag) {
System.out.println(Thread.currentThread().getName() + " turn on");
flag = false;
}
}
}, "Thread-1").start();
new Thread(() -> {
while (true) {
if (!flag) {
System.out.println(Thread.currentThread().getName() + " turn off");
flag = true;
}
}
}, "Thread-2").start();
}
等待唤醒(等待通知)机制
等待唤醒机制可以基于wait和notify方法来实现,在一个线程内调用该线程锁对象的wait方法,线程将进入等待队列进行等待直到被唤醒。
package com.warrior.juc.threadbase;
/**
* @Author warrior
* 等待唤醒机制实现线程间通讯
*/
public class WaitDemo {
private static Object lock = new Object();
private static boolean flag = true;
public static void main(String[] args) {
new Thread(() -> {
while (true) {
synchronized (lock) {
if (flag) {
System.out.println(Thread.currentThread().getName() + " turn on .......");
try {
Thread.sleep(100);
flag = false;
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}, "Thread-turn-on").start();
new Thread(() -> {
while (true) {
synchronized (lock) {
if (!flag) {
System.out.println(Thread.currentThread().getName() + " turn off .......");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
lock.notify();
}
}
}
}, "Thread-turn-off").start();
}
}
LockSupport是JDK中用来实现线程阻塞和唤醒的工具,线程调用park则等待许可,调用unpark则为指定线程提供许可,使用它可以在任何场合使用线程阻塞,可以指定任何线程进行唤醒,并且不用担心阻塞和唤醒操作的顺序,注意连续多次唤醒的效果和一次唤醒是一样的。
package com.warrior.juc.threadbase;
import java.util.concurrent.locks.LockSupport;
/**
* @Author warrior
* LockSupport 实现线程间通讯案例
*/
public class LockSupportDemo {
private static boolean flag = true;
public static void main(String[] args) throws InterruptedException {
TurnOnThread turnOnThread = new TurnOnThread();
TurnOffThread turnOffThread = new TurnOffThread();
turnOnThread.setTurnOffThread(turnOffThread);
turnOffThread.setTurnOnThread(turnOnThread);
turnOnThread.start();
Thread.sleep(100);
turnOffThread.start();
}
static class TurnOnThread extends Thread {
private Thread turnOffThread;
public void setTurnOffThread(Thread turnOffThread) {
this.turnOffThread = turnOffThread;
}
@Override
public void run() {
if (flag) {
System.out.println(Thread.currentThread().getName() + " turn on .......");
flag = false;
//阻塞当前线程
LockSupport.park();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒turnOffThread
LockSupport.unpark(turnOffThread);
}
}
}
static class TurnOffThread extends Thread {
private Thread turnOnThread;
public void setTurnOnThread(Thread turnOnThread) {
this.turnOnThread = turnOnThread;
}
@Override
public void run() {
if (!flag) {
System.out.println(Thread.currentThread().getName() + " turn off .......");
flag = true;
//唤醒turnOnThread
LockSupport.unpark(turnOnThread);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//阻塞当前线程
LockSupport.park();
}
}
}
}
管道输入输出流
管道输入/输出流和普通的文件输入/输出流或者网络输入/输出流不同之处在于,它主要用于线程之间的数据传输,而传输的媒介为内存。管道输入/输出流主要包括了如下4种具体实现:PipedOutputStream、PipedInputStream、PipedReader和PipedWriter,前两种面向字节,而后两种面向字符。
Thread.join
join可以理解成是线程合并,当在一个线程调用另一个线程的join方法时,当前线程阻塞等待被调用join方法的线程执行完毕才能继续执行,所以join的好处能够保证线程的执行顺序,但是如果调用线程的join方法其实已经失去了并行的意义,虽然存在多个线程,但是本质上还是串行的,最后join的实现其实是基于等待通知机制的。