守护线程与用户线程:
Java中线程分为两类:守护线程和用户线程。
用户线程:JVM启动是会调用main函数,main函数所在线程就是用户线程,还有就是我们自己手动开启的线程,Thread等
守护线程:JVM内部会启动好多守护线程,像垃圾回收线程等。
区别:
1、最后一个非守护线程结束时,JVM会正常退出,不管是否有守护线程。(只要有一个用户线程未结束,JVM都不会停止)
如何创建守护线程:
/**
* 测试守护线程和用户线程
*/
public class ThreadTest {
public static void main(String[] args) {
Thread daemonThread = new Thread(new Runnable() {
@Override
public void run() {
for(;;){}
}
});
daemonThread.setDaemon(true); //调用setDaemon即可这样就能设置为守护线程
daemonThread.start();
System.out.print("main is over");//因为是守护线程,执行完这一步的时候,守护线程就会停止,退出JVM
}
}
原理:main线程运行结束后,JVM会自动启动一个叫做DestroyJavaVM的线程,该线程会等待所有用户线程结束后终止JVM进程
总结:如果希望主线程结束后JVM马上退出,那么就把线程创建为守护线程,如果希望子线程结束后,JVM才结束,就使用用户线程。
线程创建的三个方法:
1、继承Thread类,重写run方法,无返回值,可以在子类
2、实现Runable接口,重写run方法,无返回值
3、实现Callable接口,重写call方法,然后包装在FutureTask类里面交给Thread执行,有返回值
常见的线程方法:
wait()等待方法
当一个线程调用一个共享变量的wait方法的时候,该调用线程会被阻塞挂起,直到其他线程notify或者notifyAll方法返回; 如果其他线程调用了该线程的interrupt方法,则线程会抛出InterruptedException异常
虚假唤醒:虚假唤醒是指在wait后,不是通过notify系列方法或者中断返回,这种唤醒就称为唤醒(怎么触发的?我理解为操作系统层面误触发的,个人见解)这时候就需要我们写一个循环去不断判断唤醒条件是否被满足,防止虚假唤醒。如:
synchronized(obj){
while(条件不满足){
obj.wait();
}
}
wait(long timeout) :调用该方法后,如果线程在timeout时间内没有被唤醒,就会自动超时唤醒。
notify()唤醒方法
该方法能唤醒一个在该共享变量上调用了wait系列方法后被挂起的线程,一个共享变量上可能有多个线程等待,具体唤醒哪个是随机的,notifyAll()方法会唤醒所有线程,然后它们去竞争共享变量。
join()方法
这个方法会让调用这个方法的线程(当前线程)阻塞,等待调用join方法的对象执行完成返回后,变回就绪状态。如:在主线程里面启动A线程,那么在主线程内部调用A.join()后,主线程被阻塞,等待A线程运行完退出后,主线程才恢复就绪状态。
线程睡眠的sleep()方法
调用这个方法的线程会让出指定时间的执行权,就是在这段时间不参与CPU调度,但是这时候获取的监视器资源(锁等)是不会让出的,指定时间过后变会就绪状态,参与CPU调度,获取到CPU资源后就能继续运行了(时间到了也不一定马上运行,还是得等到获取到CPU资源)。如果在sleep过程被调用interrupt方法,则会在sleep处抛出异常,而且清除中断标记,因此有些时候我们可能需要在异常处理上重新标记中断,否则这个中断无法被捕获。
让出CPU执行权的yield()方法
yield()方法可以让出当前线程的当前时间片剩余时间,返回就绪状态。这时,线程调度器会从线程就绪队列中获取一个优先级高的线程(当前让时间的线程也能又重新获取)来获取CPU执行权。
线程的中断:
1、void interrupt() : 中断线程,这个方法会把线程的中断标志设置为true,然后立即返回。这时线程不会马上中断,它会继续执行。如果执行该方法前,线程处于wait系列函数、join、sleep方法被阻塞挂起时,该中断线程会在调用这些方法的地方爆出InterrputedException
2、boolean isInterrupted():检测当前线程(跟哪个线程对象无关,在哪个线程调用,就返回当前线程的中断标志)是否被中断,如果是返回true,不是返回false
3、boolean interrupted():static方法,检测当前线程是否被中断,如果是清除中断标记(与上一个方法的不同地方),然后返回true,如果不是则返回false
为了让大家理解三者的区别,可以看看这个案例,思考一下:
/**
* 理解interrupt() isInterrupted() interrupted()区别
*/
public class ThreadInterruptTest1 {
public static void main(String[] args) {
Thread threadOne = new Thread(new Runnable() {
@Override
public void run() {
for (;;){
}
}
});
//启动子线程
threadOne.start();
//设置子线程中断标志,立即返回,无返回值
threadOne.interrupt();
//获取中断标志
System.out.println("isInterrupted:" + threadOne.isInterrupted());
//获取中断标志并重置
System.out.println("interrupted: " + threadOne.interrupted());
//获取中断标志并重置, 这里跟上面的一样,都是获取主线程的中断标志,所以都是false
System.out.println("interrupted: " + Thread.interrupted());
//获取子线程的中断标志
System.out.println("isInterrputed: " + threadOne.isInterrupted());
//结果如下:
//isInterrupted:true
//interrupted: false
//interrupted: false
//isInterrputed: true
}
}
setPriority()方法设置线程优先级
通过这个方法可以设置线程优先级,Java里面线程优先级由 1 到 10 ,数字越大优先级越高,对资源的抢占能力越强,但是并不能保证什么时候都是高优先级的线程优先获取资源。下面三个是线程内置的三个静态表量,默认为优先级为 5。.
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
理解线程上下文切换
在多线程编程中,一般线程个数都大于CPU个数,而CPU每一时刻只能被一个线程使用,因此CPU资源分配采用时间片轮转的策略。当线程当前的时间片使用完成后,会让出CPU,等待下一个时间片到来,继续运行,这时候切换的时候,就要记住自己上一次运行到了哪个地方,即保存当前线程的执行现场,当再次执行时根据保存的执行现场来恢复执行现场。
线程上下文切换时机:用完时间片处于就绪状态,当前线程被其他线程中断。
如何减少上下文切换:
1、无所并发编程:采取一些办法避免使用锁,如根据ID按照Hash算法取模分段,不同线程处理不同数据段。
2、CAS算法:使用Java的Atomic包的CAS算法来更新数据,不需要加锁(我的一篇文章有介绍CAS,感兴趣可以点进来)
3、使用最少线程:避免创建不需要的线程。
4、协程:在单线程里实现多任务调度,并在单线程里维持多个任务区间的切换。