13.jdk源码阅读之Semaphore

1. 写在前面

Semaphore 是一个用于控制多个线程对共享资源访问的同步工具。在正式阅读源码前,我先抛出几个问题看看大家有没有思考过:

  1. Semaphore 的基本用法是什么?
  2. 如何创建一个 Semaphore?
  3. Semaphore 的公平性是什么?
  4. Semaphore 适用于哪些场景?
  5. 如何使用 Semaphore 实现一个简单的限流器?
  6. emaphore 的内部实现原理是什么?
  7. Semaphore 如何保证线程安全?
  8. 如何使用 Semaphore 控制对共享资源的访问。
  9. 如何使用 Semaphore 实现生产者-消费者模式?
  10. Semaphore 与 ReentrantLock 的区别是什么?
  11. 如何使用 Semaphore 实现一个读写锁?

2. 从使用说起

import java.util.concurrent.Semaphore;

public class SemaphoreExample {
    private static final int MAX_CONCURRENT_THREADS = 3;
    private static final Semaphore semaphore = new Semaphore(MAX_CONCURRENT_THREADS);

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(new Worker()).start();
        }
    }

    static class Worker implements Runnable {
        @Override
        public void run() {
            try {
                semaphore.acquire();
                System.out.println(Thread.currentThread().getName() + " acquired a permit.");
                // 模拟对共享资源的访问
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                System.out.println(Thread.currentThread().getName() + " released a permit.");
                semaphore.release();
            }
        }
    }
}

这段代码演示了如何使用 Semaphore 来控制对共享资源的并发访问。它实现了一个简单的限流机制,确保最多只有三个线程可以同时访问共享资源。

2.1 类定义和成员变量

public class SemaphoreExample {
    private static final int MAX_CONCURRENT_THREADS = 3;
    private static final Semaphore semaphore = new Semaphore(MAX_CONCURRENT_THREADS);

  • 定义一个名为 SemaphoreExample 的公共类
  • 定义一个常量 MAX_CONCURRENT_THREADS,表示最多允许同时访问共享资源的线程数,这里设置为3
  • 创建一个 Semaphore 实例 semaphore,初始许可数量为 MAX_CONCURRENT_THREADS

2.2 主方法

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(new Worker()).start();
        }
    }
  • main 方法是程序的入口。
  • 使用一个 for 循环创建并启动10个线程,每个线程执行 Worker 类的 run 方法。

2.3 Worker 类

    static class Worker implements Runnable {
        @Override
        public void run() {
            try {
                semaphore.acquire();
                System.out.println(Thread.currentThread().getName() + " acquired a permit.");
                // 模拟对共享资源的访问
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                System.out.println(Thread.currentThread().getName() + " released a permit.");
                semaphore.release();
            }
        }
    }
}

  • 定义一个静态内部类 Worker,实现 Runnable 接口。
  • 在 run 方法中:
    • semaphore.acquire():尝试获取一个许可。如果没有可用的许可,线程会被阻塞,直到有许可可用。
    • 打印当前线程名称和消息,表示线程已获取许可。
    • 使用 Thread.sleep(2000) 模拟对共享资源的访问,休眠2秒。
    • 在 finally 块中:
      • 打印当前线程名称和消息,表示线程释放了许可。
      • semaphore.release():释放一个许可,增加可用许可的数量。

2.4 代码执行流程

  1. 程序启动后,main 方法创建并启动10个线程,每个线程执行 Worker 类的 run 方法。
  2. 最多只有3个线程可以同时获取许可并执行对共享资源的访问,其余线程会被阻塞,直到有线程释放许可。
  3. 每个线程获取许可后,打印消息并休眠2秒,模拟对共享资源的访问。
  4. 休眠结束后,线程释放许可,并打印消息。
  5. 被阻塞的线程在有许可可用时继续执行。

3. Semaphore 的公平性是什么?

Semaphore 可以是公平的或非公平的。公平的 Semaphore 保证线程按照它们请求许可的顺序获得许可。可以通过构造函数的第二个参数来指定:

Semaphore semaphore = new Semaphore(3, true); // 公平的 Semaphore

4. Semaphore 适用于哪些场景?

  • 控制对有限资源的访问,例如数据库连接池、文件访问等。
  • 实现简单的限流机制。
  • 作为一种信号量机制,用于线程间通信。

5. 如何使用 Semaphore 实现一个简单的限流器?

通过限制许可数量来控制并发线程数:

Semaphore semaphore = new Semaphore(5); // 允许最多5个线程同时访问

public void accessResource() {
   try {
       semaphore.acquire();
       // 访问资源的代码
   } catch (InterruptedException e) {
       Thread.currentThread().interrupt();
   } finally {
       semaphore.release();
   }
}

6. Semaphore 的内部实现原理是什么?

Semaphore 使用一个计数器来跟踪可用的许可数量。acquire() 方法会尝试减少计数器,如果计数器为零,线程会被阻塞。release() 方法会增加计数器,并唤醒等待的线程。

7. Semaphore 如何保证线程安全?

Semaphore 使用内部的 AbstractQueuedSynchronizer (AQS) 来管理许可计数和线程的阻塞/唤醒操作,确保线程安全。

8. 如何使用 Semaphore 实现生产者-消费者模式?

Semaphore 可以用于控制生产者和消费者之间的同步,例如:

import java.util.concurrent.Semaphore;

public class ProducerConsumerExample {
    private static final int BUFFER_SIZE = 10;
    private static final Semaphore emptySlots = new Semaphore(BUFFER_SIZE);
    private static final Semaphore fullSlots = new Semaphore(0);
    private static final Semaphore mutex = new Semaphore(1);

    private static final int[] buffer = new int[BUFFER_SIZE];
    private static int in = 0, out = 0;

    public static void main(String[] args) {
        Thread producer = new Thread(new Producer());
        Thread consumer = new Thread(new Consumer());

        producer.start();
        consumer.start();
    }

    static class Producer implements Runnable {
        @Override
        public void run() {
            try {
                while (true) {
                    emptySlots.acquire();
                    mutex.acquire();
                    buffer[in] = 1; // 生产一个产品
                    in = (in + 1) % BUFFER_SIZE;
                    mutex.release();
                    fullSlots.release();
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    static class Consumer implements Runnable {
        @Override
        public void run() {
            try {
                while (true) {
                    fullSlots.acquire();
                    mutex.acquire();
                    int item = buffer[out]; // 消费一个产品
                    out = (out + 1) % BUFFER_SIZE;
                    mutex.release();
                    emptySlots.release();
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

9. Semaphore 与 ReentrantLock 的区别是什么?

  • Semaphore 控制的是许可数量,可以允许多个线程同时访问共享资源;ReentrantLock 是一个互斥锁,只允许一个线程访问共享资源。
  • Semaphore 可以用来实现限流和信号量机制,而 ReentrantLock 主要用于实现互斥访问。

10. 如何使用 Semaphore 实现一个读写锁?

读写锁可以通过两个 Semaphore 来实现,一个控制读操作,一个控制写操作。

import java.util.concurrent.Semaphore;

public class ReadWriteLock {
    private final Semaphore readLock = new Semaphore(1); // 控制读操作的信号量
    private final Semaphore writeLock = new Semaphore(1); // 控制写操作的信号量
    private int readCount = 0; // 记录当前有多少个读线程

    public void acquireReadLock() throws InterruptedException {
        readLock.acquire(); // 获取读锁
        synchronized (this) {
            readCount++;
            if (readCount == 1) {
                writeLock.acquire(); // 第一个读线程获取写锁,阻止写操作
            }
        }
        readLock.release(); // 释放读锁
    }

    public void releaseReadLock() {
        synchronized (this) {
            readCount--;
            if (readCount == 0) {
                writeLock.release(); // 最后一个读线程释放写锁,允许写操作
            }
        }
    }

    public void acquireWriteLock() throws InterruptedException {
        writeLock.acquire(); // 获取写锁
    }

    public void releaseWriteLock() {
        writeLock.release(); // 释放写锁
    }

    public static void main(String[] args) {
        ReadWriteLock rwLock = new ReadWriteLock();

        // 创建并启动读线程
        for (int i = 0; i < 5; i++) {
            new Thread(new Reader(rwLock)).start();
        }

        // 创建并启动写线程
        for (int i = 0; i < 2; i++) {
            new Thread(new Writer(rwLock)).start();
        }
    }

    static class Reader implements Runnable {
        private final ReadWriteLock rwLock;

        public Reader(ReadWriteLock rwLock) {
            this.rwLock = rwLock;
        }

        @Override
        public void run() {
            try {
                rwLock.acquireReadLock();
                System.out.println(Thread.currentThread().getName() + " acquired read lock.");
                // 模拟读操作
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                System.out.println(Thread.currentThread().getName() + " released read lock.");
                rwLock.releaseReadLock();
            }
        }
    }

    static class Writer implements Runnable {
        private final ReadWriteLock rwLock;

        public Writer(ReadWriteLock rwLock) {
            this.rwLock = rwLock;
        }

        @Override
        public void run() {
            try {
                rwLock.acquireWriteLock();
                System.out.println(Thread.currentThread().getName() + " acquired write lock.");
                // 模拟写操作
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                System.out.println(Thread.currentThread().getName() + " released write lock.");
                rwLock.releaseWriteLock();
            }
        }
    }
}

10.1 ReadWriteLock 类

  • readLock:用于控制读操作的信号量,初始许可数量为1。
  • writeLock:用于控制写操作的信号量,初始许可数量为1。
  • readCount:记录当前有多少个读线程在访问共享资源。

10.2 acquireReadLock() 方法

  • 获取读锁 readLock。
  • 使用 synchronized 块保护对 readCount 的访问。如果这是第一个读线程,获取写锁 writeLock,阻止写操作。
  • 释放读锁 readLock。

10.3 releaseReadLock() 方法

使用 synchronized 块保护对 readCount 的访问。如果这是最后一个读线程,释放写锁 writeLock,允许写操作

10.4 acquireWriteLock() 方法

获取写锁 writeLock

10.5 releaseWriteLock() 方法

释放写锁 writeLock

10.6 Reader 和 Writer 类

  • Reader 类实现读操作,获取和释放读锁
  • Writer 类实现写操作,获取和释放写锁

系列文章

1.JDK源码阅读之环境搭建

2.JDK源码阅读之目录介绍

3.jdk源码阅读之ArrayList(上)

4.jdk源码阅读之ArrayList(下)

5.jdk源码阅读之HashMap

6.jdk源码阅读之HashMap(下)

7.jdk源码阅读之ConcurrentHashMap(上)

8.jdk源码阅读之ConcurrentHashMap(下)

9.jdk源码阅读之ThreadLocal

10.jdk源码阅读之ReentrantLock

11.jdk源码阅读之CountDownLatch

12.jdk源码阅读之CyclicBarrier

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

至真源

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值