更多并发相关内容,查看==>Java 线程&并发学习目录
进程是资源分配的最小单元,线程是程序执行的最小单元,通常在一个进程中包含了多个线程。单个CPU在一个时刻只能把时间片分配给一个线程去执行。线程的整个生命周期包含了多个状态,未运行的初始状态,启动后未获取CPU时间片的待运行状态,任务执行完成后的消亡态,和线程紧密工作的锁又是如何控制线程的进入和退出的,接下来就学习和了解下Java的线程和锁的基本特点。
1、线程Thread 生命周期 & 基本使用
线程类中包含了多种状态,可通过getState
方法获取到State枚举类信息
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
NEW:线程还未开始的状态
RUNNABLE:在JVM中是运行的状态,但真正执行需要操作系统分配必须的资源才可运行
BLOCKED:等待Monitor Lock(监视器锁) 被阻塞导致,例如synchronized
WAITING:因为非时间因素导致线程等待的,例如Object.wait()、Thread.join()、LockSupport.park()等
TIMED_WAITING:因为时间因素导致线程等待的,例如Thread.sleep()、Object.wait(time)、Thread.join(time)等
TERMINATED:线程执行完成,消亡状态了
实际状态流转中每一种状态的变更都是有着其具体的原因,例如Object.wait()方法执行后,线程一方面会自行暂停,另一方面会进入到当前对象的一个等待集合中,等待超时、notify唤醒又或者中断引发的中断时间而离开等待集合,如下图可能可以更好的说明线程的流转关系。
具体方法以及相关关键字在后面再做介绍,先了解下线程如何使用的,线程的使用有两种方法,继承Thread 类和实现Runnable 接口,都是重写run方法,Runnable更多的可以看做是一个Task任务,任务可以交由任何可执行的线程去执行,但是继承Thread的在一创建时就已经固定了,没有足够的灵活性,推荐使用Runnable。
用java8最新的lambda表达式也可以很方便的写成Runnable runnable = () -> {/* run方法运行实体*/}
,然后直接交给Thread执行即可。
请勿直接调用线程的run方法,那样无法创建新线程执行。调用start方法会由native方法创建新线程然后调用run方法
2、锁
现实中我们也大量的应用到锁,为了防止家里的财产被盗,离开家门时会锁门,日常用的电脑为了确保资料安全也会给自己的账户设置个密码锁,不过这也意味着我们日常外出需要带着钥匙,记住密码,要不然会导致很多不必要的麻烦。和实际生活一样,Java中的锁也是为了保证数据(服务)安全,也会对服务的性能、吞吐量等造成影响。
那锁是什么呢?
- Java中的一切都是对象,锁也不例外,这个被锁的对象只能由锁保护资源所在的线程去访问
- 锁可以控制对数据访问的先后顺序(强行串行化)
- 锁可以控制对数据是否拥有访问权
- 锁可以控制服务的活跃性问题
- 锁也可以抑制指令重排(降低效率)
常见的锁种类
- synchronized:Java原生语义支持的,内置锁,可重入,悲观的,在JDK1.5以后synchronized在不同情况下从 无锁->偏向锁->轻量级所->重量级锁的转变。
- ReentrantLock:基于AQS开发的可重入锁,有公平和非公平之分,也可以感知到中断
- CountDownLatch:基于AQS开发的锁,也叫计时器锁,当计数器减为0后,就可以执行其他任务了,不可重置
- CyclicBarrier:同样是基于AQS开发的锁,也叫栅栏锁,有换代的操作,可重置(想象成多个栅栏一般)
- Condition:条件锁,用在阻塞队列中,可控制消费者和生产者的读取进度
- ReentrantReadWriteLock:读写锁,读和写拆开,可以极大的提高读多写少的场景下的性能
对象锁对应的监视器锁结构如下图所示
本图来自:https://www.programcreek.com/2011/12/monitors-java-synchronization-mechanism/
- Special Room:当前运行的线程所存储的地方
- Entry Set: 所有需要获取当前锁的线程存储的地方
- Wait Set:所有被调用wait方法的线程存储的地方
3、Object 的wait 和 notify 方法
wait、notify方法是Object类的final native
方法,不能被重写,而且必须有同步器synchronize的辅助才可以使用(这个是JVM规定的)
synchronized (OBJ) {
try {
while (count <=0) {
// 当前线程暂停执行
OBJ.wait();
}
......
// 具体的任务
OBJ.notify();
// 唤醒其他线程工作
} catch (InterruptedException e1) {
e1.printStackTrace();
// 由wait方法触发的中断
}
}
wait方法是用来释放锁并且暂停服务的,从上面的线程流转图也可以看出来,对象调用wait()方法后,当前执行的线程会进入到当前对象的监视器对象的Wait Set区域内,执行notify方法,会从Wait Set集合中随便挑选一个然后唤醒操作进入到Entry Set集合中,后续的具体执行还得看具体的资源分配等情况,notifyAll方法可以唤醒所有等待的线程,至于那一个现在真正的执行同样也是无法感知的。