【Java并发】(4)JUC框架

ReentrantLock

1. ReentrantLock使用

  • 获取锁lock()/释放锁unlock()

三个线程依次获取锁/释放锁

public class TestReentrantLock {
    static ReentrantLock lock = new ReentrantLock();
    static void f(){
        lock.lock();
        System.out.println(Thread.currentThread().getName()+"获取锁");
        lock.unlock();
        System.out.println(Thread.currentThread().getName()+"释放锁");
    }
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        executorService.execute(TestReentrantLock::f);
        executorService.execute(TestReentrantLock::f);
        executorService.execute(TestReentrantLock::f);
        executorService.shutdown();
    }
}
  • 可中断地获取锁lockInterruptibly

调用后一直阻塞到获得锁 但是接受中断信号

//可中断地获取锁
static void g(){
    try {
        lock.lockInterruptibly();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
public static void main(String[] args) throws Exception {
    Thread thread1 = new Thread(TestReentrantLock::g);
    thread1.start();
    thread1.interrupt();
}

如果被在获取锁时被中断,就抛出异常,类似sleep

  • 尝试获取锁tryLock、tryLock(timeout)

尝试是否能获得锁 如果不能获得立即返回false,不阻塞当前线程,否则,直接获取锁

lock.tryLock();//尝试获取锁,不阻塞当前线程


//阻塞线程,尝试获取锁2秒钟,超时后放弃获取锁。
try {
    lock.tryLock(2000, TimeUnit.SECONDS);
} catch (InterruptedException e) {
    e.printStackTrace();
}
  • 公平锁/非公平锁

ReentrantLock可以实现公平锁,将线程放入同步队列中,依次获取锁

static ReentrantLock fairLock = new ReentrantLock(true);
//公平锁
static void r() {
    fairLock.lock();
    System.out.println(Thread.currentThread().getName() + "获取锁");
    try {
        Thread.sleep(100);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    fairLock.unlock();
    System.out.println(Thread.currentThread().getName() + "释放锁");
}
public static void main(String[] args) throws Exception {
    for (int i = 0; i < 100; i++) {
        Thread.sleep(10);
        new Thread(TestReentrantLock::r).start();
    }
    System.out.println("线程创建完毕");
}

这里每隔10毫秒创建一个线程,可以保证线程获取锁的顺序,如果是公平锁,那么线程获取锁是有序的,如果是非公平锁,线程获取锁是需要竞争的(注释:这个例子在正常运行时,不知什么原因公平锁和非公平锁得到了一样的结果,如果使用线程调试,那么结果是满足期望的,公平锁起了作用)

  • Condition

和wait、notify、notifyAll用法类似,对应await、signal、signalAll方法

public class TestCondition {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        Lock lock = new ReentrantLock();
        Condition condition1 = lock.newCondition();
        Condition condition2 = lock.newCondition();

        Runnable awaitRunnable = () -> {
            lock.lock();
            try {
                System.out.println(Thread.currentThread() + "condition1,await");
                condition1.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread() + "condition1,notified");
            lock.unlock();
        };
        Runnable signalRunnable = () -> {
            lock.lock();
            try {
                System.out.println(Thread.currentThread() + "Sleep");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            condition1.signal();
            System.out.println(Thread.currentThread() + "signal");
            lock.unlock();
        };
        threadPool.execute(awaitRunnable);
        threadPool.execute(signalRunnable);
        threadPool.shutdown();
        threadPool.awaitTermination(2000, TimeUnit.SECONDS);
    }
}

和wait、notify、notifyAll用法基本一致,但ReentrantLock可以使用多个condition监视器,对应多个线程等待队列,Object只有1个监视器。
使用多个Condition可以控制唤醒指定的1个或多个线程(在这些线程中使用特定的condition的await方法即可)。

2. ReentrantLock和synchronized对比

  • 相同点:
  1. 都是阻塞式同步,拥有锁的线程才能够访问同步代码块。
  2. 都是重入锁,拥有锁的线程能够再次加锁而不被阻塞。
  3. 性能大致相同,Java对synchronized做了很多锁优化。
  • 不同点:
  1. synchronized是JVM实现的,ReentrantLock是JDK实现的。
  2. 当持有锁的线程长时间不释放锁时,ReentrantLock可以实现线程的等待可中断,synchronized不可以。
  3. ReentrantLock可以实现公平锁和非公平锁,synchronized只能是非公平锁。(公平锁:多个线程在等待锁时,按照申请锁的先后顺序来依次获得锁)
  4. 在等待/通知机制中,synchronized使用Object内置的监视器,通过wait/notify等方法做等待和唤醒操作,notifyAll会唤醒所有线程;ReentrantLock使用条件监视器Condition,一个ReentrantLock有多个condition,每个condition维护自己的等待线程队列调用signalAll只会唤醒自己队列内的线程。
  5. ReentrantLock有显式的锁对象,可以由用户决定请求锁和释放锁的时机,甚至可以不在一个代码块内,synchronized没有这么灵活。

3.实现原理

CountDownLatch

1. 使用

  • CountDownLatch可以等待一个或多个线程等待其他线程完成操作,比join方法灵活程度更高。CountDownLatch不能重用
  • 例子

主线程等待两个线程完成工作

public class TestCountDownLatch {
    static CountDownLatch cdl = new CountDownLatch(2);

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程1完成工作");
                cdl.countDown();
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程2完成工作");
                cdl.countDown();
            }
        });
        thread1.start();
        thread2.start();
//        thread1.join();
//        thread2.join();
        cdl.await();
        System.out.println("回到Main线程");
    }
}

主线程中的cdl.await();将会阻塞主线程,直到计数器为0

2. 方法

  • countDown()
    计数器减一
  • await()、await(millis)
    阻塞当前线程,等待计数器为0,可以设置超时时间

3. CountDownLatch比join好在哪?

CountDownLatch与join方法功能类似,但比join方法控制能力更高,join只能等待其他线程完成其全部工作后,才能返回。
而CountDownLatch的计数器只监控数值是否为0,也就是说,该数值与线程数无关,一个线程可以调用countDown()方法多次。
例如一个线程包含多个任务,我们只需要等待其完成一个任务即可,使用join只能等待该线程完成全部任务。

4. 实现原理

CyclicBarrier

1. 使用

  • CyclicBarrier让一组线程到达屏障时被阻塞,直到最后一个线程到达屏障时,所有线程都被唤醒。

10个线程到达屏障,最后一个线程到达时,10个线程都被唤醒

public class TestCyclicBarrier {
    private CyclicBarrier cyclicBarrier = new CyclicBarrier(10);
    private Runnable runnable = () -> {
        System.out.println(Thread.currentThread()+"到达屏障");
        try {
            Thread.sleep(1000);
            cyclicBarrier.await();
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread()+"继续执行");
    };
    public static void main(String[] args) {
        TestCyclicBarrier testCyclicBarrier = new TestCyclicBarrier();
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for(int i = 0 ; i < 10 ; i++){
            executorService.execute(testCyclicBarrier.runnable);
        }
        executorService.shutdown();
    }
}
输出:
Thread[pool-1-thread-1,5,main]到达屏障
Thread[pool-1-thread-5,5,main]到达屏障
Thread[pool-1-thread-4,5,main]到达屏障
Thread[pool-1-thread-3,5,main]到达屏障
Thread[pool-1-thread-2,5,main]到达屏障
Thread[pool-1-thread-7,5,main]到达屏障
Thread[pool-1-thread-6,5,main]到达屏障
Thread[pool-1-thread-8,5,main]到达屏障
Thread[pool-1-thread-9,5,main]到达屏障
Thread[pool-1-thread-10,5,main]到达屏障
Thread[pool-1-thread-2,5,main]继续执行
Thread[pool-1-thread-9,5,main]继续执行
Thread[pool-1-thread-4,5,main]继续执行
Thread[pool-1-thread-7,5,main]继续执行
Thread[pool-1-thread-1,5,main]继续执行
Thread[pool-1-thread-5,5,main]继续执行
Thread[pool-1-thread-6,5,main]继续执行
Thread[pool-1-thread-8,5,main]继续执行
Thread[pool-1-thread-10,5,main]继续执行
Thread[pool-1-thread-3,5,main]继续执行

2. 方法

  • 构造方法CyclicBarrier(int parties)
    parties代表在屏障解除前,需要拦截的线程的数量。如果指定为10,只有小于10的线程执行了await()方法,那么屏障将不会解除。
  • 构造方法CyclicBarrier(int parties, Runnable barrierAction)
    barrierAction的代码块将会在解除屏障后马上执行,执行的线程为最后到达屏障的线程。
  • await
    表示线程到达了屏障
  • await(long timeout, TimeUnit unit)
    等待指定的时间,如果屏障没有解除,那么抛出TimeoutException,正在等待屏障解除的线程抛出BrokenBarrierException
  • reset()
    可以重置,重置后可以继续使用

3. 实现原理

Semaphore

1. 使用

  • Semaphore是流量控制工具,控制某一代码块同时可以被多少线程同时访问,比线程池的控制更精细化

例:线程池有30个线程,而我们只想其中10个线程并发操作数据库

public class TestSemaphore {
    Semaphore semaphore = new Semaphore(10);
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            try {
                semaphore.acquire();
                System.out.println(Thread.currentThread()+"开始数据库操作");
                Thread.sleep(2000);
                System.out.println(Thread.currentThread()+"完成数据库操作");
                semaphore.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };

    public static void main(String[] args) {
        TestSemaphore testSemaphore = new TestSemaphore();
        ExecutorService executorService = Executors.newFixedThreadPool(30);
        for(int i = 0 ; i < 30 ;i++){
            executorService.execute(testSemaphore.runnable);
        }
        executorService.shutdown();
    }
}

2.主要方法

  • 构造方法Semaphore(int permits)
    允许并发访问代码块的线程数量
  • 构造方法Semaphore(int permits, boolean fair)
    fair表示是否公平访问
  • acquire()
    获取访问权
  • release()
    释放访问权

3.和线程池的区别

线程池控制的是线程级别的同时并发数量,而Semaphore控制的是代码块级别的同时并发数量,细粒度更高,如一个线程可能同时需要进行数据库操作和文件操作等,就需要Semaphore来精细控制。

Exchanger

使用

  • Exchanger用于进行线程间的数据交换,它提供一个同步点,在这个同步点,两个线程可以交换彼此的数据。这两个线程通过 exchange方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也 执行exchange方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。

线程1发出10,收到20;线程2发出20,收到10

public class TestExchanger {
    static Exchanger<Integer> exchanger = new Exchanger<>();

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println(Thread.currentThread()+"发出值:"+10);
                    Integer exchange = exchanger.exchange(10);
                    System.out.println(Thread.currentThread()+"收到值:"+exchange);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println(Thread.currentThread()+"发出值:"+20);
                    Integer exchange = exchanger.exchange(20);
                    System.out.println(Thread.currentThread()+"收到值:"+exchange);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值