线程基础(二十一)-并发容器-ArrayBlockingQueue(上)

本文作者:王一飞,叩丁狼高级讲师。原创文章,转载请注明出处。

在正式讲解ArrayBlockingQueue类前,先来科普一下线程中各类锁,只有了解这些锁之后,理解ArrayBlockingQueue那就更轻松了。

可重入锁

一种递归无阻塞的同步机制,也叫做递归锁。简单讲一个线程获取到锁对象之后,还是可以再次获取该锁对象时,不会发生阻塞。

java中 synchronized 跟ReentrantLock 都是可重入锁, synchronized 为隐性, 而ReentrantLock 为显示。 下面以synchronized 为例:

public class ThreadDemo {
    public synchronized void method1(){
        System.out.println(Thread.currentThread().getName() + "进入method1....");
        try {
            Thread.sleep(1000);
            method2();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void method2(){
        System.out.println(Thread.currentThread().getName() + "进入method2....");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class App {
    public static void main(String[] args) {
        ThreadDemo threadDemo = new ThreadDemo();
        new Thread(new Runnable() {
            @Override
            public void run() {
                threadDemo.method1();
            }
        }, "t1").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                threadDemo.method2();
            }
        }, "t2").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                threadDemo.method2();
            }
        }, "t3").start();
    }
}

运行结果

上面代码, t1线程先进入method1, 1s之后进入method2, 因为使用synchronized 加锁,t2, t3线程无法进入method2,必须等,而t1线程执行完method1后,可以直接进入method2, 无需要重复获取锁的操作。
改进: 可以再多开几个线程访问method2方法,会发现结果是一致的。

不可重入锁

不可重入锁是跟重入锁是对立的,表示一线程获取到锁对象后,想再次获取该锁对象时,必须先释放之前获取锁对象,否则阻塞等待。
java中没有线程类实现不可重入锁,更多时候,需要我们编程实现。

//自定义锁对象模拟不可重入锁
public class MyLock {
    private boolean isLock = false;
    //模拟获取锁
    public synchronized void lock() throws InterruptedException {
        //自旋排除一些硬件执行干扰
        while(isLock){
            wait();
        }
        isLock = true;
    }
    //模拟释放锁
    public synchronized  void unLock(){
        isLock = false;
        notify();
    }
}
public class ThreadDemo {
    private MyLock lock = new MyLock();

    public void method1() throws InterruptedException {
        lock.lock();
        System.out.println("method1...in");
        method2();
        System.out.println("method1...out");
        lock.unLock();
    }

    public void method2() throws InterruptedException {
        lock.lock();
        System.out.println("method2......");
        lock.unLock();
    }
}
public class App {
    public static void main(String[] args) throws InterruptedException {
        ThreadDemo demo = new ThreadDemo();
        demo.method1();
    }
}

运行之后, 打印只有method1...in, 在执行method1时,调用lock.lock()方法, isLock标记量改为true,表示锁被持有,跳过循环。 执行method2时,再次执行lock.lock(),isLock标签为true, 进入循环,线程等待。模拟拉当锁已经被持有,同一个线程第二次申请同一把锁,需要等待。

互斥锁

同一个时刻,只允许获取到所对象的线程执行。synchronized ReentrantLock 本身就是互斥锁。

public class ThreadDemo {
    //同一个时刻只允许一个线程进入
    public synchronized void method1(){
        System.out.println(Thread.currentThread().getName() + "进入....");
    }
}
public class ThreadDemo {
    private ReentrantLock lock = new ReentrantLock();
    //同一个时刻只允许一个线程进入
    public  void method1(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "进入...."); 
        }finally {
            lock.unlock();
        }
    }
}

自旋锁

一种非阻塞锁,也就是说,如果某线程需要获取锁,但锁已经被线程占用时,线程不阻塞等待,而是通过空循环来消耗CPU时间片,等待其他线程释放锁。注意,自旋锁中的循环也不是瞎循环, 一般会设置一定循环次数或者循环跳出条件。
自旋锁运用非常广泛, jdk中的juc包原子操作类中都是, 比如: AtomicInteger

public class AtomicInteger extends Number implements java.io.Serializable {
   public final int getAndSet(int newValue) {
        return unsafe.getAndSetInt(this, valueOffset, newValue);
    }
}

Unsafe类

public final int getAndSetInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var4));

        return var5;
 }

偏向锁 / 轻量级锁 / 重量级锁

偏向锁,轻量锁,重量锁并不是对线程加锁机制,而是jdk1.6之后提出的对线程加锁的优化策略。
偏向锁:当线程没有竞争的环境下,需要重复获取某个锁对象时,jvm为减少开销,让线程进入偏向模式,再次获取锁对象时,取消之前已经获取锁同步操作(即一系列的cas判断),直接取得锁对象。如果期间有其他线程参与竞争,则退出偏向模式。

当线程退出偏向模式最后,进入轻量级锁模式。此时,线程尝试使用自旋方式来获取锁,如果获取成功,继续逻辑执行, 如果获取失败,表示当前锁对象存在竞争,锁就会膨胀成重量级锁。

当线程进入重量级锁模式, 所有操作就跟我们所认知那样,争夺CUP,在操作过程中,争夺失败线程会被操作系统挂起,阻塞等待。那么线程间的切换和调用成本就会大大提高,性能也就对应下降了。

乐观锁

顾名思义,就是很乐观,在获取数据时认为别人不会对数据做修改,所以不上锁,但是在更新的时候会先判断别人有没有更新了此数据,最通用的实现是使用版本号判断方式,java的juc并发包中的原子操作类使用的CAS机制,其实也是一种乐观锁实现。

悲观锁

与乐观锁是相对的,操作前都假设最坏的情况,在获取数据的认为别人会对数据做修改,所以每次操作前都会上锁。而别人想操作此数据就会阻塞直到它拿到锁。Java中synchronized关键字ReentrantLock的实现便是悲观锁。

公平锁

公平锁,讲究公平,当锁对象被占用时,参与锁对象争夺的线程按照FIFO的顺序排序等待锁释放,人人有机会,不争不抢。

public class Resource implements Runnable {

    private ReentrantLock lock = new ReentrantLock(true);  //公平锁
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 进入了.....");
        lock.lock(); //争锁
        try {
            System.out.println(Thread.currentThread().getName() + " 获取锁并执行了.....");
        }finally {
            lock.unlock();
        }
    }
}
public class App {
    public static void main(String[] args) throws InterruptedException {

        Resource resource = new Resource(); //共享资源
        for (int i = 0; i <10; i++) {
            new Thread(resource, "t_" + i).start();
        }
    }
}
t_4 进入了.....
t_2 进入了.....
t_4 获取锁并执行了.....
t_7 进入了.....
t_6 进入了.....
t_3 进入了.....
t_5 进入了.....
t_2 获取锁并执行了.....
t_7 获取锁并执行了.....
t_6 获取锁并执行了.....
t_3 获取锁并执行了.....
t_5 获取锁并执行了.....
t_0 进入了.....
t_0 获取锁并执行了.....
t_1 进入了.....
t_1 获取锁并执行了.....
t_8 进入了.....
t_8 获取锁并执行了.....
t_9 进入了.....
t_9 获取锁并执行了.....

观察执行结果,当锁是公平锁时(new ReentrantLock(true))会发现进入顺序是4,2,7,6,3,5,0,1,8,9 而执行的顺序是4,2,7,6,3,5,0,1,8,9。两者顺序一样,这就是公平的体现,谁先来,谁先执行。

非公平锁

与公平锁相对,当锁对象被释放时,所有参与争夺锁对象的线程各凭本事,撑死胆大的,饿死胆小的。

其他代码不变,仅仅将参数改为false或者去掉
private ReentrantLock lock = new ReentrantLock(false);  //非公平锁
t_4 进入了.....
t_2 进入了.....
t_5 进入了.....
t_3 进入了.....
t_4 获取锁并执行了.....
t_7 进入了.....
t_7 获取锁并执行了.....
t_0 进入了.....
t_1 进入了.....
t_0 获取锁并执行了.....
t_8 进入了.....
t_2 获取锁并执行了.....
t_9 进入了.....
t_9 获取锁并执行了.....
t_6 进入了.....
t_5 获取锁并执行了.....
t_3 获取锁并执行了.....
t_1 获取锁并执行了.....
t_8 获取锁并执行了.....
t_6 获取锁并执行了.....

当锁是非公平锁时(new ReentrantLock(false))进入的顺序与执行顺序不一样啦,这就是非公平锁,争夺CPU各凭本事。

好的,到这线程中的锁知识普及就结束了。

想获取更多技术干货,请前往叩丁狼官网:http://www.wolfcode.cn/all_article.html

猜你喜欢

转载自blog.csdn.net/wolfcode_cn/article/details/84874610