多线程基础(三)JUC并发包:Lock锁、CountDownLath、CyclicBarrier、Semaphore、LockSupport

这篇具有很好参考价值的文章主要介绍了多线程基础(三)JUC并发包:Lock锁、CountDownLath、CyclicBarrier、Semaphore、LockSupport。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、Lock锁

1. 可重入锁 ReentrantLock

我们可以使用 ReentrantLock 来替代 Synchronized锁,实现方法为:

    Lock lock = new ReentrantLock();
    void m2 (){
        try{
            // 加锁
            lock.lock();
            for(int i=0;i<10000;i++){
                count.incrementAndGet();  // 相当于线程安全的 count++
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            // 释放锁
            lock.unlock();
        }
    }

使用Synchronized是自动解锁的。但是使用lock锁,必须使用try-catch-finally包裹,在try中加锁,在finally中释放锁。

2. ReadWriteLock 读写锁

允许同一时刻多个读线程访问,但所有写线程均被阻塞。读写分离,并发性提升。

java中实现类的读写锁为 ReentrantReadWriteLock 类。

import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockTest {
    static Lock lock  = new ReentrantLock();
    private static int value;

    static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    static Lock readLock = readWriteLock.readLock();
    static Lock writeLock = readWriteLock.writeLock();

    public static void read (Lock lock){
        try {
            lock.lock();
            // 模拟读操作
            Thread.sleep(1000);
            System.out.println("read over!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void write (Lock lock,int v){
        try {
            lock.lock();
            // 模拟写操作
            Thread.sleep(1000);
            value = v;
            System.out.println("write over!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        // 使用 互斥锁(排他锁) lock,需要一个一个执行线程
        Runnable readR = () -> read(lock);
        Runnable writeR = () -> write(lock,new Random().nextInt());
        for(int i=0;i<18;i++) new Thread(readR).start();
        for(int i=0;i<2;i++) new Thread(writeR).start();
    }
}

这里我们使用 互斥锁 lock 来去模拟读写操作时会发现,每个线程一个一个执行,只有当前线程执行完毕,下一个线程抢到资源才继续执行。

如果我们使用读写锁进行模拟操作,会发现,读操作几乎是在一瞬间全部执行完毕,没有等待。

    public static void main(String[] args) {
        // 使用 读写锁 readWriteLock,所有的读写操作并发执行,读写不需要等待
        Runnable readLockR = () -> read(readLock);
        Runnable writeLockR = () -> write(writeLock,new Random().nextInt());
        for(int i=0;i<18;i++) new Thread(readLockR).start();
        for(int i=0;i<2;i++) new Thread(writeLockR).start();
    }

3. Lock提供了Synchronized 不具备的特性:

(1)我们可以使用 tryLock 进行尝试锁定,不管锁定与否,方法都将继续执行可以根据 tryLock 的返回值来判定是否锁定,也可以指定tryLock的时间,由于tryLock(time)抛出异常,所以要注意unclock的释放。

(2)使用 lock.lockInterruptibly(); 方法,可以将当前获得锁的线程中断,抛出异常并释放锁,需要配合 ti.interrupt(); 方法使用。

        Thread ti =  new Thread(() -> {
            try {
                lock.lockInterruptibly();
                index.compareAndSet(10, 11);
                index.compareAndSet(11, 10);
                System.out.println(Thread.currentThread().getName()+": 10->11->10");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        ti.interrupt();

(3)初始化一个公平锁

static Lock lock = new ReentrantLock(true);

参数为 true表示为公平锁。当前所有线程在等待队列中,这个时候来了一个新的线程,如果是公平锁,则它会去查看是否有队列在等待,如果有,则让这个新的线程也加入队列等待。如果不是公平锁,这个线程会去抢占资源。

二 、CountDownLath

CountDownLath:允许其他多个线程等待当前latch线程,只有当前线程的latch计数器为0时,才放行,让其他线程执行。

latch.countDown():每执行一次 latch.countDown,计算器减一,直到为0。

latch.await(): latch.await相当于一道门,只有 latch 的计数器为0的时候才放行。

import java.util.concurrent.CountDownLatch;

public class CountDownLathTest {
    private static void usingCountDownLatch() {
        Thread[] threads = new Thread[100];
        // 定义一个CountDownLatch,给定计数器为 threads.length
        CountDownLatch latch = new CountDownLatch(threads.length);
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(() -> {
                int result = 0;
                for (int j = 0; j < 100; j++) result += j;
                // 每执行一次 latch.countDown,计算器减一,直到为0
                latch.countDown();
                System.out.println(Thread.currentThread().getName());
            });
        }
        for (int i = 0; i < threads.length; i++) {
            threads[i].start();
        }
        try {
            // latch.await相当于一道门,只有 latch 的计数器为0的时候才放行
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("latch计数器为0,放行了") ;
    }

    public static void main(String[] args) {
        usingCountDownLatch();
    }
}

CountDownLath 的作用和 join() 作用一样,都是让其他线程等待,先执行指定线程。

    private static void usingJoin() {
        Thread[] threads = new Thread[100];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(() -> {
                int result = 0;
                for (int j = 0; j < 100; j++) result += j;
                System.out.println(Thread.currentThread().getName());
            });
        }
        for (int i = 0; i < threads.length; i++) {
            threads[i].start();
        }
        for (int i = 0; i < threads.length; i++) {
            try {
                // 将线程循环放入,确保执行顺序
                threads[i].join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("join顺序线程执行完毕,到我了") ;
    }

三、CyclicBarrier

CyclicBarrier:可循环使用屏障,定义一个指定瓶颈的CyclicBarrier,当调用 await方法到达这个瓶颈后,就去执行相应的瓶颈方法。

cyclicBarrier.await():如果没有达到计数器瓶颈,就等待

cyclicBarrier.reset():使计数器重置

    public static void main(String[] args) {
        // CyclicBarrier 第二个参数作用是,当达到瓶颈 20 后,去做一些事情
        CyclicBarrier cyclicBarrier = new CyclicBarrier(20, new Runnable() {
            @Override
            public void run() {
                System.out.println("达到了瓶颈,重置");
            }
        });
        for (int i=0;i<100;i++){
            int finalI = i;
            new Thread(() -> {
                try {
                    // 每执行一次cyclicBarrier.await(),相当与 cyclicBarrier的计数器+1,直到等于20,执行瓶颈方法
                    cyclicBarrier.await();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }

    }

CyclicBarrier使用场景:

        1. 限流,当请求数量达到一定数量时,进行限流。

        2. 复杂操作数据库、网络、文件

        3. 并发执行线程-操作线程-操作

 四、Semaphore 信号量

Semaphore :用来控制同时访问资源的线程数量。

可以指定是否为公平锁。

公平锁:获取锁时,如果获取的顺序符合请求的绝对时间顺序,则为公平锁,FIFO。

        // 允许 n 个线程同时执行
        Semaphore semaphore = new Semaphore(1);
        // 可以指定是否为公平锁
        Semaphore semaphore2 = new Semaphore(2,true);
        new Thread(() -> {
            try {
                semaphore.acquire();
                System.out.println("T1 running ...");
                Thread.sleep(200);
                System.out.println("T1 running ...");
                semaphore.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(() -> {
            try {
                semaphore.acquire();
                System.out.println("T2 running ...");
                Thread.sleep(200);
                System.out.println("T2 running ...");
                semaphore.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }

T1 running ...
T1 running ...
T2 running ...
T2 running ...

如果我们把  new Semaphore(1); 改为 new Semaphore(2); 执行结果:

T1 running ...
T2 running ...
T1 running ...
T2 running ...

可以看到,为1时,T2只能等 T1执行完毕,才开始执行。为2时,T1、T2可以同时执行。

主要使用场景为限流,类比高速收费站,5个车道2个收费站。

 五、Exchanger 交换器

 Exchanger:用于线程通信(线程交换数据)的工具类,提供一个同步点,当第一个线程执行了 exchanger()方法,它会一直等待下一个线程也执行 exchanger()方法,然后进行交换数据。可以设置最大等待时间。

import java.util.concurrent.Exchanger;

public class ExchangerTest {
    static Exchanger<String> exchanger = new Exchanger<>();
    public static void main(String[] args) {
        new Thread(() ->{
            String s = "T1";
            try {
                // 设置多大等待时间
                // s = exchanger.exchange(s,2000, TimeUnit.SECONDS);
                s = exchanger.exchange(s);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+" "+s);
        },"t1").start();

        new Thread(() ->{
            String s = "T2";
            try {
                s = exchanger.exchange(s);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+" "+s);
        },"t2").start();
    }
}

t2 T1
t1 T2

使用场景如:游戏中俩个人交换装备。 

 六、LockSupport 工具

 LockSupport:定义了一组静态方法,提供了最基本的线程阻塞和唤醒功能。

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

public class LockSupportTest {
    public static void main(String[] args) {
        Thread t = new Thread(() ->{
            for(int i =0;i<10;i++){
                System.out.println(i);
                if(i == 5){
                    // 阻塞当前线程
                    LockSupport.park();
                }
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        // 唤醒指定线程
        // LockSupport.unpark(t);
    }
}

0
1
2
3
4
5

 由于 LockSupport.park(); 当循环到5的时候,阻塞当前线程。

将 LockSupport.unpark(t); 打开后,唤醒线程,完整打印。

0
1
2
3
4
5
6
7
8
9

当我们查看 part()方法的实现后发现,这个方法是 Unsafe 类实现的。还记得之前 CAS 中说到的这个类吗?

        所有以 AtomXXX开头的类,底层都是使用cas方法,并是通过 Unsafe类实现的。

Unsafe类的出现等于c/c++的指针,给 java语言赋予了原来 C/C++实现的指针方法。

    /**
     * Disables the current thread for thread scheduling purposes unless the
     * permit is available.
     *
     * <p>If the permit is available then it is consumed and the call
     * returns immediately; otherwise the current thread becomes disabled
     * for thread scheduling purposes and lies dormant until one of three
     * things happens:
     *
     * <ul>
     *
     * <li>Some other thread invokes {@link #unpark unpark} with the
     * current thread as the target; or
     *
     * <li>Some other thread {@linkplain Thread#interrupt interrupts}
     * the current thread; or
     *
     * <li>The call spuriously (that is, for no reason) returns.
     * </ul>
     *
     * <p>This method does <em>not</em> report which of these caused the
     * method to return. Callers should re-check the conditions which caused
     * the thread to park in the first place. Callers may also determine,
     * for example, the interrupt status of the thread upon return.
     */
    public static void park() {
        UNSAFE.park(false, 0L);
    }

翻译:

出于线程调度目的禁用当前线程,除非允许可用。
如果许可证可用,则将其使用,呼叫立即返回;否则,当前线程将出于线程调度目的而被禁用并处于休眠状态,直到发生以下三种情况之一:
其他一些线程以当前线程作为目标进行调用 unpark ;或者
其他线程 中断 当前线程;或
虚假调用(即无缘无故)返回。
此方法 不会 报告哪些导致该方法返回。调用方应重新检查导致线程首先停放的条件。例如,调用方还可以确定线程在返回时的中断状态文章来源地址https://www.toymoban.com/news/detail-461735.html

到了这里,关于多线程基础(三)JUC并发包:Lock锁、CountDownLath、CyclicBarrier、Semaphore、LockSupport的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Java基础-多线程&JUC-线程池和自定义线程池

    主要核心原理 不推荐Executors创建没有上线的线程池,建议使用自定义的线程池; Java工具类创建线程池; 当只有3个任务时,直接上处理机运行; 当有6个任务时,任务1-3上处理机运行,任务4-6进入阻塞队列等待; 当有9个任务时,任务1-3上处理机运行,任务4-6进入阻塞队列等

    2024年02月12日
    浏览(37)
  • Java并发体系-第三阶段-JUC并发包-[2]-CompleableFuture,SynchronousQueue

    java7中引入了一种新的可重复使用的同步屏障,称为移相器Phaser。Phaser拥有与 CyclicBarrier 和 CountDownLatch 类似的功能. 但是这个类提供了更加灵活的应用。CountDownLatch和CyclicBarrier都是只适用于固定数量的参与者。移相器适用于可变数目的屏障,在这个意义上,可以在任何时间注册

    2024年02月07日
    浏览(38)
  • CyclicBarrier线程同步

    关于作者: CSDN内容合伙人、技术专家, 从零开始做 日活千万级APP ,带领团队单日营收超千万。 专注于分享各领域原创系列文章 ,擅长java后端、移动开发、商业化变现、人工智能等,希望大家多多支持。 我们继续总结学习 Java基础知识 ,温故知新。 本文涉及知识点: A

    2024年02月06日
    浏览(33)
  • JUC并发编程学习笔记(二)Lock锁(重点)

    传统的synchronized 传统的解决多线程并发导致的一些问题我们会使用synchronized来解决,synchronized的本质就是队列、锁。 Lock的实现类有:可重复锁(最常用)、读锁、写锁 在创建可重复锁时,可传入boolean类型值来决定该锁是公平锁(先来后到)还是非公平锁(可插队)

    2024年02月06日
    浏览(41)
  • x86游戏逆向之实战游戏线程发包与普通发包的逆向

    网游找Call的过程中难免会遇到不方便通过数据来找的或者仅仅查找数据根本找不到的东西,但是网游中一般的工程肯定要发给服务器,比如你打怪,如果都是在本地处理的话就特别容易产生变态功能,而且不方便与其他玩家通信,所以找到了游戏发包的地方,再找功能就易如

    2024年02月06日
    浏览(46)
  • JUC之线程、线程池

    start方法开启一个新线程,异步执行。 run方法同步执行,不会产生新的线程。 start方法只能执行一次,run方法可以执行多次。 sleep() 线程睡眠 两种方式调用: 线程打断 interrupt()这种方式打断,线程会抛出InterruptedException异常。比如在线程睡眠的时候,调用interrupt(),线程抛出

    2024年04月27日
    浏览(33)
  • 多线程---JUC

    JUC是:java.util.concurrent这个包名的缩写。它里面包含了与并发相关,即与多线程相关的很多东西。我们下面就来介绍这些东西。 Callable接口类似与Runnable接口: Runnable接口:描述的任务是不带返回值的。 callable接口:描述的任务是带返回值的,存在的意义就是让我们获取到结果

    2024年02月07日
    浏览(39)
  • 多线程(进阶三:JUC)

    目录 一、Callable接口 1、创建线程的操作 2、编写多线程代码 (1)实现Runnable接口(使用匿名内部类) (2)实现Callable接口(使用匿名内部类) 二、ReentrantLock 1、ReentrantLock和synchronized的区别 2、如何选择使用哪个锁? 三、原子类 四、线程池 五、信号量 Semaphore 代码示例 六、

    2024年02月20日
    浏览(32)
  • java 多线程Lock接口

    Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。 Lock接口中常用的方法为 lock()  和  unlock() 想要使用Lock接口中方法要先创建Lock锁对象 run方法中用  ck.lock() 来上锁,用  ck.unlock() 来把锁归还。

    2024年02月08日
    浏览(36)
  • Lock实现线程间定制化通信

    案例 要求 三个线程,AA BB CC AA线程打印5次,BB线程打印10次,CC线程打印15次 代码实现 学习时,关于代码的疑问 不理解,为什么一定是,AA线程执行完后,,BB线程执行完后,是c3是c2去执行唤醒一个等待线程操作去执行唤醒一个等待线程的操作,CC线程执行完后,是c1执行唤醒

    2024年02月08日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包