1.为什么要使用多线程
- 充分发挥多核CPU的性能
- 方便进行业务拆分,提升服务性能
2. java多线程有什么缺点
(1) 频繁的上下文切换
线程在切换过程中,CPU需要保存当前线程的状态,以便切换回来时能够恢复到当前状态,这个过程会损耗CPU性能。频繁的上下文切换无法发挥多线程的优势。为了减少上下文切换,可以采用无锁并发编程、CAS算法、使用最少的线程或是使用协程
- 无锁并发编程:在有锁并发场景中,线程会因为没有竞争到锁而阻塞,让出CPU,提前进行线程切换
- CAS操作,只有一个线程能够执行成功,其他线程会循环竞争锁,直到时间片执行完成
- 使用少量线程:任务较少时,避免创建过多的线程,以至于多个线程处于等待状态
- 使用协程
协程概念:基于线程之上,但是比线程更轻量级的存在,由程序员自己写程序控制
协程的目的:当线程出现长时间IO时,由程序控制,挂起当前任务,并保存当前栈信息,去执行另一个任务,等待任务完成或是达到某个条件时,再还原原先的栈信息,并继续执行。
协程特点:
(1)线程有OS进行调度,协程由用户自己进行调度。且协程是在同一线程内部操作,所以可以减少线程上下文的切换
(2)线程默认的stack是1M,而协程默认是接近1k,所以一个线程内部可以有多个协程
(3)协程适用于存在阻塞的并发场景,而不适用于大量计算的场景
(2) 线程安全问题
在多线程环境下,无论线程以何种顺序执行,都能保证程序的正确性。线程安全问题,本质就是对共享数据的访问问题
3. java线程状态
(1) 新建(new):创建后尚未启动的线程处于这个状态
(2) 运行(runable): ready + running
- ready:就绪状态,等待cpu分配时间片即可运行
- running:正在运行
(3) 无限等待(waiting):处于这个状态的线程不会被cpu分配时间片,其等待其他线程显示唤醒。有如下方法可以进入该状态:
- 没有设置timeout的Object.wait()方法
- 没有设置timeout的Thread.join()方法
- LockSupport.park()方法
(4) 超时等待(time_waiting):该状态下,线程也不会被CPU配时间片,但是与Waiting不同的是,该状态无需等待其他线程显示唤醒,超过超时时间之后,系统会自动唤醒线程。有如下方法可以让线程进入超时等待状态
- Thread.sleep()
- 设置了timeout的Object.wait()方法
- 设置了timeout的Thread.join()方法
- LockSupport.parkNanos()方法
- LockSupport.parkUtils()方法
(5) 阻塞(Blocked): 线程因为某种原因放弃CPU使用权,暂时停止运行。阻塞状态分两种
- 同步阻塞:线程在进入同步代码块(synchronize)时,未获得锁将进入这个状态
- 其他阻塞:正在运行的线程发出IO请求,JVM会把该线程置为阻塞状态
(6) 终止(Terminated):已经终止的线程状态。线程run()方法或是main()方法执行结束,或是线程因异常退run方法,该线程结束生命周期
4. 线程生命周期内的一些操作
除了新建线程之外,线程生命周期内还有一些其他操作,这些操作可以作为线程间的一种通信方式
- 中断 interrupt
(1) 中断是什么
interrupt()就是中断某个线程,主要用于线程间的协作,如果A线程需要中断B线程,就调用B.interrupt()
(2) interrupt()一定会中断线程吗
interrupt()可以看做是线程的一个标志位,某个线程被中断之后,就会记录该中断状态。所以调用某个线程的interrupt()之后,并不一定会真正中断该线程,仅仅是告知该线程你该中断了,线程是否中断应该由线程自身判断,而不是由外部线程决定
(3) 线程中断状态怎么用
原则上,在设计线程的执行流程时,首先要判断线程的中断状态来决定执行内容
(4) 什么时候会抛出InterrupedException
线程处于wait()、sleep()、join()等状态,此时调用线程的interrupt()会抛出InterrupedException,且中断状态会被清除
(5) 抛出的InterruptException该怎么处理- 继续上抛,交由上游处理
- catch异常,调用interrupt()方法,恢复当前线程的中断状态(调用线程的interrupt方法,需要通过中断状态判断线程是否被中断,如果不恢复,就可能会导致状态判断失败)
public class ThreadInterruptDemo {
public static void main(String[] args) {
Thread sleepThread1 = new Thread(()-> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread sleepThread2 = new Thread(()-> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//重置中断状态
Thread.interrupted();
e.printStackTrace();
}
});
Thread busyThread = new Thread(()-> {
while(true){
}
});
sleepThread1.start();
sleepThread2.start();
busyThread.start();
sleepThread1.interrupt();
sleepThread2.interrupt();
busyThread.interrupt();
System.out.println("sleepThread1 isInterrupt = " + sleepThread1.isInterrupted());
System.out.println("sleepThread2 isInterrupt = " + sleepThread2.isInterrupted());
System.out.println("busyThread isInterrupt = " + busyThread.isInterrupted());
}
}
- join
join可以看作是线程间的一种协作方式,在很多时候,一个线程能否执行,依赖另一个线程的执行结果,当A线程依赖B线程时,可以调用B.jion(),阻塞一直到B线程执行完成
join方法的核心源码,判断锁等待线程isAlive,如果存活,则无限期等待,当B线程执行完成退出时,会调用notifyAll()方法,通知所有线程
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
//无超时的join(),无限期等待
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
//有超时的等待
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
//java join demo
public class THreadJoinDemo {
public static void main(String[] args) {
Thread previousThread = Thread.currentThread();
for (int i = 0; i < 10; i++) {
TestJoin testJoin = new TestJoin(previousThread);
testJoin.start();
previousThread = testJoin;
}
}
}
class TestJoin extends Thread {
Thread currentThread;
public TestJoin(Thread currentThread) {
this.currentThread = currentThread;
}
@Override
public void run() {
try {
currentThread.join();
System.out.println(currentThread.getName() + " terminated");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
-
sleep
sleep就是按照当前指定的时间休眠,时间精度取决于处理器的计时器和调度器。
sleep对比wait
(1) sleep是Thread类的静态方法,wait是Object类的实例方法
(2) sleep可以在任意地方使用;wait只能在同步代码块或是同步方法中使用,也就是对象已经获得锁。调用wait之后,会释放锁,线程进入线程池,等待下一次获取资源;sleep不会释放锁,仅仅让出CPU
(3) sleep的线程在时间结束之后,只需获得CPU时间片就会继续执行;而wait的线程,必须等待其他线程调用notify或是notifyAll才会离开线程池,在获得时间片之后才能继续执行 -
yield
yield()是Thread的静态方法,执行之后表示该线程让出CPU,但是让出CPU不代表该线程就不运行了,如果在下次的竞争中,该线程获得CPU,将继续执行。
yield对比sleep
(1) 相同:都是Thread的静态方法,执行之后都是让出CPU
(2) 不同:Thread让出CPU之后,交由其他线程去竞争,yield让出CPU之后,交由与自己相同或是更高优先级的线程去竞争,自己有可能继续获取cpu并执行 -
守护线程deamon
守护线程是一种特殊的线程,在后台为系统提供服务。与之对应的是用户线程,只有当最后一个用户线程退出时,守护线程才会结束,JVM也才会终止运行。
注意:
在deamon线程退出时,并不会执行finally代码块
在deamon线程退出时,并不会执行finally代码块
在deamon线程退出时,并不会执行finally代码块
public class DeamonThreadDemo {
public static void main(String[] args) {
Thread deamonThread = new Thread(()->{
while (true) {
System.out.println("i am alive");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("i am finally quit");
}
}
});
deamonThread.setDaemon(true);
deamonThread.start();
try {
Thread.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
demo中,设置了守护线程deamonThread,主线程sleep(800),会让守护线程获得一次执行机会,打印一次“i am alive”和“i am finally quit”,主线程sleep(500)之后,守护线程继续执行一次“i am alive”,接着主线程退出,守护线程也跟着退出,并没有执行finally代码块。
网友评论