Java 信号量机制实现

Java 信号量机制实现

信号量 Semaphore

信号量可限制访问共享资源的线程数。线程访问资源前需从信号量获取许可,访问结束后需将许可归还信号量。
image

场景介绍

一个停车场只有5个车位,现有100辆车争抢这5个车位。理想情况下,最多同时有5辆车能抢到车位,未抢到车位的车需等待,直到其他车让出车位后才有机会使用。

实现代码

package com.semaphore;

import java.util.concurrent.*;

/**
 * @author Jing61
 */
public class CarPark {
    public static void main(String[] args) {
        //阻塞队列
        BlockingQueue<String> parks = new LinkedBlockingQueue<>(5);

        parks.offer("车位一");
        parks.offer("车位二");
        parks.offer("车位三");
        parks.offer("车位四");
        parks.offer("车位五");

        ExecutorService executorService = Executors.newCachedThreadPool();

        // 5个车位(许可)
        Semaphore semaphore = new Semaphore(5);

        for (int i = 0; i < 100; i++) {
            final int no = i;
            Thread t1 = new Thread(() -> {
                try {
                    /**
                     * 获取许可,首先判断semaphore内部的数字是否大于0,如果大于0,
                     * 才能获得许可,然后将初始值5减去1,线程才会接着去执行;如果没有
                     * 获得许可(原因是因为已经有5个线程获得到许可,semaphore内部的数字为0),
                     * 线程会阻塞直到已经获得到许可的线程,调用release()方法,释放掉许可,
                     * 也就是将semaphore内部的数字加1,该线程才有可能获得许可。
                     */
                    semaphore.acquire();
                    /**
                     *  对应的线程会到阻塞对,对应车辆去获取到车位,如果没有拿到一致阻塞,
                     *  直到其他车辆归还车位。
                     */
                    String park = parks.take();  
                    System.out.println("车辆【" + no + "】获取到: " + park);
                    Thread.sleep((long) Math.random() * 6000);
                    System.out.println("车辆【" + no + "】离开 " + park);
                    semaphore.release(); //线程释放掉许可,通俗来将就是将semaphore内部的数字加1
                    parks.offer(park);  //归还车位
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            executorService.execute(t1);
        }
        executorService.shutdown();
    }
}

哲学家进餐问题

1965年,荷兰计算机科学家、图灵奖得主Edsger Wybe Dijkstra提出并解决了“哲学家进餐”同步问题。

问题描述

五个哲学家围坐在圆桌旁,每人面前有一盘通心粉。由于通心粉较滑,需两把叉子才能夹住。相邻两个盘子之间放有一把叉子(如下图所示)。

image

哲学家的生活有两种交替活动:吃饭和思考。当哲学家感到饥饿时,会尝试分两次取左右两边的叉子(每次拿一把,不分次序)。成功拿到两把叉子后开始吃饭,吃完后放下叉子继续思考。

若将哲学家替换为线程,叉子替换为竞争的临界资源,该问题即为线程竞争资源问题。若设计不当,系统可能出现死锁、活锁、吞吐量下降等问题。

信号量原语解决方案(Java 实现)

以下是使用 Java 5 并发工具包中的 Semaphore 类解决哲学家进餐问题的代码:

package com.wise.tiger;

import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * 存放线程共享信号量上下文
 * @author Jing61
 */
public class SemaphoreContext {
    public static final int NUM_OF_FORKS = 5;//叉子数量(资源)
    public static final int NUM_OF_PHILO = 5;//哲学家数量(线程)
    public static Semaphore[] forks;
    public static Semaphore counter;

    static{
        forks = new Semaphore[NUM_OF_FORKS];
        for(int i = 0 ; i < forks.length; i++){
            forks[i] = new Semaphore(1);
        }
        counter = new Semaphore(NUM_OF_PHILO - 1);
    }
    public static void putFork(int index,boolean leftFirst) throws Exception{
        if(leftFirst) {
            forks[index].acquire();
            forks[(index + 1) % NUM_OF_PHILO].acquire();
        }
        else {
            forks[(index + 1) % NUM_OF_PHILO].acquire();
            forks[index].acquire();
        }
    }

    public static void takeFork(int index,boolean leftFirst) throws Exception{
        if(leftFirst) {
            forks[index].release();
            forks[(index + 1) % NUM_OF_PHILO].release();
        }
        else {
            forks[(index + 1) % NUM_OF_PHILO].release();
            forks[index].release();
        }
    }

    static class Philo implements Runnable{
        private int index;
        private String name;

        public Philo(int index, String name) {
            this.index = index;
            this.name = name;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    counter.acquire();
                    boolean isLeftFirst = index % 2 == 0;
                    putFork(index, isLeftFirst);
                    System.out.println(name + "正在吃通心粉。。。。。");
                    takeFork(index, isLeftFirst);
                    System.out.println(name + "吃完了,正在思考");
                    counter.release();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        Philo[] philos = {new Philo(0,"peppa"),
                new Philo(1,"pedro"),
                new Philo(2,"emily"),
                new Philo(3,"suzy"),
                new Philo(4,"danny"),};
        var pool = Executors.newCachedThreadPool();
        for(int i= 0; i < philos.length;i++){
            pool.execute(philos[i]);
        }
        pool.shutdown();

    }
}

posted @ 2025-11-14 15:00  Jing61  阅读(5)  评论(0)    收藏  举报