Java锁
Thread几个方法
a、start()方法,调用该方法开始执行该线程;
b、stop()方法,调用该方法强制结束该线程执行;
c、join方法,调用该方法等待该线程结束。
d、sleep()方法,调用该方法该线程进入等待。
e、run()方法,调用该方法直接执行线程的run()方法,但是线程调用start()方法时也会运行run()方法,区别就是一个是由线程调度运行run()方法,一个是直接调用了线程中的run()方法
wait()与notify()方法是Object的方法,不是Thread的方法。同时,wait()与notify()会配合使用,分别表示线程挂起和线程恢复
wait()与sleep()的区别,简单来说wait()会释放对象锁而sleep()不会释放对象锁。
线程状态
- 新建状态:新建线程对象,并没有调用start()方法之前
- 就绪状态:调用start()方法之后线程就进入就绪状态,但是并不是说只要调用start()方法线程就马上变为当前线程,在变为当前线程之前都是为就绪状态。值得一提的是,线程在睡眠和挂起中恢复的时候也会进入就绪状态哦。
- 运行状态:线程被设置为当前线程,开始执行run()方法。就是线程进入运行状态
- 阻塞状态:线程被暂停,比如说调用sleep()方法后线程就进入阻塞状态
- 死亡状态:线程执行结束
锁类型
- 可重入锁:在执行对象中所有同步方法不用再次获得锁,synchronized 和 ReentrantLock 都是可重入锁,可重入锁可以在一定程度避免死锁。
- 可中断锁:在等待获取锁过程中可中断
- 公平锁/非公平锁: 公平锁指多个线程按照申请锁的顺序来获取锁,非公平锁就是没有顺序完全随机,所以能会造成优先级反转或者饥饿现象;synchronized 就是非公平锁,ReentrantLock(使用 CAS 和 AQS 实现) 通过构造参数可以决定是非公平锁还是公平锁,默认构造是非公平锁;非公平锁的吞吐量性能比公平锁大好
- 读写锁:对资源读取和写入的时候拆分为2部分处理,读的时候可以多线程一起读,写的时候必须同步地写
- Java加锁有两种:
- Synchronized关键字
- 显示Lock
Synchronized
是Java的一个关键字,它能够将代码块(方法)锁起来
public synchronized void test() {
// doSomething
}
synchronized是一种互斥锁
,一次只能允许一个线程进入被锁住的代码块
synchronized是一种内置锁/监视器锁
,Java中每个对象都有一个内置锁(监视器,也可以理解成锁标记),而synchronized就是使用对象的内置锁(监视器)来将代码块(方法)锁定的
- synchronized保证了线程的
原子性
。(被保护的代码块是一次被执行的,没有任何线程会同时访问) - synchronized还保证了
可见性
。(当执行完synchronized之后,修改后的变量对其他的线程是可见的) - synchronized实现原理:
synchronized是悲观锁
,在字节码层被映射成两个指令:monitorenter
和monitorexit
,当一个线程遇到monitorenter
指令时,会尝试去获取锁,如果获取成功,锁的数量+1,(因为synchronized
是一个可重入锁,需要使用锁计数来判断锁的情况),如果没有获取到锁,就会阻塞;当线程遇到monitorexit
指令时,锁计数-1,当计数器为0时,线程释放锁;如果线程遇到异常,虚拟机会释放锁
Lock
Lock完全用Java写成,在java这个层面是无关JVM实现的。在
java.util.concurrent.locks
包中有很多Lock的实现类,常用的有ReentrantLock
、ReadWriteLock
(实现类ReentrantReadWriteLock
),其实现都依赖java.util.concurrent.AbstractQueuedSynchronizer
类,实现思路都大同小异,因此我们以ReentrantLock作为讲解切入点
- lock是一个java接口,一般使用ReentrantLock这个实现类。
- 经过观察ReentrantLock把所有Lock接口的操作都委派到一个Sync类上,该类继承了AbstractQueuedSynchronizer
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer
public void lock() {
sync.lock();
}
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
使用lock显示加锁,可以判断锁的状态,有多种获取锁的方式,获取失败的线程不一定需要阻塞
//需要参与同步的方法
private void method(Thread thread){
lock.lock();
try {
System.out.println("线程名"+thread.getName() + "获得了锁");
}catch(Exception e){
e.printStackTrace();
} finally {
System.out.println("线程名"+thread.getName() + "释放了锁");
lock.unlock();
}
}
- lock实现原理:
lock是使用CAS乐观锁
(CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。)
AQS,抽象队列同步器
AQS即AbstractQueuedSynchronizer的缩写,是并发编程中实现同步器的一个框架,它在内部定义了一个int state变量,用来表示同步状态
AQS基于一个FIFO双向队列实现,被设计给那些依赖一个代表状态的原子int值的同步器使用,在AQS中即为一个叫state的int值,该值通过CAS进行原子修改。
-
在AQS中存在一个FIFO队列,队列中的节点表示被阻塞的线程,队列节点元素有4种类型, 每种类型表示线程被阻塞的原因,这四种类型分别是:
- CANCELLED : 表示该线程是因为超时或者中断原因而被放到队列中
- CONDITION : 表示该线程是因为某个条件不满足而被放到队列中,需要等待一个条件,直到条件成立后才会出队
- SIGNAL : 表示该线程需要被唤醒
- PROPAGATE : 表示在共享模式下,当前节点执行释放release操作后,当前结点需要传播通知给后面所有节点
-
由于一个共享资源同一时间只能由一条线程持有,也可以被多个线程持有,因此AQS中存在两种模式,如下
- 独占模式: 独占模式表示共享状态值state每次只能由一条线程持有,其他线程如果需要获取,则需要阻塞,如JUC中的ReentrantLock
- 共享模式:共享模式表示共享状态值state每次可以由多个线程持有,如JUC中的CountDownLatch