寻找有关获取和释放如何使用信号量同步线程的可靠解释

时间:2014-10-25 12:27:28

标签: java multithreading semaphore

我试图操纵这个程序来打印":---)))))"反复。 我知道信号量是控制线程的一种方式,并且获取基本上获取许可(读取)并释放返回信号量的许可证。 (写道)

我在初始化信号量时试图操纵许可证的数量,但我不明白如何将它们同步在一起,因为我无法弄清楚信号量如何与它们如何获取和释放一起运行。

我正在寻找一个有用的解释,它只涉及使用信号量,获取和释放以及它们如何协同工作以正确地使用线程"同步"

import java.lang.Thread;
import java.util.concurrent.*;

public class ThreadSync {
private static boolean runFlag = true; 
private static Semaphore canPrintC = new Semaphore(1);
private static Semaphore canPrintD = new Semaphore(0);
private static Semaphore canPrintP = new Semaphore(0);


public static void main(String [] args) {

    // Create and start each runnable 
    Runnable task1 = new TaskPrintC(); 
    Runnable task2 = new TaskPrintD(); 
    Runnable task3 = new TaskPrintP(); 

    Thread thread1 = new Thread(task1);
    Thread thread2 = new Thread(task2);
    Thread thread3 = new Thread(task3);

    thread1.start(); 
    thread2.start();
    thread3.start();

    // Let them run for 500 ms
    try {
        Thread.sleep(500);
    }
    catch (InterruptedException e) {
        e.printStackTrace(); 
    }

    runFlag = false;

    thread3.interrupt();
    thread2.interrupt();
    thread1.interrupt();

}

public static class TaskPrintC implements Runnable {
    public void run() { 
        while (runFlag) {
            try {
                canPrintC.acquire();

            }
            catch (InterruptedException ex) {
                ex.printStackTrace();
            }
            System.out.printf("%s", ":");
            canPrintD.release();




        }
    }
}
public static class TaskPrintD implements Runnable {
    public void run() { 
        while (runFlag) {
            try {
                canPrintD.acquire();

            }
            catch (InterruptedException ex) {
                ex.printStackTrace();
            }
            System.out.printf("%s", "-");
            canPrintP.release();



        }
    }
}
public static class TaskPrintP implements Runnable {
    public void run() { 
        while (runFlag) {
            try {
                canPrintP.acquire();

            }
            catch (InterruptedException ex) {
                ex.printStackTrace();
            }
            System.out.printf("%s", ")");     
            canPrintC.release();


        }
    }
}       

}

2 个答案:

答案 0 :(得分:0)

信号量 - 解决生产者/消费者问题

信号量的高级解释。

信号量包含一个指示资源是否已锁定或可用的计数。信号量是一种信号机制(“我做完了,你可以继续。”)。资源本身可能不是线程安全的。

  • 生产者

    semObject.Post(); //发送信号

  

将信号量计数增加1.如果线程正在等待   指定的信号量,它被唤醒。[1]

  • 消费

    semObject.Wait(); //等待信号

  

当信号量计数为零时,调用此函数的线程   将等待信号量。当信号量计数非零时,   count将减1并且线程调用此函数   将继续。[1]

参考

[1] Massa,Anthony J.,eCos嵌入式软件开发,Pearson Education,Inc.,2002

答案 1 :(得分:0)

线程执行任务和信号量可以帮助您让任务(或可运行对象)知道彼此的状态(例如,任务A等待来自任务B的输入,而任务B可以向任务A发出输入可用的信号)。任务和线程之间的区别很重要。

为了强调这一点,我采用了你的例子并创建了一个可运行的类,它执行多次打印字符的任务(通过构造函数中的变量配置)。为了模仿序列化行为(任务在彼此之后运行),runnable还知道应该执行打印任务的下一个runnable。

为了完成示例,我还确保执行main方法的线程知道任务何时完成,以便程序在适当的时间停止。在这种情况下使用CountDownLatch(CountDownLatch是信号量的一个非常简单的变体)。

下面的示例可能有点难以理解,但它显示了一些好的做法(重用代码,使用停止标志而不是中断,使用执行程序来运行任务,清理并在发生错误时停止任务)。它还显示了信号量如何协调任务的执行。

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class ChainedSemaphoreTasks {

// amount of times chained tasks are executed.
static int MAX_CHAINED_LOOPS = 3;

// helper to let main-thread know when chained loops have been executed.
final static CountDownLatch MAX_LOOPS_REACHED = new CountDownLatch(1);

public static void main(String[] args) {

    String printChars = ":-)";
    int[] repeatChars = { 1, 3, 5};

    List<ChainedTask> tasks = buildTasks(printChars, repeatChars);
    ExecutorService executor = Executors.newCachedThreadPool();
    for (ChainedTask task : tasks) {
        executor.execute(task);
    }

    try {
        // Trigger first task to start running.
        tasks.get(0).triggerPrintTask();
        // wait for loop to complete, but not too long.
        if (!MAX_LOOPS_REACHED.await(5000L, TimeUnit.MILLISECONDS)) {
            throw new RuntimeException("Chained tasks loop did not complete within timeout.");
        }
        long waitStart = System.currentTimeMillis();
        executor.shutdown();
        if (executor.awaitTermination(1000L, TimeUnit.MILLISECONDS)) {
            System.out.println("All tasks stopped within " + (System.currentTimeMillis() - waitStart) + " ms.");
        } else {
            throw new RuntimeException("Not all chained tasks stopped within timeout.");
        }
    } catch (Exception e) {
        e.printStackTrace();
        // cleanup
        try {
            tasks.get(0).stop();
        } catch (Exception e2) {
            e2.printStackTrace();
        }
        executor.shutdownNow();
    }
}

static List<ChainedTask> buildTasks(String printChars, int[] repeatChars) {

    List<ChainedTask> tasks = new ArrayList<ChainedTask>();
    int maxTasks = printChars.length();
    if (maxTasks != repeatChars.length) {
        throw new IllegalArgumentException("Amount of repeats per pritn character must match amount of characters.");
    }
    for (int i = 0; i < maxTasks; i++) {
        ChainedTask task = new ChainedTask(printChars.charAt(i), repeatChars[i]); 
        tasks.add(task);
        if (i > 0) {
            tasks.get(i - 1).setNextTask(task);
        } 
    }
    // make last task trigger first task - creates an endless loop.
    tasks.get(maxTasks - 1).setNextTask(tasks.get(0));
    tasks.get(maxTasks - 1).setLastTask(true);
    return tasks;
}

static AtomicInteger chainedLoopsCount = new AtomicInteger();

static class ChainedTask implements Runnable {

    // Semaphore to trigger execution 
    Semaphore performTask = new Semaphore(0);
    // If stop is true, task must finish.
    // stop must be volatile to ensure updated value is always visible.
    volatile boolean stop = false;
    // The last task is responsible for stopping execution 
    boolean lastTask;
    // The next task to run after this task.
    ChainedTask nextTask;

    char printChar;
    int repeatAmount;

    ChainedTask(char printChar, int repeatAmount) {
        this.printChar = printChar;
        this.repeatAmount = repeatAmount;
        System.out.println("Created " + printChar + " / " + repeatAmount);
    }

    void triggerPrintTask() {

        performTask.release(repeatAmount);
    }

    void stop() {

        // first indicate to stop 
        stop = true;
        // then release a permit to pickup the stop sign.
        performTask.release();
        // also stop next task, unless this is the last task 
        if (!isLastTask()) {
            getNextTask().stop();
        }
    }

    @Override
    public void run() {

        try {
            while (!stop) {
                runTask();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("Stopped " + printChar + " / " + repeatAmount);
    }

    void runTask() throws Exception {

        // wait for our turn
        performTask.acquire();
        // must check 'stop' after getting permit, see the stop-method:
        // first stop is set to true and then a permit is released.
        if (stop) {
            return;
        }
        // print text for loop-amount
        do {
            System.out.print(printChar);
        } while (performTask.tryAcquire());
        if (isLastTask()) {
            System.out.println();
            // check if we should stop
            if (chainedLoopsCount.incrementAndGet() >= MAX_CHAINED_LOOPS) {
                // since this is the last task, the next task is the first task.
                // stopping the first task will call the stop-method on all tasks, including this one.
                getNextTask().stop();
                // signal main-thread we are done.
                MAX_LOOPS_REACHED.countDown();
            }
            // Sleep for a long time to test what happens when last task hangs.
            // Should trigger the "cleanup" code in the main method.
            // Thread.sleep(10000);
        }
        // trigger next chained task to run
        // this has no effect if next chained task was stopped 
        getNextTask().triggerPrintTask();
    }

    void setNextTask(ChainedTask nextTask) {
        this.nextTask = nextTask;
    }

    ChainedTask getNextTask() {
        return nextTask;
    }

    void setLastTask(boolean lastTask) {
        this.lastTask = lastTask;
    }

    boolean isLastTask() {
        return lastTask;
    }
}
}