线程
一、线程和进程
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位 线程个数最优公式 N(thread)= (1+T(io)/T(cpu))*Ncpu
进程是操作系统资源分配的基本单位,线程是任务调度和执行的基本单位,进程维护资源,线程是真正的执行体
包含关系:一个进程可以创建多个线程,且最少一个主线程,线程只能属于一个进程,线程是进程的一部分。
二、上下文切换
cpu为线程分配时间片,时间片非常短(毫秒级别),cpu不停的切换线程执行,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这个任务的状态,让我们感觉是多个程序同时运行的。上下文的频繁切换,会带来一定的性能开销
如何减少上下文切换的开销?
无锁并发编程:多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一些办法来避免使用锁,如将数据的ID按照Hash算法取模分段,不同的线程处理不同段的数据
CAS:Java的Atomic包使用CAS算法来更新数据,而不需要加锁。使用最少线程
使用最少线程:避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这样会造成大量线程都处于等待状态
协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。--主要是go语言
三、线程的状态
操作系统的状态
- NEW:该状态下的线程还没开始执行
- READY:线程等待获取CPU的时间片
- RUNNING:线程正在运行
- WAITING:线程等待
- TERMINATING:该状态下的线程已经退出
java的状态
- NEW:该状态下的线程还没开始执行
- RUNNABLE:该线程正运行中
- BLOCKED:该状态下的线程被阻塞并且等待一个监听锁,主要是等待synchronized语句块的锁的状态
- WAITING:该状态下的线程无限期等待另外一条线程执行特定的操作,执行了object.wait()、join()、LockSupport.part(),进入该状态的线程需要等待其它线程做出一些特定的动作(通知或者中断)。
- TIMED_WAITING:该状态下的线程在特定的时间内等待另外一条线程执行某种操作。执行了Object.wait(long)、Thread.join()、LockSupport.parkNanos()、LockSupport.parkUtil,会在特定时间内自行返回。
- TERMINATING:该状态下的线程已经退出
四、忙循环
忙循环就是用一个循环让一个线程等待,它不像wait、sleep会放弃cpu的控制权,而是一直在执行当前的程序,这是为了保留CPU的缓存。在多核系统中,一个等待线程醒来的时候可能会在另一个内核运行,这样会重建缓存。为了避免重建缓存和减少等待重建的时间就可以使用它了。
六、线程挂起
线程的挂起是为了使线程进入“非可执行”状态,这个状态下的线程CPU不会分给线程时间片,进入这个状态用来暂停一个线程的运行。
为什么要挂起:cpu分配的时间片非常短,同时也非常珍贵,为了避免资源的浪费。
挂起线程操作:
1、废弃的方法,thread.suspend()该方法不会释放资源,可能会导致死锁,所以废弃。thread.resume()是跟着suspend()存在的,也跟着废弃。
2、可以使用的方法:wait()方法可以挂起线程,会释放资源(包括锁和CPU资源),notify()可以唤醒一个该锁的线程,notifyAll()是唤醒所有锁的线程。这几个方法必须是持有锁对象,必须在同步的方法体内。
使用场景:当所有资源没有准备到位的时候,需要挂起等到所有资源都拿到了,再执行。
七、线程中断
stop()方法已经被废弃,因为该方法会强行关闭线程,会导致线程安全问题。
使用interrupt()方法可以使线程中断,但是要视情况而定,如果不使用线程池,直接使用该方法,但是如果使用线程池,需要根据标记位去判断线程的状态。
/**
* @Description 中断线程
* @Author lcy
* @Date 2020/9/1 21:13
*/
public class InterruptThread {
/**
* 要保证原子性操作,如果不使用原子操作的类,需要加volatile
* 即 private static volatile boolean interrupt = true;
*/
private static AtomicBoolean interrupt = new AtomicBoolean(true);
public static void main(String[] args) throws InterruptedException{
ExecutorService executorService = new ThreadPoolExecutor(0, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>());
//手动中断线程的方法
Thread thread = new Thread(()->{
while(!Thread.currentThread().isInterrupted()){
System.out.println("手动的线程正在运行");
}
System.out.println("手动的线程结束");
});
thread.start();
//线程池中断
executorService.execute(()->{
while(interrupt.get()){
System.out.println("线程池的线程正在运行");
}
System.out.println("线程池的线程结束");
});
Thread.sleep(1L);
//线程中断
thread.interrupt();
//让线程池里的这个线程结束 --注意,使用线程池测试的时候,线程池不一定会直接关闭,需要自己配置线程池参数,
// 让线程池跟着子线程一起关闭
interrupt.set(false);
}
}
八、线程优先级
线程优先级的高度,跟数字的大小相反,优先级为1最高,为10最低
线程的优先级告诉程序线程的重要程度,当线程堵塞的时候,程序会尽可能的先运行优先级比较高的线程,优先级比较低的线程被准许运行的概率会比较低。
/**
* @Description 线程优先级
* @Author lcy
* @Date 2020/9/2 9:13
*/
public class ThreadPriority {
public static void main(String[] args) throws InterruptedException{
//因为队列的原因,需要三个线程才能测试,有一个进入消息队列。这样另外两个临时线程才会同步
ExecutorService executorService = new ThreadPoolExecutor(0,3,
0L,TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(1));
Object object = new Object();
new Thread(() -> {
Thread.currentThread().setName("线程A");
//默认优先级5
System.out.println(Thread.currentThread().getName() + "尝试获取锁资源!");
synchronized (object) {
System.out.println(Thread.currentThread().getName() + "准备等待!");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "被唤醒!");
}
}).start();
new Thread(() -> {
Thread.currentThread().setName("线程B");
//设置优先级
Thread.currentThread().setPriority(4);
System.out.println(Thread.currentThread().getName() + "尝试获取锁资源!");
synchronized (object) {
System.out.println(Thread.currentThread().getName() + "准备等待!");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "被唤醒!");
}
}).start();
//System.out.println("-----------------线程池-----------------");
Object o = new Object();
executorService.execute(() -> {
Thread.currentThread().setName("线程池线程C");
//默认优先级5
System.out.println(Thread.currentThread().getName() + "尝试获取锁资源!");
synchronized (o) {
System.out.println(Thread.currentThread().getName() + "准备等待!");
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "被唤醒!");
}
});
executorService.execute(() -> {
Thread.currentThread().setName("线程池线程D");
//默认优先级5
System.out.println(Thread.currentThread().getName() + "尝试获取锁资源!");
synchronized (o) {
System.out.println(Thread.currentThread().getName() + "准备等待!");
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "被唤醒!");
}
});
executorService.execute(() -> {
Thread.currentThread().setName("线程池线程E");
//默认优先级5
Thread.currentThread().setPriority(4);
System.out.println(Thread.currentThread().getName() + "尝试获取锁资源!");
synchronized (o) {
System.out.println(Thread.currentThread().getName() + "准备等待!");
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "被唤醒!");
}
});
//唤醒线程必须在同步方法里,否则报IllegalMonitorStateException
Thread.sleep(1000L);
synchronized (object){
object.notifyAll();
}
synchronized (o){
o.notifyAll();
}
}
}
九、守护线程
线程分为用户线程和守护线程,守护线程是指整个程序中的所有线程用户的守护者,只要有活着的用户线程,守护线程就或者。当jvm当中的最后一个非守护线程结束时,守护线程也随着jvm一起退出。
尽量少使用守护线程,因为不可控制。不要在守护线程当中去进行读写操作、执行计算逻辑等等。