1.显式锁介绍
java中有显示锁与隐藏锁:显示锁是可以有JavaApi来控制的锁属于(Api级别),隐藏锁属于java内置语法比如Synchronized同步锁,隐藏的获取锁跟释放锁如果现在我想拿到Synchronized正在等待的线程是拿不到的我不知道内部的状态,这个锁就是隐藏锁属于(JVM级别)
显示锁的用法:
lock():获取锁
lockInterruptibly(): 当有两个线程同时获取锁时,会有一个线程拿不到锁直接把这个拿不到锁的线程停止运行
tryLock():尝试获取锁获取到了返回true,获取不到返回false,就是去拿锁的一个方法lock()内部调用了他
tryLock(long time, TimeUnit unit):在指定时间内获取锁当前线程会存在3种情况返回
1.当前线程在指定时间内获取到了锁
2.当前线程被调用中断方法进行了中断
3.没有在指定时间内获取到锁
unlock():释放锁
基础用法代码:
public class LockBasicMain {
//使用重入锁(递归锁)
private static Lock lock=new ReentrantLock();
static int i=0;
private static void accumulate() throws InterruptedException {
lock.lock();
for (int j = 0; j < 1000; ++j) {
i=i+1;
}
lock.unlock();
}
public static void main(String[] args) {
for (int j = 0; j < 10; ++j) {
new Thread(()->{
try {
accumulate();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
while (Thread.activeCount()>1){
Thread.yield();
}
System.out.println("合计:"+i);
}
}
读写锁用法代码:
public class Commodity {
private int length;
private String name;
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class ReadWriteLockMain {
static Commodity commodity=new Commodity();
static ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
static Lock readLock=readWriteLock.readLock();
static Lock writeLock=readWriteLock.writeLock();
static int readThreadSize=10;
static int writeThreadSize=3;
//测试重入独占读
static Lock lock=new ReentrantLock();
public static void selectLength(){
readLock.lock();
try {
Thread.sleep(100);
System.out.println("当前商品"+commodity.getName()+"数量为:"+commodity.getLength());
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
readLock.unlock();
}
}
public static void writeLength(){
writeLock.lock();
try {
Thread.sleep(100);
commodity.setLength(commodity.getLength()-1);
System.out.println("已购买当前商品"+commodity.getName()+",剩余数量为:"+commodity.getLength());
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
writeLock.unlock();
}
}
public static void main(String[] args) {
commodity.setName("立白洗衣粉.这个东西特么真好用");
commodity.setLength(3);
for (int i = 0; i < writeThreadSize; ++i) {
new Thread(()->{
long recordTime=System.currentTimeMillis();
writeLength();
System.out.println("当前线程运行总时长:"+(System.currentTimeMillis()-recordTime));
}).start();
for (int j = 0; j < readThreadSize; ++j) {
new Thread(()->{
long recordTime1=System.currentTimeMillis();
selectLength();
System.out.println("当前线程运行总时长:"+(System.currentTimeMillis()-recordTime1));
}).start();;
}
}
}
使用Lock显示锁的 Condition 线程状态监控管理类用法代码:
public class ConditionMain {
Lock lock=new ReentrantLock();
Condition condition=lock.newCondition();
public int money=0;
public void labor(){
lock.lock();
try {
while (money==0){
condition.await();
}
System.out.println("感谢老板大发慈悲,微信红包已到账:"+money+"元");
money=0;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void boss() {
lock.lock();
money=100;
System.out.println("农民工同志,我大发慈悲的给你发工资了,你点一下红包!");
condition.signalAll();
lock.unlock();
}
public static void main(String[] args) {
ConditionMain conditionMain=new ConditionMain();
for (int i = 0; i < 100; ++i) {
new Thread(()->{
if(conditionMain.money==0){
conditionMain.boss();
}else{
conditionMain.labor();
}
}).start();
}
}
}
2.CLH队列锁解析
CLH队列锁:就跟Java的单向链表队列一样,排成一条长长的队伍,第一个线程执行完成了,第二个线程通过自旋的方式看第一个线程有没有释放锁有释放锁,我就可以拿到锁,后面等待的线程都是如此,节点中存在队列中的第一个节点,跟最后一个节点,上一个节点的信息.
AQS(抽象排队同步器):就是对现在个人PC,或者服务器系统用的CLH队列锁做了一个变种的实现,就是根据CLH队列锁队列的一套思想来实现的抽象排队同步器,这个AQS是采用了双向链表的结构来管理节点的并且节点中可以存储更多的一些值,这个就是对CLH队列锁的一个封装实现,AQS具体就是一个同步化的一个组件,只有集成了他就可以实现自己做一个自己想要的锁
我之前一直有个疑问:现在明白了,为什么synchronized中有两个线程如果时间点一模一样就是两个线程获取锁拿锁全不一致的情况下为什么不会有两个线程进入synchronized方法,原理是由硬件控制了CPU处理器的CAS指令如果有一个线程在写线程共享资源他会阻塞住另外一个线程不让他写,就是一个串行执行的过程,所以不会出现两个线程改一个值的情况发生
3.AQS使用
AQS继承实现锁使用注意:
变量:
state:状态变量记录线程数量,可以由自己设置,让自己进行拿锁跟释放锁的逻辑操作
访问或修改同步状态的方法:
getState():获取当前同步状态
compareAndSetState(int expect,int update):使用CAS设置当前状态,该方法能够保证状态设置的原子性
实现独占锁具体代码:
public class ExclusiveLock implements Lock {
static class Sync extends AbstractQueuedSynchronizer {
@Override /** 判断当前锁是否处于占用状态 **/
protected boolean isHeldExclusively() {
return getState()>0;
}
@Override /** 进行拿锁操作 **/
protected boolean tryAcquire(int arg) {
if(compareAndSetState(0,arg)){
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override /** 进行释放锁操作 **/
protected boolean tryRelease(int arg) {
if(getState()==0){
throw new IllegalMonitorStateException();
}
setExclusiveOwnerThread(null);
setState(arg);
return true;
}
/** 返回当前锁的线程状态监控条件集合 **/
Condition newCondition(){
return new ConditionObject();
}
}
private final Sync sync=new Sync();
@Override
public void lock() {
sync.acquire(1);
System.out.println(Thread.currentThread().getName()+"已经拿到锁了");
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1,unit.toNanos(time));
}
@Override
public void unlock() {
sync.release(0);
System.out.println(Thread.currentThread().getName()+"已经释放锁了");
}
@Override
public Condition newCondition() {
return sync.newCondition();
}
}
public class AqsExclusiveMain {
static class sum{
static Lock lock=new ExclusiveLock();
static int i=0;
public static void accumulate(){
lock.lock();
i=i+1;
lock.unlock();
}
}
public static void main(String[] args) {
for (int i = 0; i < 1000; ++i) {
new Thread(()->{
sum.accumulate();
}).start();
}
while (Thread.activeCount()>1){
Thread.yield();
}
System.out.println("合计:"+sum.i);
}
}
实现共享锁具体代码:
public class SharedLock implements Lock {
//具体实现最多同时是能有5个线程可以拿到锁
static class Sync extends AbstractQueuedSynchronizer{
/** 设置最大线程数量 **/
protected Sync(int max) {
if(max <= 0){
throw new IllegalMonitorStateException();
}
setState(max);
}
@Override /** 采用自旋拿取锁操作 **/
protected int tryAcquireShared(int arg) {
for (;;){
int state=getState();
int modify=state-arg;
if(modify<0 || compareAndSetState(state,modify)){
return modify;
}
}
}
@Override /** 采用自旋释放锁操作 **/
protected boolean tryReleaseShared(int arg) {
for (;;){
int state=getState();
int modify=state+arg;
if(compareAndSetState(state,modify)){
return true;
}
}
}
@Override
protected boolean isHeldExclusively() {
return getState()>0;
}
Condition newCondition(){
return new ConditionObject();
}
}
private final Sync sync=new Sync(5);
@Override
public void lock() {
sync.acquireShared(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquireShared(1) >= 0;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireSharedNanos(1,unit.toNanos(time));
}
@Override
public void unlock() {
sync.releaseShared(1);
}
@Override
public Condition newCondition() {
return sync.newCondition();
}
}
public class AqsSharedLockMain {
static class sum{
static Lock lock=new SharedLock();
static int i=1000;
public static void accumulate(){
lock.lock();
try {
Thread.sleep(1000);
System.out.println("我中将了得了"+i+"块");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
for (int i = 0; i < 100; ++i) {
new Thread(()->{
long recordTime=System.currentTimeMillis();
sum.accumulate();
System.out.println("当前线程读取消耗的时间:"+(System.currentTimeMillis()-recordTime));
}).start();
}
}
}
4.AQS深入源码:
Node节点类重要的变量
线程的 2 种等待模式:
SHARED: 表示线程以共享的模式等待锁(如 ReadLock)
EXCLUSIVE: 表示线程以互斥的模式等待锁(如 ReetrantLock,互斥就是一 把锁只能由一个线程持有,不能同时存在多个线程使用同一个锁
线程在队列中的状态枚举:
CANCELLED: 值为 1,表示线程的获锁请求已经“取消”
SIGNAL: 值为-1,表示该线程一切都准备好了,就等待锁空闲出来给我
CONDITION: 值为-2,表示线程等待某一个条件(Condition)被满足
PROPAGATE: 值为-3,当线程处在“SHARED”模式时,该字段才会被使用 上 初始化
Node对象时: 默认为 0
waitStatus: 该 int 变量表示线程在队列中的状态,其值就是上述提到的 CANCELLED、SIGNAL、CONDITION、PROPAGATE
prev: 该变量类型为 Node 对象,表示该节点的前一个 Node 节点(前驱)
next: 该变量类型为 Node 对象,表示该节点的后一个 Node 节点(后继)
thread: 该变量类型为 Thread 对象,表示该节点的代表的线程
nextWaiter:
介绍:AQS中阻塞队列采用的是用双向链表保存,用prve和next相互链接,而AQS中条件队列是使用单向列表保存的,用nextWaiter来连接,阻塞队列和条件队列并不是使用的相同的数据结构。
nextWaiter实际上标记的就是在该节点唤醒后依据该节点的状态判断是否依据条件唤醒下一个节点。
SHARED(共享模式)直接唤醒下一个节点
EXCLUSIVE(独占模式)等待当前线程执行完成后再唤醒
其他非空值依据条件决定怎么唤醒下一个线程。类似semaphore中控制几个线程通过
当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构 造成为一个节点(Node)并将其加入同步队列,同时会阻塞当前线程,当同步 状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。同步队列 中的节点(Node)用来保存获取同步状态失败的线程引用、等待状态以及前驱 和后继节点。
AQS 还拥有首节点(head)和尾节点(tail)两个引用,一个指向队列头节 点,而另一个指向队列尾节点。