Java多线程是为了更好利用CPU资源,提升系统吞吐率,在一些适合的场合,用多线程可以避免阻塞。
一、线程简介
简单main函数查看线程信息(JDK11)
public class PrintThread {
public static void main(String[] args) {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
for (ThreadInfo threadInfo : threadInfos) {
System.out.println("["+threadInfo.getThreadId()+"] " + threadInfo.getThreadName());
//可以直接输出threadInfo 打印详细信息
}
}
}
输出:
[1] main #主线程main,程序入口
[2] Reference Handler #清除Reference线程
[3] Finalizer #调用对象finalize方法的线程
[4] Signal Dispatcher #分发处理发给JVM信号的线程
[12] Notification Thread. #JDK11才有
[13] Common-Cleaner #JDK11才有
相关虚拟机接口类MXBean,可查看JDK中java.lang.management包,比如
线程支持优先级设置setPriority(int), 范围1~10,默认优先级是5,优先级越高分配的时间片越多。有些操作系统会忽略对线程优先级的设定,
不推荐使用
Daemon守护线程是一种支持型线程,负责后台调度或者支持工作。当虚拟机不存在非Daemon进程的时候,虚拟机将会退出。通过setDaemon(true)设置,只能在启动之前设置,否则抛异常。
线程状态(6种)
状态名称 | 说明 |
---|---|
NEW | 初始状态,线程被构建,但还没有调用start方法 |
RUNNABLE | 运行状态,Java线程把操作系统的就绪和运行两种状态笼统地称作"运行中" |
BLOCKED | 阻塞状态,表示线程阻塞于锁 |
WAITING | 等待状态,进入该状态表示当前线程需要等待其它线程通知或者中断 |
TIME_WAITING | 超时等待状态,不同于WAITING,它可以在指定的时间自行返回 |
TERMINATED | 终止状态,表示当前线程已经执行完毕 |
参考java.lang.Thread 中枚举类State
线程状态变迁图
- wait/notify/notifyAll Object对象的方法,必须跟synchronized搭配使用;且使用同一个锁对象;wait会释放锁,notify/notifyAll不会释放锁,等待线程真正唤醒要等到唤醒线程释放锁,并获取到锁,才真正唤醒;
- join 等待线程执行结束,Thread对象的方法,内部实现原理也是wait方式
public final void join() throws InterruptedException {
join(0);
}
public final synchronized void join(long millis)
throws InterruptedException {
...
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
...
}
}
- LockSuppport.park()/unpark(Thread) 支持唤醒指定线程
public class ParkThread extends Thread{
@Override
public void run() {
System.out.println("doing....");
LockSupport.park(); //进入waiting
System.out.println("unpack continue doing...");
}
public static void main(String[] args) throws InterruptedException {
ParkThread parkThread = new ParkThread();
parkThread.start();
Thread.sleep(5000); //休眠5s
LockSupport.unpark(parkThread); //唤醒
}
}
- Thread.sleep(long) 休眠不释放锁, 时间到了自动唤醒
二、线程启动/停止/中断
- 线程启动支持两种方式:(1)继承Thread (2)实现Runnable接口
class MyThread extends Thread{
public void run(){
...
}
}
MyThread p = new MyThread();
p.start();
class MyJob implements Runable{
public void run(){
...
}
}
new Thread(new MyJob()).start();
- 优雅停止线程方式:自定义标志位和中断标志位
public class StopThread extends Thread{
private volatile boolean exit = false;
private long count = 0;
@Override
public void run() {
while (!exit && !Thread.currentThread().isInterrupted()){
count ++;
}
System.out.println(Thread.currentThread().getName()+ " count:" + count);
}
public void cancel(){
this.exit = true;
}
public static void main(String[] args) throws InterruptedException {
StopThread stopThread1 = new StopThread();
stopThread1.setName("FlagThread");
stopThread1.start();
StopThread stopThread2 = new StopThread();
stopThread2.setName("InterruptedThread");
stopThread2.start();
Thread.sleep(20);
stopThread1.cancel(); //自定义标志位
Thread.sleep(10);
stopThread2.interrupt();//中断标志位
}
}
中断其实是一个标志位,当线程执行interrupt()方法,如果线程处于Runnable状态时,isInterrupted() 返回true,如果线程处于阻塞阶段,线程会抛InterruptedException,并重置中断标志位,所以isInterrupted() 返回false
线程stop()方法可以停止线程,但不推荐使用,它是一个被废弃的方法,无法保证资源的完全释放。
线程暂停suspend()和恢复resume() 方法也是被废弃的方法,suspend() 不释放锁,容器导致死锁。
三、线程通讯
线程间通讯主要有共享变量和消息传递
1.volatile
public class VolatileTest{
static volatile boolean flag = false; //定义共享变量,volatile修饰线程及时感知
public static void main(String[] args) throws InterruptedException{
List list = new ArrayList<>();
Thread threadA = new Thread(()->{
for(int i = 1; i <= 10; i++){
list.add("hello");
System.out.println("线程A添加元素,size="+list.size());
//Thread.sleep(1000);
if(list.size()==5){ //线程A添加到第五个元素通知B线程
flag = true;
}
}
});
Thread threadB = new Thread(()->{
while(true){
if(flag){
System.out.println("线程B收到通知执行自己业务");
break;
}
}
});
threadB.start();
Thread.sleep(1000); //确保先启动threadB
threadA.start();
}
}
2.wait()/notify()
public class WaitNotifyTest{
public static void main(String[] args) throws InterruptedException{
Object lock = new Object();
List list = new ArrayList<>();
Thread notifyThread = new Thread(()->{
synchronized(lock){
for(int i = 1; i <= 10; i++){
list.add("hello");
System.out.println("NotityThread添加元素,size="+list.size());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(list.size()==5){ //添加到第五个元素通知WaitThread
lock.notify();
}
}
}
});
Thread waitThread = new Thread(()->{
synchronized(lock){
if(list.size()!=5){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("WaitThread收到通知执行自己业务");
}
});
waitThread.start();
Thread.sleep(1000); //确保先启动WaitThread
notifyThread.start();
}
}
NotityThread线程虽然调用notify()唤醒,依然是要走完自己业务,WaitThread线程才开始真正执行;因为notify()并没有释放锁,NotifyThread线程运行结束释放锁,WaitThread线程获取锁才真正唤醒进入Runnable状态
3. ReentrantLock + Condition
public class NotifyThread{
public static void main(String[] args) throws InterruptedException{
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
List list = new ArrayList<>();
Thread notifyThread = new Thread(()->{
lock.lock();
try{
for(int i = 1; i <= 10; i++){
list.add("hello");
System.out.println("NotityThread添加元素,size="+list.size());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(list.size()==5){ //添加到第五个元素通知WaitThread
condition.signal();
}
}
}finally {
lock.unlock();
}
});
Thread waitThread = new Thread(()->{
lock.lock();
try{
if(list.size()!=5){
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("WaitThread收到通知执行自己业务");
}finally {
lock.unlock();
}
});
waitThread.start();
Thread.sleep(1000); //确保先启动WaitThread
notifyThread.start();
}
}
这种方法跟Object的wait()/notify() 一样,WaitThread线程要等到NotifyThread线程释放锁,才能真正唤醒。