线程相关—锁

1.闭锁CountDownLatch

闭锁:想实现它管理的线程都执行完后,在执行其它线程。
在调用构造方法创建CountDownLatch对象时需要指定管理线程的个数(计数器的值)。
await():会产生阻塞,直到计数器减为0的时候才会释放。
countDown():每调用一次,会将计数器–。

例子:锅和菜买回来之后,才可以输出开始做饭

//两个线程类
class BuyGuo implements Runnable{
    private CountDownLatch cdl ;
    public BuyGuo(CountDownLatch cdl){
        this.cdl = cdl;
    }
    public void run(){
        System.out.println("锅买回来了...");
        cdl.countDown();
    }
}
class BuyCai implements Runnable{
    private CountDownLatch cdl;
    public BuyCai(CountDownLatch cdl){
        this.cdl = cdl;
    }
    public void run(){
        System.out.println("菜买回来了...");
        cdl.countDown();
    }
}
//主方法
public static void main(String[] args) {
        //创建闭锁,管理2个线程
        CountDownLatch cdl = new CountDownLatch(2);
        //将闭锁传入。
        new Thread(new BuyGuo(cdl)).start();
        new Thread(new BuyCai(cdl)).start();
        try {
            //阻塞,直到上述2个线程执行完毕再继续执行。
            cdl.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("开始做饭...");
    }

2.栅栏 CyclicBarrier

也需要在创建时确定管理线程个数,await()方法是加法了,产生阻塞并且计数器加一,当加到管理线程的个数时,释放阻塞。

public class DemoCyclicBarrier {
    public static void main(String[] args) {
        CyclicBarrier cb = new CyclicBarrier(2);
        new Thread(new Horse1(cb)).start();
        new Thread(new Horse2(cb)).start();

    }
}
class Horse1 implements Runnable{
    private CyclicBarrier cb;
    public Horse1(CyclicBarrier cb){
        this.cb = cb;
    }
    public void run(){
        System.out.println("第一匹马来到起跑线,做好了准备..");
        try {
        //阻塞,知道计数器为2时才释放。
            cb.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
        System.out.println("第一匹马开始比赛...跑起");
    }
}
class Horse2 implements Runnable{
    private CyclicBarrier cb;
    public Horse2(CyclicBarrier cb){
        this.cb = cb;
    }
    public void run(){
        System.out.println("第二匹马正在拉肚子ing....");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("第二匹马来到了起跑线,准备好...");
        try {
        //阻塞,知道计数器为2时才释放。
            cb.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
        System.out.println("第二匹马开始比赛...跑起");
    }
}
//两匹马同时起跑~~

CountDownLatch和CyclicBarrier的主要联系和区别如下:(这段转自千山独行大大的博客~)
1.闭锁CountDownLatch做减计数,而栅栏CyclicBarrier则是加计数。
2.CountDownLatch是一次性的,CyclicBarrier可以重用。
3.CountDownLatch强调一个线程等多个线程完成某件事情。CyclicBarrier是多个线程互等,等大家都完成。
4.鉴于上面的描述,CyclicBarrier在一些场景中可以替代CountDownLatch实现类似的功能。

3.交换机 Exchanger

public class DemoExchanger {
    public static void main(String[] args) {
    //创建交换机。
        Exchanger<String> exc = new Exchanger<String>();
        new Thread(new Spy1(exc)).start();
        new Thread(new Spy2(exc)).start();
    }
}
class Spy1 implements Runnable{
    private Exchanger<String> exc;
    public Spy1(Exchanger<String> exc){
        this.exc = exc;
    }
    public void run(){
        //返回的是线程2传给线程1的内容
        try {
        //该方法给线程传信息,并接收对方传的信息。
            String msg = exc.exchange("天王盖地虎");
            System.out.println("间谍2传给间谍1的信息:"+msg);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
class Spy2 implements Runnable{
    private Exchanger<String> exc;
    public Spy2(Exchanger<String> exc){
        this.exc = exc;
    }
    public void run(){
        //返回的是线程2传给线程1的内容
        try {
        //该方法给线程传信息,并接收对方传的信息。
            String msg = exc.exchange("宝塔镇河妖");
            System.out.println("间谍1传给间谍2的信息:"+msg);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

4.锁—lock

之前线程安全问题需要解决,都会用synchronized同步代码块,这个方法我们还需要指定锁的钥匙,不够灵活。

synchronized:
假设切换线程时,线程1运行一次,然后等待,唤醒线程2,线程2运行。这期前假设运行时间1s,唤醒时间2秒。那么运行2次的时间一共是4秒。
lock:lock在唤醒期间,如果线程2没醒会多次运行,线程1运行1秒后,线程2处再唤醒中,那么线程1会在唤醒的2秒中继续运行2次,等线程2唤醒后,线程2运行。那么4秒的时间一共运行了4次。

lock代码具体如下:

public class TestDemo3 {
    public static String name="李雷";
    public static String gender = "男";
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        new Thread(new ReadRunner3(lock)).start();
        new Thread(new WriteRunner3(lock)).start();
    }
}
class ReadRunner3 implements Runnable{
    private Lock lock;
    public ReadRunner3(Lock lock){
        this.lock = lock;
    }
    public void run(){
        while(true){
            //添加锁
            lock.lock();
                System.out.println(TestDemo3.name+","+TestDemo3.gender);
            //释放锁,如果涉及到异常处理的代码,该行代码一定要放在finally中
            lock.unlock();
        }
    }
}
class WriteRunner3 implements Runnable{
    private Lock lock;
    public WriteRunner3(Lock lock){
        this.lock= lock;
    }
    public void run(){
        while(true){
            lock.lock();
                if("李雷".equals(TestDemo3.name)){
                    TestDemo3.name = "韩梅梅";
                    TestDemo3.gender ="女";
                }else{
                    TestDemo3.name="李雷";
                    TestDemo3.gender="男";
                }
            lock.unlock();
        }
    }
}

读写锁ReadWriteLock
读写锁分为读锁和写锁,与Lock的区别就是,读锁和读锁之间可以共存,如果是对一条数据的多个请求的读操作,不会进行锁定,但是读锁和写锁,写锁和写锁会被锁定。

代码升级:

public class TestDemo4 {
    public static String name="李雷";
    public static String gender = "男";
    public static void main(String[] args) {
        ReadWriteLock lock = new ReentrantReadWriteLock ();
        new Thread(new ReadRunner4(lock)).start();
        new Thread(new WriteRunner4(lock)).start();
    }
}
class ReadRunner4 implements Runnable{
    private ReadWriteLock lock;
    public ReadRunner4(ReadWriteLock lock){
        this.lock = lock;
    }
    public void run(){
        while(true){
            //添加锁
            lock.readLock().lock();
                System.out.println(TestDemo4.name+","+TestDemo4.gender);
            //释放锁,如果涉及到异常处理的代码,该行代码一定要放在finally中
            lock.readLock().unlock();
        }
    }
}
class WriteRunner4 implements Runnable{
    private ReadWriteLock lock;
    public WriteRunner4(ReadWriteLock lock){
        this.lock= lock;
    }
    public void run(){
        while(true){
            lock.writeLock().lock();
                if("李雷".equals(TestDemo4.name)){
                    TestDemo4.name = "韩梅梅";
                    TestDemo4.gender ="女";
                }else{
                    TestDemo4.name="李雷";
                    TestDemo4.gender="男";
                }
            lock.writeLock().unlock();
        }
    }
}

5.原子性AtomicInteger

如果设置一个静态成员变量,2个线程分别从1加到100000,那么结束后输出最后的结果,会是多少呢?
会是200000嘛?不会,因为线程安全没有任何措施,导致会有重复的相加,即两个线程同时加完后结果只加了1.而不是加2.
添加synchronized同步代码块或锁可以解决该问题,我们还有别的解决方案,原子性AtomicInteger

public class DemoAtomic {
    //新建原子性变量。
    public static AtomicInteger num =
            new AtomicInteger(0);
    public static void main(String[] args) {
    //闭锁,全部完成后再输出结果
        CountDownLatch cdl = new CountDownLatch(2);
        new Thread(new AddRunner(cdl)).start();
        new Thread(new AddRunner(cdl)).start();
        try {
            cdl.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(num);
    }
}
class AddRunner implements Runnable{
    private CountDownLatch cdl;
    public AddRunner(CountDownLatch cdl){
        this.cdl = cdl;
    }
    public void run(){
        for (int i = 0; i < 100000; i++) {
            //每次加一 相当于num++
            DemoAtomic.num.getAndAdd(1);
        }
        cdl.countDown();
    }
}

其源码的逻辑是每次加完后会进行安全检查,如果发现重复相加则再加一次,直至没有重复相加。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值