并发编程之JUC并发工具类上

目录

ReentrantLock(独占锁/可重入锁)

特点

常用方法

ReentrantLock应用场景

Semaphore(信号量)

特点

常用方法

Semaphore应用场景

CountDownLatch(闭锁)

特点

常用方法

CountDownLatch应用场景


ReentrantLock(独占锁/可重入锁)

       ReentrantLock是Java中的一个并发工具类,可以实现可重入的互斥锁。与传统的synchronized关键字相比,ReentrantLock更加灵活,可以支持更复杂的并发控制。以下是ReentrantLock的一些特点以及常见使用方法:

特点

1. 可重入性:与synchronized一样,ReentrantLock允许线程在已经拥有锁的情况下重复获取该锁,而不会造成死锁。

2. 公平性:ReentrantLock支持公平锁和非公平锁,默认情况下是非公平锁。在公平模式下,锁会按照请求的顺序分配给等待的线程,而在非公平模式下,锁可能分配给等待时间更短的线程。

3. 条件等待:ReentrantLock允许你使用newCondition方法创建多个条件对象,用于在线程之间进行协调和通信。这些条件对象可以用于等待和唤醒线程。

4. 可中断性:ReentrantLock支持可中断的锁获取,如果线程在等待锁时被中断,可以通过捕获InterruptedException来响应中断。

5. 超时等待:你可以使用tryLock(long timeout, TimeUnit unit)方法来尝试获取锁,并在一定时间内等待,如果等待超时则返回结果,而不是一直阻塞等待。

常用方法

1. 创建ReentrantLock实例

ReentrantLock lock = new ReentrantLock();     //非公平锁
ReentrantLock lock = new ReentrantLock(true); //公平锁

2. 获取锁

使用lock()方法获取锁,如果锁已经被其他线程占用,则当前线程会阻塞,直到获取到锁。

lock.lock();
try {
    // 业务代码
} finally {
    lock.unlock(); // 必须在finally块中释放锁,以确保锁的释放
}

3. 条件等待/通知

class SharedQueue {
    private Queue<Integer> queue = new LinkedList<>();
    private int capacity = 5; // 队列容量
    private Lock lock = new ReentrantLock();
    private Condition notFull = lock.newCondition();
    private Condition notEmpty = lock.newCondition();

    public void produce(int item) {
        lock.lock();
        try {
            // 如果队列已满,等待
            while (queue.size() == capacity) {
                notFull.await();
            }
            // 生产者放入数据
            queue.add(item);
            System.out.println("Produced: " + item);
            // 通知消费者队列非空
            notEmpty.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void consume() {
        lock.lock();
        try {
            // 如果队列为空,等待
            while (queue.isEmpty()) {
                notEmpty.await();
            }
            // 消费者取出数据
            int item = queue.poll();
            System.out.println("Consumed: " + item);
            // 通知生产者队列非满
            notFull.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

class ProducerConsumerExample {
    public static void main(String[] args) throws InterruptedException {
        SharedQueue sharedQueue = new SharedQueue();
        // 创建生产者线程
        Thread producerThread = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                sharedQueue.produce(i);
                try {
                    Thread.sleep(100); // 模拟生产耗时
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Producer");
        // 创建消费者线程
        Thread consumerThread = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                sharedQueue.consume();
                try {
                    Thread.sleep(150); // 模拟消费耗时
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Consumer");
        // 启动线程
        producerThread.start();
        Thread.sleep(1000);
        consumerThread.start();
    }
}

   SharedQueue类表示共享的队列。生产者使用produce方法往队列中放入数据,如果队列已满,则调用notFull.await()等待;消费者使用consume方法从队列中取出数据,如果队列为空,则调用notEmpty.await()等待。

4. 尝试获取锁

       使用tryLock()方法可以尝试获取锁,如果锁已经被其他线程占用,则返回false。可以用于实现超时等待的逻辑。  

if (lock.tryLock()) {
    try {
        // 获取锁成功后的操作
    } finally {
        lock.unlock();
    }
} else {
    // 获取锁失败,处理其他逻辑
}

5. 可中断获取锁

       使用tryLock(long timeout, TimeUnit unit)方法来尝试获取锁,如果在指定的时间内获取不到锁,返回false。当线程在等待获取锁的过程中被其他线程中断时,会抛出InterruptedException异常。

try {
    if (lock.tryLock(5, TimeUnit.SECONDS)) {
        try {
            // 获取锁成功后的操作
        } finally {
            lock.unlock();
        }
    } else {
        // 获取锁失败,处理其他逻辑
    }
} catch (InterruptedException e) {
    // 线程被中断,处理中断逻辑
}

6. 释放锁

     必须在finally块中使用unlock()来释放锁,以确保锁的正确释放,避免死锁。

lock.lock();
try {
    // 临界区代码
} finally {
    lock.unlock(); // 必须在finally块中释放锁
}
ReentrantLock应用场景

1. 解决多线程竞争资源的问题,例如多个线程同时对同一个数据库进行写操作,可以使用ReentrantLock保证每次只有一个线程能够写入。

2. 实现多线程任务的顺序执行,例如在一个线程执行完某个任务后,再让另一个线程执行任务。

3. 实现多线程等待/通知机制,例如在某个线程执行完某个任务后,通知其他线程继续执行任务。

可重入锁保护资源举例如下:

class Example {
    private final Lock lock = new ReentrantLock();
    public void outerMethod() {
        lock.lock(); // 第一次获取锁
        try {
            System.out.println("Entered outerMethod");
            innerMethod(); // 在outerMethod内部调用另一个需要同一把锁的方法
        } finally {
            lock.unlock(); // 释放锁
        }
    }
    private void innerMethod() {
        lock.lock(); // 第二次获取锁,可重入
        try {
            System.out.println("Entered innerMethod");
            // 执行一些操作
        } finally {
            lock.unlock(); // 释放锁
        }
    }
}

public class ReentrantLockExample {
    public static void main(String[] args) {
        Example example = new Example();

        Thread thread = new Thread(() -> {
            example.outerMethod();
        });
        thread.start();
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

       outerMethod()和innerMethod()都使用了同一把ReentrantLock来保护共享资源。在outerMethod()中获取锁后,它调用了innerMethod(),而innerMethod()再次获取了同一把锁,表示可重入锁。这种锁机制允许同一个线程在持有锁的情况下多次获取同一把锁,而不会造成死锁。


Semaphore(信号量)

  Semaphore是Java并发包中的一个同步工具类,它可以用来控制同时访问某个资源的线程数量。可用于控制并发线程数,可以用作同步工具。以下是Semaphore的一些特点以及常见使用方法:

特点

1. 许可证机制:Semaphore维护了一个许可证集合,线程在尝试获取许可证时会被阻塞,直到有可用的许可证。

2. 可用许可证数量:Semaphore有一个许可证计数器,表示可用的许可证数量。线程成功获取许可证后,计数器会减少;释放许可证后,计数器会增加。

常用方法

1. 创建Semaphore实例

Semaphore semaphore = new Semaphore(3); // 创建一个包含3个许可证的Semaphore

2. 获取许可证

      使用acquire()方法尝试获取许可证。如果没有可用的许可证,当前线程将被阻塞,直到有许可证可用。

try {
    semaphore.acquire(); // 尝试获取一个许可证
    // 访问共享资源或执行其他操作
} catch (InterruptedException e) {
    // 处理中断异常
} finally {
    // 释放许可证
    semaphore.release();
}

3. 尝试获取许可证

使用tryAcquire()方法尝试获取许可证,如果成功返回true,否则返回false。

if (semaphore.tryAcquire()) {
    try {
        // 获取许可证成功后的操作
    } finally {
        semaphore.release(); // 释放许可证
    }
} else {
    // 获取许可证失败,执行其他逻辑
}

4. 获取多个许可证

semaphore.acquire(2); // 尝试获取2个许可证
// 执行需要2个许可证的操作
semaphore.release(2); // 释放2个许可证

5. 获取可用许可证数量

使用availablePermits()方法可以获取当前可用的许可证数量。

int permits = semaphore.availablePermits(); // 获取可用许可证数量

6. 动态增加许可证数量

使用release(int permits)方法可以动态地增加许可证的数量。

semaphore.release(3); // 增加3个许可证

7. 公平性和非公平性

Semaphore fairSemaphore = new Semaphore(3, true); // 创建一个公平的Semaphore
Semaphore应用场景

资源池:Semaphore可以用于实现资源池,以维护一组有限的共享资源。

限流:Semaphore可以用于限制对共享资源的并发访问数量,以控制系统的流量。

class SharedResource {
    // 假设这是一个共享资源
    private int sharedValue = 0;
    // 使用Semaphore来控制对共享资源的访问数量
    private Semaphore semaphore = new Semaphore(1); // 参数1表示只允许一个线程同时访问共享资源
    public void accessSharedResource() {
        try {
            // 获取许可证,如果没有许可证可用,线程将被阻塞直到有许可证为止
            semaphore.acquire();
            // 访问共享资源
            sharedValue++;
            System.out.println("Thread " + Thread.currentThread().getId() + " is accessing shared resource. Value: " + sharedValue);
            // 模拟一些工作时间
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 释放许可证
            semaphore.release();
        }
    }
}

class MyThread extends Thread {
    private SharedResource sharedResource;

    public MyThread(SharedResource sharedResource) {
        this.sharedResource = sharedResource;
    }
    @Override
    public void run() {
        // 在线程中访问共享资源
        sharedResource.accessSharedResource();
    }
}

public class SemaphoreExample {
    public static void main(String[] args) {
        // 创建一个共享资源实例
        SharedResource sharedResource = new SharedResource();
        // 创建多个线程,并让它们同时访问共享资源
        for (int i = 1; i <= 5; i++) {
            MyThread myThread = new MyThread(sharedResource);
            myThread.start();
        }
    }
}

       在以上述例子中,SharedResource类表示一个共享资源,Semaphore用于限制对该资源的并发访问数量。每个线程在访问共享资源之前必须先获取许可证,如果没有可用的许可证,线程将被阻塞。在访问完成后,线程释放许可证,以便其他线程可以继续访问。这样可以确保同时只有一个线程在访问共享资源。


CountDownLatch(闭锁)

CountDownLatch是一个同步协助类,允许一个或多个线程等待,直到其他线程完成操作集。

特点

1. 计数器机制: CountDownLatch内部维护一个计数器,初始值由用户设置。该计数器可以通过countDown()方法递减,当计数器值变为零时,等待的线程可以继续执行。

2. 一次性: CountDownLatch是一次性的,一旦计数器减到零,就不能重置。如果需要类似的功能,并能重置计数器,可以考虑使用CyclicBarrier。

3. 等待阻塞: 方await()法会阻塞调用线程,直到计数器减至零。可以选择在等待过程中设置超时时间。

常用方法
1. CountDownLatch(int count): 构造方法,初始化计数器的值。

2. void await(): 调用此方法的线程会阻塞,直到计数器减至零。

3. boolean await(long timeout, TimeUnit unit): 可以设置等待超时时间,超过指定时间仍未减至零则返回false。

4. void countDown(): 减少计数器的值,通常在任务完成时调用。
CountDownLatch应用场景

1. 并行任务同步:CountDownLatch可以用于协调多个并行任务的完成情况,确保所有任务都完成后再继续执行下一步操作。

2. 多任务汇总:CountDownLatch可以用于统计多个线程的完成情况,以确定所有线程都已完成工作。

3. 资源初始化:CountDownLatch可以用于等待资源的初始化完成,以便在资源初始化完成后开始使用。

以模拟百米赛跑举例如下:

public class CountDownLatchDemo {
        // begin 代表裁判 初始为 1
    private static CountDownLatch begin = new CountDownLatch(1);

    // end 代表玩家 初始为 8
    private static CountDownLatch end = new CountDownLatch(8);

    public static void main(String[] args) throws InterruptedException {

        for (int i = 1; i <= 8; i++) {
            new Thread(new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                  // 预备状态
                  System.out.println("参赛者"+Thread.currentThread().getName()+ "准备好了");
                  // 等待裁判吹哨
                  begin.await();
                  // 开始跑步
                  System.out.println("参赛者"+Thread.currentThread().getName() + "开始跑步");
                  Thread.sleep(1000);
                  // 跑步结束, 跑完了
                  System.out.println("参赛者"+Thread.currentThread().getName()+ "到达终点");
                  // 跑到终点, 计数器就减一
                  end.countDown();
                }
            }).start();
           }
        // 等待 5s 就开始吹哨
        Thread.sleep(5000);
        System.out.println("开始比赛");
        // 裁判吹哨, 计数器减一
        begin.countDown();
        // 等待所有玩家到达终点
        end.await();
        System.out.println("比赛结束");

    }

相关推荐

  1. 并发编程JUC并发工具

    2024-01-11 00:38:04       33 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-01-11 00:38:04       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-01-11 00:38:04       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-01-11 00:38:04       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-01-11 00:38:04       20 阅读

热门阅读

  1. 算法笔记:全排列

    2024-01-11 00:38:04       35 阅读
  2. Hive之set参数大全-3

    2024-01-11 00:38:04       27 阅读
  3. 毕业论文idea

    2024-01-11 00:38:04       32 阅读
  4. 调整Hive查询临时内存大小的方法

    2024-01-11 00:38:04       34 阅读
  5. 搭建大数据开发环境【AutoDL容器】

    2024-01-11 00:38:04       38 阅读
  6. Django模版过滤器Markdown

    2024-01-11 00:38:04       38 阅读
  7. Hadoop之mapreduce参数大全-1

    2024-01-11 00:38:04       31 阅读
  8. 在React和Vue中实现锚点定位功能

    2024-01-11 00:38:04       34 阅读