进程是系统进行资源分配和调度的基本单位,线程是CPU分配的基本单位。
Java中有三种线程创建方式,分别为实现Runnable接口的run方法,继承Thread类并重写run的方法,使用FutureTask方式。
前两种方式都有一个缺点,就是任务没有返回值。
线程等待与通知
wait()函数
当一个线程调用共享变量的wait()方法时,该调用线程会被阻塞挂起,直到发生下面几件事情之一才返回:(1)其他线程调用了该共享对象的notify()或者notifyAll()方法(2)其他线程调用了该线程的interrupt()方法,该线程抛出InterruptedException异常返回。
wait(long timeout)函数
该方法相比wait()方法多了一个超时参数,它的不同之处在于,如果一个线程调用共享对象的该方法挂起后,没有在指定的时间内被其他线程调用该共享变量的notify()或者botifyAll()方法唤醒,那么该函数还是会因为超时而返回。
notify()函数
一个线程调用共享对象的notify的方法后,会唤醒一个在该共享变量上调用wait系列方法后被挂起的线程。一个共享变量上可能会有多个线程在等待,具体唤醒哪个等待的线程是随机的。
notifyAll()函数
notify()方法会唤醒所有在该共享变量上由于调用wait系列方法而被挂起的线程。
等待线程执行终止的join方法
让线程睡眠的sleep方法
让出CPU执行权的yield方法
当一个线程调用yield方法,当前线程会让出CPU使用权,然后处于就绪状态,线程调度器会从线程就绪队列里面获取一个线程优先级最高的线程,当然也有可能会调度到刚刚让出CPU的那个线程来获取CPU执行权。
线程中断
Java中的线程中断是一种线程间的协作模式,通过设置线程的中断标志并不能终止该线程的执行,而是被中断的线程根据中断状态自行处理。
void interrupt()方法:中断线程。
当线程A运行时,线程B可以调用线程A的interrupt()方法来设置线程A的中断标志为true并立即返回。设置标志仅仅是设置标志,线程A实际并没有被中断,它会继续往下执行。如果线程A因为调用了wait系列函数、join方法或者sleep方法而被阻塞挂起,这时候若线程B调用A的interrupt()方法,线程A会在调用这些方法的地方抛出InterruptedException异常而返回。
boolean isInterrupted()方法:
检测当前线程是否被中断,如果是返回true
boolean interrupted()方法:
检测当前线程是否被中断。如果是,则清除中断标志。
线程死锁
死锁的产生必须具备以下四个条件。
- 互斥条件:该资源只能同时被一个线程占用。
- 请求并持有条件:一个线程已经持有了至少一个资源,又提出新的资源请求,而请求的资源被其他线程占有。
- 不可剥夺条件:已经持有的资源不能被其他线程抢占。
- 环路等待:在发生死锁时,必然存在一个线程-资源的环形链。
造成死锁的原因其实和申请资源的顺序有很大关系,使用资源申请的有序性原则就可以避免死锁。
守护线程与用户线程
不管当前是否有守护线程,并不影响JVM的退出。只要有一个用户线程还没结束,正常情况下JVM就不会退出。
//设置为守护线程
thread.setDaemon(true);
如果希望主线程结束后JVM进程马上结束,那么在创建线程时可以将其设置为守护线程。
ThreadLocal
如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。
ThreadLocal的实现原理
在每个线程内部都有一个名为threadLocals的成员变量,该变量的类型为HashMap,其中key为我们定义的ThreadLocal变量的this引用,value则为我们使用set方法设置的值。每个线程的本地变量存放在线程自己的内部变量threadLocals中,如果当前线程一直不消亡,那么这些本地变量会一直存在,所以可能会造成内存溢出,因此使用完毕后要记得调用ThreadLocal的remove方法删除对应线程的threadLocals的本地变量。