Java 线程
线程是操作系统中一种概念,Java 对其进行了封装,Java 线程本质上就是操作系统的中线程,其状态与操作系统的状态大致相同,但还是存在一些区别。
Java 线程状态
Java 线程状态定义在 Thread.State
枚举中,使用 thread#getState 方法可以获取当前线程的状态。
Java 线程总共存在 6 中状态,分别为:
- NEW(初始状态)新创建了一个线程对象
- RUNNABLE(运行状态) Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。调用
start()
方法后,线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。 - BLOCKED(阻塞状态)表示线程阻塞于锁。线程阻塞在进入synchronized 修饰的方法或代码块时的状态
- WATTING(等待状态)进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
- TIMED_WAITING(限时等待状态)该状态不同于WAITING,它可以在指定的时间后自行返回。
- TERMINATED(终止状态)表示该线程已经执行完毕。线程一旦执行结束或者线程执行过程发生异常且未正常捕获处理,状态都将会自动变成 TERMINATED。
RUNNABLE 与 WATTING
Object#wait
线程在获取到 synchronized 隐式锁后,显示的调用 Object#wait()方法。这种情况下该线程将会让出隐式锁,一旦其他线程获取到该锁,且调用了 Object.notify() 或object.notifyAll(),线程将会唤醒,然后变成 RUNNABLE。
Thread#join
join 方法是一种线程同步方法。假设我们在 main 方法中执行 Thread A.join() 方法,main 线程状态就会变成 WATTING。直到 A 线程执行完毕,main 线程才会再变成 RUNNABLE。
LockSupport#park()
LockSupport 是 JDK 并发包里重要对象,很多锁的实现都依靠该对象。一旦调用 LockSupport#park(),线程就将会变为 WATTING 状态。如果需要唤醒线程就需要调用 LockSupport#unpark,然后线程状态重新变为 RUNNABLE。
RUNNABLE 与 TIMED_WAITING
- Thread#sleep(long millis)
- 占有 synchronized 隐式锁的线程调用 Object.wait (long timeout) 方法
- Thread#join (long millis)
- LockSupport#parkNanos (Object blocker, long deadline)
- LockSupport#parkUntil (long deadline)
几个方法的比较
Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进入TIMED_WAITING状态,但不释放对象锁,millis后线程自动苏醒进入就绪状态。作用:给其它线程执行机会的最佳方式。
|
|
Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的CPU时间片,但不释放锁资源,由运行状态变为就绪状态,让OS再次选择线程。 作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。该方法与sleep()类似,只是不能由用户指定暂停多长时间。
thread.join()/thread.join(long millis),当前线程里调用其它线程t的join方法,当前线程进入WAITING/TIMED_WAITING状态,当前线程不会释放已经持有的对象锁。线程t执行完毕或者millis时间到,当前线程一般情况下进入RUNNABLE状态,也有可能进入BLOCKED状态(因为join是基于wait实现的)。
Object.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout) timeout时间到自动唤醒。
Object.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。
LockSupport.park()/LockSupport.parkNanos(long nanos),LockSupport.parkUntil(long deadlines), 当前线程进入WAITING/TIMED_WAITING状态。对比wait方法,不需要获得锁就可以让线程进入WAITING/TIMED_WAITING状态,需要通过LockSupport.unpark(Thread thread)唤醒。
调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) 代码段内。 线程等待时间到了或被notify/notifyAll唤醒后,会进入同步队列竞争锁,如果获得锁,进入RUNNABLE状态,否则进入BLOCKED状态等待获取锁。
创建线程
- 线程的ID是唯一标识getId()
- 线程的名称:getName(),如果不设置线程名称默认为“Thread-xx”
- 线程的优先级:getPriority,线程优先级从1-10,其中数字越大表示优先级别越高,同时获得JVM调度执行的可能性越大
- 线程池执行
|
|
一般不推荐设置线程的优先级,如果进行设置了非法的优先级程序就会出现IllegalArgumentException异常。
- 继承Thread类。覆盖Thread类中的run方法。调用start方法开启线程
- 实现Runnable接口,覆盖run方法,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。
- 实现Callable接口
Q&A
sleep 和 yield的不同之处:
- sleep(long)方法会使线程转入超时等待状态,时间到了之后才会转入就绪状态。而yield()方法不会将线程转入等待,而是强制线程进入就绪状态。
- 使用sleep(long)方法需要处理异常,而yield()不用。
停止线程
- run方法执行完成,自然终止。
- stop()方法,suspend()以及resume()都是过期作废方法,使用它们结果不可预期。
- 大多数停止一个线程的操作使用Thread.interrupt()等于说给线程打一个停止的标记, 此方法不回去终止一个正在运行的线程,需要加入一个判断才能可以完成线程的停止。
interrupted 和 isInterrupted
- interrupted : 判断当前线程是否已经中断,会清除状态。
- isInterrupted :判断线程是否已经中断,不会清除状态。
Java线程有两种
- 一种是用户线程
- 一种是守护线程。
守护线程是一个比较特殊的线程,主要被用做程序中后台调度以及支持性工作。当Java虚拟机中不存在非守护线程时,守护线程才会随着JVM一同结束工作。典型的守护线程-GC(垃圾回收器)。启动线程之前设置 Thread.setDaemon(true);
锁的升降级规则 Java SE 1.6 为了提高锁的性能。引入了“偏向锁”和轻量级锁“。
Java SE 1.6 中锁有4种状态。级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。
锁只能升级不能降级。
偏向锁
大多数情况,锁不仅不存在多线程竞争,而且总由同一线程多次获得。当一个线程访问同步块并获取锁时,会在对象头和栈帧中记录存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行 cas操作来加锁和解锁,只需测试一下对象头 Mark Word里是否存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁,如果失败,则需要测试下Mark Word中偏向锁的标示是否已经设置成1(表示当前时偏向锁),如果没有设置,则使用cas竞争锁,如果设置了,则尝试使用cas将对象头的偏向锁只想当前线程。
轻量级锁
线程在执行同步块,jvm会现在当前线程的栈帧中创建用于储存锁记录的空间。并将对象头中的Mark Word复制到锁记录中。然后线程尝试使用cas将对象头中的Mark Word替换为之乡锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。
Java如何实现原子操作
Java中通过锁和循环cas的方式来实现原子操作,JVM的CAS操作利用了处理器提供的CMPXCHG指令来实现的。自旋CAS实现的基本思路就是循环进行CAS操作直到成功为止。
Condition接口
提供了类似Object监视器方法,与 Lock配合使用实现等待/通知模式。
|
|
实际上,Java 的BlockingQueue接口的实现类中都采用了这种方式,在集合内部针对 put 和 take 有不同的条件 Condition,保证了put完成后,只有调用take的线程被唤醒,take完成后,只有调用put的线程被唤醒。
|
|