线程简介
现代操作系统调度的最小单元是线程。在一个进程中可以创建多个线程,这些线程都可以有各自的计数器,堆栈和局部变量等属性,并且可以访问共享的内存变量。
线程优先级
现代操作系统采用时间片的形式调度运行的线程,当线程的时间片用完了就会发生线程调度,并等待下次分配。而线程的优先级就是决定需要多或少分配一些处理器资源的线程属性。
在Java的Thread中,通过一个int变量priority来控制优先级。优先级范围从1~10,默认优先级是5,优先级高的线程分配的时间片的数量要多于优先级低的线程。通过Thread类的setPriority()方法可以修改优先级。在不同的JVM以及操作系统上,线程规划会存在差异,有些操作系统还会忽略对线程优先级的设定,故线程优先级设定不能作为程序正确性的依赖。
线程状态
Java线程在运行的生命周期有6种状态,分别是NEW,RUNNABLE,BLOCKED,WAITING,TIME_WAITING,TERMINATED.各个状态之间的切换关系引用下面一张图来表示:
等待/通知机制
当一个线程修改了一个对象的值,而另一个线程要感知到变化,才能进行相应的操作,这时就需要等待/通知机制。
等待/通知的经典范式:
等待方遵循如下的原则。
1)获取对象的锁。
2)如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件。
3)条件满足则执行对应的逻辑。
通知方遵循如下原则。
1)获得对象的锁。
2)改变条件。
3)通知所有等待在对象上的线程。
在Java中等待/通知方法是任意类都具备的,因为这些方法被定义在所有对象的超类java.lang.Object上,相关方法和描述如下:
方法名称 | 描述 |
notify() | 通知一个在对象上等待的线程,使其从wait()方法返回,而返回的前提是该线程获取到了对象的锁 |
notifyAll() | 通知所有等待在该对象上的线程 |
wait() | 调用该方法的线程进入WAITING状态,只有等待另外线程的通知或线程被中断才会返回,需要注意,调用wait()方法后,会释放对象的锁 |
wait(long) | 超时等待一段时间,这里的参数时间是毫秒,也就是等待长达n毫秒,如果没有通知就超时返回 |
wait(long,int) | 对于超时时间更细粒度的控制,可以达到纳秒 |
在使用wait()、notify()及notifyAll()时需要注意一下细节:
1) 使用wait(),notify(),notifyAll()时需要先对调用对象加锁。
2) 使用wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列。
3) notify()或notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或notifyAll()的线程释放锁之后,等待线程才有机会从wait()返回。
4) notify()方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll()方法则是将等待队列中所有的线程全部移到同步队列,被移动的线程状态有WAITING变为BLOCKED。
5) 从wait() 方法返回的前提是获得了调用对象的锁。
Daemon线程
Daemon线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。所以,当一个Java虚拟机中不存在非Daemon线程的时候,JVM将会退出。由于这个特点,在JVM退出时Daemon线程中的finally块并不一定会执行。通过Thread类的setDaemon(true)将线程设置为Daemon线程。
启动线程
线程对象创建成功后,调用start()方法就可以启动这个线程。start()方法的含义是:当前线程同步告知Java虚拟机,只有线程规划器空闲,应立即启动调用start()方法的线程。
线程中断
中断是线程的一个标识位属性,它表示一个运行中的线程是否被其他线程进行了中断操作。线程可以通过isInterrupted()来判断是否被中断,通过调用静态方法Thread.interrupted()来判断中断状态,该方法调用了之后会恢复中断标志位。但线程已处于终结状态,即使被中断过,调用isInterrupted()依然会返回false。
在Java的API中,有许多抛出InterruptedException的方法,这些方法在抛出InterruptedException之前,JVM会先将线程的中断标志位复位,然后抛出InterruptedException。
sleep()
使当前所在线程进入阻塞。只是让出 CPU ,并没有释放对象锁。
由于休眠时间结束后不一定会立即被 CPU 调度,因此线程休眠的时间可能大于传入参数。
如果被中断会抛出InterruptedException 。