信号量在Java线程中的应用

本文详细介绍了Java中Semaphore类的应用,通过一个苹果橘子问题的实例,展示了如何使用Semaphore解决线程同步问题,确保了多个线程间的有序执行,避免了公共资源的混乱访问。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

  相信学习java的人都知道Thread这个线程类,它可以用来开启一个子线程运行,不会阻塞主线程。但是随之都会有让大家苦恼的问题,如果存在多个Thread的实例同时运行,那么就会造成线程错乱的问题,尤其是访问公共资源的时候,特别容易造成混乱,也一直没有找到好的方法解决。但是这学期学了操作系统课程,里面有信号量在进程同步方面的应用,十分的方便,那么在java里面有没有封装对信号量的使用呢?答案是YES,Semaphore这个类就是我今天要讲的东西,它可以很方便实现信号量在进程同步方面的作用,下面让我们拭目以待。

实现

  下面我就拿一个苹果橘子问题来说明问题,并用Java来实现它的具体操作。

问题描述

  桌子上有一个盘子,每次只能放入一只水果;爸爸专向盘子中放苹果,妈妈专向盘子中放橘子,儿子专吃盘子中的苹果,女儿专吃盘子中的橘子。

问题分析

  问题有以下的要点:

  • 爸爸和妈妈不能同时往盘子里放水果

  • 儿子只能吃盘子里的苹果

  • 女儿只能吃盘子里的橘子

  也就是说,在爸爸或妈妈放水果之前需要判断盘子里是否已经有水果,在儿子或女儿吃水果之前先判断盘子里是否有水果。而且,表示父亲的线程一定运行在表示儿子的线程之前,母亲和女儿同理。

  按照之前的思路,我先设置一个变量表示盘子里是否有水果,然后在父亲线程和母亲线程判断这个变量,比如:

    //父亲线程:
        if(盘子为空){
            //开始放苹果,设置盘子不为空
        }
    //母亲线程:
        if(盘子为空){
            //开始放橘子,设置盘子不为空
        }

  乍一看好像好像可以,但是其实是错误的。如果盘子开始为空,父亲线程和母亲线程同时运行到if的判断语句,那么都可以通过,然后就放了水果,导致同时放了水果出现错误。

  但是引入了信号量之后,问题就迎刃而解了。

  在此之前,我需要补充一下信号量的P和V操作。我们给信号量一个初值,比如为1,当执行P操作的时候,我们先把信号量的值减去1,然后判断现在的值是否不小于0,如果是则继续执行,如果不是则该线程被加入到队列之中等待运行。当执行V操作的时候,我们把信号量的值加1,然后判断现在的值是否不大于0,如果是则从队列之中取出一个线程运行,否则就返回。

  我们设置一个信号量,并把初值设置为1,当父亲线程执行P操作的时候,此时信号量的值变为了0,父亲线程继续执行,当母亲线程执行P操作的时候,此时的值变为了-1,母亲线程就被阻塞添加到队列之中等待运行,就不会出现同时放水果的问题。

  下面来看一下具体的代码:

import java.util.concurrent.Semaphore;
public class MyMain {
    private Thread fatherThread,motherThread,sonThread,daughterThread;
    private Semaphore plateFull = new Semaphore(1);  //盘子开始可以放水果
    private Semaphore appleEmpty = new Semaphore(0);  //开始不能吃苹果
    private Semaphore orangeEmpty = new Semaphore(0);  //开始不能吃橘子
    public static void main(String[] args){
        MyMain myMain = new MyMain();
        myMain.start();
    }

    public void start(){
        fatherThread = new Thread(new Runnable() {
            @Override
            public void run(){
                while(true){
                    try {
                        plateFull.acquire();
                        System.out.println("father puts an apple");
                        appleEmpty.release();
                        Thread.sleep(3000);
                    } catch (Exception e) {}
                }
            }
        });
        motherThread = new Thread(new Runnable(){
            @Override
            public void run(){
                while(true){
                    try {
                        plateFull.acquire();
                        System.out.println("mother puts an orange");
                        orangeEmpty.release();
                        Thread.sleep(1000);
                    } catch (Exception e) {}
                }

            }
        });
        sonThread = new Thread(new Runnable() {
            @Override
            public void run(){
                while(true){
                    try {
                        appleEmpty.acquire();
                        System.out.println("son eats an apple");
                        plateFull.release();
                    } catch (Exception e) {}
                }
            }
        });
        daughterThread = new Thread(new Runnable(){
            @Override
            public void run() {
                while(true){
                    try {
                        orangeEmpty.acquire();
                        System.out.println("daughter eats an orange");
                        plateFull.release();
                    } catch (Exception e) {}
                }
            }
        });
        fatherThread.start();
        motherThread.start();
        sonThread.start();
        daughterThread.start();
    }
}

  我共设置了三个信号量,一个用来表示盘子里是否有水果,一个表示盘子里是否苹果,最后一个表示盘子里是否有橘子。

  看一下运行结果:

mother puts an orange
daughter eats an orange
father puts an apple
son eats an apple
mother puts an orange
daughter eats an orange
mother puts an orange
daughter eats an orange
mother puts an orange
daughter eats an orange
father puts an apple
son eats an apple
mother puts an orange
daughter eats an orange
mother puts an orange
daughter eats an orange
father puts an apple
son eats an apple
mother puts an orange
daughter eats an orange

  从结果中我们可以很明显的看到,父亲线程是在儿子线程之前运行,母亲线程在女儿线程之前运行。

小结

  我没有设置太多的标志变量,只是用了三个信号量就可以实现四个线程的有序运行。信号量的运用范围还是挺广的,比如我们可以使用它来确保一个对象在使用之前必须已经被初始化,如果初始化和操作在两个不同的线程中运行的话,这样确实很方便而且还不会造成错误。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值