多线程-锁

多线程-锁

锁的种类

锁 分为两种, 1:阻塞锁 2:自旋锁
- 阻塞锁 当一个线程已经持有了锁,那么其他线程则必须等待。在持有锁的线程,释放锁之前,其他线程都要处于空闲状态。当持有锁的线程,释放锁后,其他线程才能获得锁。
- 自旋锁 当第一次看到这个名字的时候,直接被它吓到��。说白了,当一个线程已经持有了锁,那么其他线程则不断的在循环判断一个标志,该标志标识锁是否被释放。如果循环判断的过程中,发现标志位变成“被释放”,那么其他线程就有获取锁的机会。
两种锁的区别
阻塞锁 阻塞这个操作要通过JVM去实现,没有抢到锁的线程是处于挂起状态。而自旋锁,即使锁已经被抢占,这些没抢到锁的线程,也一直在运行,一直在循环判断标志位。

自己实现锁

手动实现自旋锁
为什么是自旋锁,而不是阻塞锁。因为自旋锁根据上面的描述好实现啊。要想实现阻塞锁,就不是这么简单了,这个以后我们再说。
通过上面的自旋锁介绍。要实现自旋锁,需要有一个标志位,这个标志位的作用就是标识当前锁的状态(释放、已被抢占),如果是释放,则代表锁可以被使用,否则锁已被占用,无法被抢占。
��想一想,有一个问题,假如锁被释放的一瞬间,锁的状态被标识为“释放”。有几个线程同时发现锁的状态为“释放”,那就出现,同一时刻,有多个线程同时抢占到了锁,这不是我们想看到的。所以,锁的标志位必须具有原子性,这里我们使用AtomicBoolean.
首先我们先看一个不使用锁的DEMO
public class Test{
public int value;

    public void add(){
        try {
            int temp = value;
            TimeUnit.MILLISECONDS.sleep(1000);
            value = temp + 1;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Test test = new Test();
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.add();
                    System.out.println(test.value);
                }
            }).start();
        }
    }
}

运行结果为上面的代码比较简单,做一个简单说明,开启10个线程,每个线程都对value加1操作。我们想看到结果是,每次线程执行后,value都会加1,但是现在没有。
现在我们实现一个自旋锁。
public class CasMutex {
private AtomicBoolean mutexFlag = new AtomicBoolean(true);// true 代表锁被释放 false 代表所被占用

    /**
     * 抢占锁.
     */
    public void lock(){
        while(!mutexFlag.compareAndSet(true, false)) {//锁被抢到,则跳出while,继续向下执行。否则,一直在While中自旋.
        }
    }

    /**
     * 释放锁.
     */
    public void unLock(){
        mutexFlag.set(true);
    }

    class Test{

    }
}

修改我们Test类的Main方法
public static void main(String[] args) {
Test test = new Test();
CasMutex casMutex = new CasMutex();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try{
casMutex.lock();//加锁
test.add();
System.out.println(test.value);
} finally {
casMutex.unLock();//释放锁,释放锁在finally里面,保证锁一定会被释放
}
}
}).start();
}
}
运行结果:

上面的自旋锁有什么缺点

自旋锁会一直占着CPU,等待获取锁,如果自旋的时间过长,就会影响整体的性能。如果自旋的时间正好是持有锁的时间,那将多好,可是做不到。HotSpot任务最佳的的自旋周期是一个线程上下文切换的时间,但目前并没有做到。
当所有的线程共享一个变量,当这个变量一旦变动,就会导致,所有线程中这个变量的副本失效,所以有可能因为缓存一致性导致流量风暴。缓存一致性模型

所有的CPU缓存通过Bus(总线进行通讯),Bus和主存(内存)通信。当一个变量被多个CPU共享,其中一个CPU的缓存改变,通知其他CPU中缓存的该变量都失效,而通知机制就是通过Bus这个总线来实现。所以当有很多线程共享一个变量就有可能导致总线流量变大(流量风暴)。

CLHLock

为了避免所有线程共享一个变量,又要求能实现锁的功能,这个时候出现了CLHLock,CLHLock有一个头结点和一个尾节点。当有一个线程来竞争锁,就创建一个节点,然后将线程和节点绑定,最后将节点插入到CLHLock的尾节点位置,从而形成一个虚拟队列。线程何时能获取锁的标志是判断前一个节点的状态,而并非判断自身节点的状态

每一个节点都在循环判断上一个节点的状态,此时如果头结点释放锁,设置自己的状态为释放状态,此时头结点下一个节点的线程就会处于激活状态。

CLHLock解决了,所有的线程共享一个变量的问题,但是它仍然无法实现真正的阻塞,也无法实现线程中断等操作,此时AbstractQueuedSynchronizer出厂。

AbstractQueuedSynchronizer

AbstractQueuedSynchronizer(简称AQS)是一种类似CLHLock的结构,不过它的内部实现了更加丰富的语义,包括线程阻塞、共享、中断、定时等。
AQS和CLHLock相比,AQS多了向后节点指向,而不是只有前指向。node节点属性也更多,而不仅仅只有一个status状态。

node节点有五个属性
1. waitStatus: node的状态。该属性有5个值:CANCELLED(1)、SIGNAL(-1)、CONDITION(-2)、PROPAGATE(-3)、0
2. prev: 上一个节点.
3. next: 下一个几点。
4. thread: 绑定的线程
5. nextWaiter: 标识节点的运行模式,该属性有两个值SHARED和EXCLUSIVE

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值