从另一个线程暂停线程,并从另一个线程停止/启动它

时间:2015-06-18 18:43:02

标签: java multithreading thread-safety

我已找到good hints here。但是我有更困难的任务 - 额外的请求是:
- 我的低优先级永远线程可以从主线程启动/停止(相同) - 但它也必须锁定一个资源以进行独占访问。
- 我的低优先级永远线程可以从另一个高优先级线程暂停/继续(然后它们也锁定并使用那个资源)
- 我也希望低优先级线程不会在每个循环中释放锁定,但只有在被告知时才释放锁定(为了速度目的 - 我需要初始化/取消资源)(如果我有释放锁定每个循环然后我可以让java管理竞争线程并期望高优先级线程将不时获胜。

我想出了解决方案,我认为这是安全和合理的。但我是新手,所以我愿意在我的解决方案中找到错误或改进建议。所以我的问题是:
1)我的解决方案在所有情况下都是真正的线程安全吗?
2)它是最佳解决方案还是可以改进?

如果您同意,则将其用作模板。

这是核心代码:

FlagNotify lowPriorRunReq   = new FlagNotify(false); // low priority task run request
FlagNotify lowPriorPauseReq = new FlagNotify(false); // low priority task pause request (it uses notify)
volatile boolean lowPriorRunFlag = false;  // low priority task run flag
Lock      groupLock        = new ReentrantLock();  // group lock (used to acquire lowPriorRunFlag always correctly)
Semaphore resourceSemaphore = new Semaphore(1);    // main semaphore protecting resource that has to be accessed sequentially

public class PrioritySingleTaskThread extends Thread {
    @Override
    public void run() {
        prn("High Priority Task created");
        groupLock.lock();
        if(lowPriorRunFlag == true) lowPriorPauseReq.setNotify(true);
        resourceSemaphore.acquireUninterruptibly();
        groupLock.unlock();
        accessResource("high priority task");
        resourceSemaphore.release();
        groupLock.lock();
        if(lowPriorPauseReq.get() == true) lowPriorPauseReq.setNotify(false);
        groupLock.unlock();
        prn("High Priority Task closed");
    }
}

public class LowPriorityContinuousThread extends Thread {
    void getResourceSemaphore(){
        groupLock.lock();
        resourceSemaphore.acquireUninterruptibly();
        lowPriorRunFlag = true;
        groupLock.unlock();
        accessResource("low priority init"); // here it is initialization and I want to happen only on request from priority thread
    }
    void releaseResourceSemaphore(){
        accessResource("low priority de-init"); // here it is de-initialization and I want to happen only on request from priority thread
        lowPriorRunFlag = false;
        resourceSemaphore.release();
    }

    @Override
    public void run() {
        while(true){
            //prn("Low Priority Run req: "+lowPriorRunReq.get());
            if(lowPriorRunReq.get() == true && lowPriorRunFlag == false){
                prn("Low  Priority Task starting");
                getResourceSemaphore();
                prn("Low  Priority Task started");
            }
            if(lowPriorRunReq.get() == false && lowPriorRunFlag == true){
                prn("Low  Priority Task stopping");
                releaseResourceSemaphore();
                prn("Low  Priority Task stopped");
                lowPriorRunReq.smartWait(true);
            }
            // note keep checking lowPriorRunFlag. Imagine there is RunFlag detected by high priority thread
            // before de-asserted, then pauseReq would be requested from high priority thread
            // then resource is released when low priority task stops.
            // High priority lock and use resource, but since pauseReq is set
            // this thread would try to access device in order to de-init and pause (unless we check runFlag)
            if(lowPriorPauseReq.get() == true && lowPriorRunFlag == true){
                prn("Low  Priority Task pausing");
                releaseResourceSemaphore();
                prn("Low  Priority Task paused");
                lowPriorPauseReq.smartWait(false);
                getResourceSemaphore();
                prn("Low  Priority Task continue");
            }
            if(lowPriorRunFlag){
                accessResource("low priority task");
            }
        }
    }
}

这里有完整的可编译Java代码,包括测试平台(所以我暗示它是一个安全的解决方案 - 但你永远不会知道这些线程)

import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Main {
    // this inner class is only for setting flag and waiting to it by notify - which does not hog CPU
    public class FlagNotify{
        private Boolean flag; // do not synchro on Boolean - it is immutable, thus new object is created every value change....
        private Object synchro = new Object();

        public FlagNotify(boolean val) {
            flag = val;
        }

        public void setNotify(boolean val) {
            synchronized (synchro) {
                flag = val;
                synchro.notify();
            }
        }

        public boolean get(){
            return flag;
        }
        public void smartWait(boolean expVal){
            synchronized (synchro){
                while(flag != expVal){
                    try {
                        synchro.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    FlagNotify lowPriorRunReq   = new FlagNotify(false); // low priority task run request
    FlagNotify lowPriorPauseReq = new FlagNotify(false); // low priority task pause request (it uses notify)
    volatile boolean lowPriorRunFlag = false;  // low priority task run flag
    Lock      groupLock        = new ReentrantLock();  // group lock (used to acquire lowPriorRunFlag always correctly)
    Semaphore resourceSemaphore = new Semaphore(1);    // main semaphore protecting resource that has to be accessed sequentially

    public class PrioritySingleTaskThread extends Thread {
        @Override
        public void run() {
            prn("High Priority Task created");
            groupLock.lock();
            if(lowPriorRunFlag == true) lowPriorPauseReq.setNotify(true);
            resourceSemaphore.acquireUninterruptibly();
            groupLock.unlock();
            accessResource("high priority task");
            resourceSemaphore.release();
            groupLock.lock();
            if(lowPriorPauseReq.get() == true) lowPriorPauseReq.setNotify(false);
            groupLock.unlock();
            prn("High Priority Task closed");
        }
    }

    public class LowPriorityContinuousThread extends Thread {
        void getResourceSemaphore(){
            groupLock.lock();
            resourceSemaphore.acquireUninterruptibly();
            lowPriorRunFlag = true;
            groupLock.unlock();
            accessResource("low priority init"); // here it is initialization and I want to happen only on request from priority thread
        }
        void releaseResourceSemaphore(){
            accessResource("low priority de-init"); // here it is de-initialization and I want to happen only on request from priority thread
            lowPriorRunFlag = false;
            resourceSemaphore.release();
        }

        @Override
        public void run() {
            while(true){
                //prn("Low Priority Run req: "+lowPriorRunReq.get());
                if(lowPriorRunReq.get() == true && lowPriorRunFlag == false){
                    prn("Low  Priority Task starting");
                    getResourceSemaphore();
                    prn("Low  Priority Task started");
                }
                if(lowPriorRunReq.get() == false && lowPriorRunFlag == true){
                    prn("Low  Priority Task stopping");
                    releaseResourceSemaphore();
                    prn("Low  Priority Task stopped");
                    lowPriorRunReq.smartWait(true);
                }
                // note keep checking lowPriorRunFlag. Imagine there is RunFlag detected by high priority thread
                // before de-asserted, then pauseReq would be requested from high priority thread
                // then resource is released when low priority task stops.
                // High priority lock and use resource, but since pauseReq is set
                // this thread would try to access device in order to de-init and pause (unless we check runFlag)
                if(lowPriorPauseReq.get() == true && lowPriorRunFlag == true){
                    prn("Low  Priority Task pausing");
                    releaseResourceSemaphore();
                    prn("Low  Priority Task paused");
                    lowPriorPauseReq.smartWait(false);
                    getResourceSemaphore();
                    prn("Low  Priority Task continue");
                }
                if(lowPriorRunFlag){
                    accessResource("low priority task");
                }
            }
        }
    }
//-------------------------------------------------------------------------
//-- following functions are meant only for testing
    AtomicInteger clashDetector = new AtomicInteger(0); // only for testing purposes

    public void accessResource(String from){
        prn("Resource used from "+from);
        if(clashDetector.addAndGet(1)>1){
            System.out.println("Clash detected - you are a bad programmer :((((((");
            System.exit(-1);
        }
        sleepRandom(5);
        clashDetector.getAndAdd(-1);
    }

    public void sleepRandom(long maxMiliSec){
        mySleep((long)(Math.random()*maxMiliSec));
    }

    public void mySleep(long miliSec){
        try{
            Thread.sleep(miliSec);
        }catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    void prn(String s){
        System.out.println(s);
    }

    public void test(){
        new LowPriorityContinuousThread().start();
        for(long i=0; i< (long)1e3; i++){
            lowPriorRunReq.setNotify(true);
            for(int j=0; j<Math.random()*100;j++){
                sleepRandom(10);
                new PrioritySingleTaskThread().start();
            }
            //sleepRandom(20);
            lowPriorRunReq.setNotify(false);

            for(int j=0; j<Math.random()*20;j++){
                sleepRandom(10);
                new PrioritySingleTaskThread().start();
            }
            //sleepRandom(20);
        }
        mySleep(200);
        System.out.println("Test OK :)))))))))))))))))))))");
        mySleep(200);
        System.exit(0);
    }

    public static void main(String[] args) throws Exception {
        new Main().test();
    }

}

2 个答案:

答案 0 :(得分:1)

我不知道“启动/停止”“永久线程”意味着什么,但是可用于暂停线程的一种设计模式称为“旋转门”。当只有一个控制器线程被允许“锁定”或“解锁”旋转门时,这个是有用的:

import java.util.concurrent.Semaphore;

class Turnstile {
    private final Semaphore semaphore = Semaphore.new(1);

    // Called only from the "controller" thread.
    public void lock() {
        semaphore.acquire();
    }

    // Called only from the "controller" thread.
    public void unlock() {
        semaphore.release();
    }

    // Called from worker threads.
    public void passThrough() {
        semaphore.lock();
        semaphore.release();
    }
}

最初,旋转栅门处于“未锁定”状态,并且当工作人员调用它时,passThrough()方法会立即返回。如果主线程“锁定”旋转门,那么任何调用passThrough()的工作人员都将被阻塞,直到主人再次“解锁”它。然后,所有工人将一个接一个地“通过”。

如果你想拥有多个“主人”,你可以修改这个例子,但是当你有一个主人希望锁定旋转栅门而另一个人希望它被解锁时,你可以决定如何解决冲突。

然后,您可以编写一个处理冲突解决方案的新Turnstile而不是使用 a {{1实际上阻​​止了工人。

答案 1 :(得分:0)

正如James Large建议在response中使用Turnstile。我确实重写了代码,它至少对我来说看起来更好。所以我做了DoubleTurnstile它看起来像这样:

public class DoubleTurnstile{
    Semaphore  resourceSemaphore = new Semaphore(1);    // main semaphore protecting resource that has to be accessed sequentially
    volatile boolean  lowPriorRunReq    = false; // low priority task run request
    volatile boolean lowPriorPauseReq   = false; // low priority task pause request (it uses notify)
    private Object notifyWait = new Object();
    Lock       groupLock         = new ReentrantLock();  // group lock (used to acquire lowPriorRunFlag always correctly)
    volatile boolean lowPriorRunFlag = false;  // low priority task run flag

    private void myWaitNotify(){
        synchronized (notifyWait){
            try {
                notifyWait.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    private void myNotify(){
        synchronized (notifyWait){
            notifyWait.notify();
        }
    }

    public void highPriorityEnter() {
        groupLock.lock();
        //if (lowPriorRunFlag){ // "if" is not necessary, but correct
            lowPriorPauseReq = true;
        //}
        resourceSemaphore.acquireUninterruptibly();
        groupLock.unlock();
    }

    public void highPriorityLeaves() {
        resourceSemaphore.release();
        groupLock.lock();
        if(lowPriorPauseReq == true){
            lowPriorPauseReq = false;
            myNotify();
        }
        groupLock.unlock();
    }

    public void lowPriorityLoopEnter() {
        while(true){
            if((lowPriorRunReq == true)
                && (lowPriorPauseReq == false)) break;
            myWaitNotify();
        }
        groupLock.lock();
        resourceSemaphore.acquireUninterruptibly();
        lowPriorRunFlag = true;
        groupLock.unlock();
    }

    public boolean lowPriorityLoop_ShallEnd() {
        return (lowPriorRunReq == false)
                || (lowPriorPauseReq == true);
    }
    public void lowPriorityLoop_Leaves() {
        lowPriorRunFlag = false;
        resourceSemaphore.release();
    }

    public void masterLowPriorityRunEn(boolean shallRun) {
        lowPriorRunReq = shallRun;
        myNotify();
    }
}

然后优先级任务使用DoubleTurnstile:

public class PrioritySingleTaskThread extends Thread {
    @Override
    public void run() {
        prn("High Priority Task created");
        dblTurnstile.highPriorityEnter();
        accessResource("high priority task");
        dblTurnstile.highPriorityLeaves();
        prn("High Priority Task closed");
    }
}

和低优先级连续线程使用它如下:

public class LowPriorityContinuousThread extends Thread {
    @Override
    public void run() {
        while(true){
            dblTurnstile.lowPriorityLoopEnter();
            accessResource("low priority init"); // here it is initialization and I want to happen only on request from priority thread
            while(true){
                accessResource("low priority task");
                if(dblTurnstile.lowPriorityLoop_ShallEnd() == true){
                    accessResource("low priority de-init"); // here it is de-initialization and I want to happen only on request from priority thread
                    dblTurnstile.lowPriorityLoop_Leaves();
                    break;
                }

            }
        }
    }
}

现在它更像是模式,更容易重复使用。

带有测试方法的完整可编译代码如下:

import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Main2 {

    DoubleTurnstile dblTurnstile = new DoubleTurnstile();

    public class DoubleTurnstile{
        Semaphore  resourceSemaphore = new Semaphore(1);    // main semaphore protecting resource that has to be accessed sequentially
        volatile boolean  lowPriorRunReq    = false; // low priority task run request
        volatile boolean lowPriorPauseReq   = false; // low priority task pause request (it uses notify)
        private Object notifyWait = new Object();
        Lock       groupLock         = new ReentrantLock();  // group lock (used to acquire lowPriorRunFlag always correctly)
        volatile boolean lowPriorRunFlag = false;  // low priority task run flag

        private void myWaitNotify(){
            synchronized (notifyWait){
                try {
                    notifyWait.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        private void myNotify(){
            synchronized (notifyWait){
                notifyWait.notify();
            }
        }
        // call highPriorityLeaves() after used shared resources
        public void highPriorityEnter() {
            groupLock.lock();
            //if (lowPriorRunFlag){ // "if" is not necessary but correct
                lowPriorPauseReq = true;
            //}
            resourceSemaphore.acquireUninterruptibly();
            groupLock.unlock();
        }
        // call exactly once for each previous highPriorityEnter()
        public void highPriorityLeaves() {
            resourceSemaphore.release();
            groupLock.lock();
            if(lowPriorPauseReq == true){
                lowPriorPauseReq = false;
                myNotify();
            }
            groupLock.unlock();
        }

        public void lowPriorityLoopEnter() {
            while(true){
                if((lowPriorRunReq == true)
                    && (lowPriorPauseReq == false)) break;
                myWaitNotify();
            }
            groupLock.lock();
            resourceSemaphore.acquireUninterruptibly();
            lowPriorRunFlag = true;
            groupLock.unlock();
        }

        public boolean lowPriorityLoop_ShallEnd() {
            return (lowPriorRunReq == false)
                    || (lowPriorPauseReq == true);
        }
        public void lowPriorityLoop_Leaves() {
            lowPriorRunFlag = false;
            resourceSemaphore.release();
        }

        public void masterLowPriorityRunEn(boolean shallRun) {
            lowPriorRunReq = shallRun;
            myNotify();
        }
    }

    public class PrioritySingleTaskThread extends Thread {
        int id;
        public PrioritySingleTaskThread(int id){this.id=id;}
        @Override
        public void run() {
            prn("High Priority Task created");
            dblTurnstile.highPriorityEnter();
            accessResource("high priority task",true, id);
            dblTurnstile.highPriorityLeaves();
            prn("High Priority Task closed");
        }
    }

    public class LowPriorityContinuousThread extends Thread {
        public int id = 0;
        @Override
        public void run() {
            while(true){
                dblTurnstile.lowPriorityLoopEnter();
                accessResource("low priority init",false,id++); // here it is initialization and I want to happen only on request from priority thread
                while(true){
                    accessResource("low priority task",false,id++);
                    if(dblTurnstile.lowPriorityLoop_ShallEnd() == true){
                        accessResource("low priority de-init",false,id++); // here it is de-initialization and I want to happen only on request from priority thread
                        dblTurnstile.lowPriorityLoop_Leaves();
                        break;
                    }

                }
            }
        }
    }

    //-------------------------------------------------------------------------
    //-- following functions are meant only for testing
    AtomicInteger clashDetector = new AtomicInteger(0); // only for testing purposes
    int hiPriorityCnt; // only for testing purposes
    int loPriorityCnt; // only for testing purposes
    int lastLowPriorityId=-1;
    int lastHiPriorityId=-1;
    int hiPriorityOutOfOrder=0;

    public void accessResource(String from,boolean hiPriority, int id) {
        prn("Resource used from " + from+" id: "+id);
        if(hiPriority){
            if( (id - lastHiPriorityId) < 1) {
                // note if id - lastHiPriorityId=+2 (one sample over-jumped) it will be detected
                // when returned to the over-jumped sample,
                // or at the end of the test one sample missing will be detected
                // so no injustice will escape it's punishment ;)
                // On the other hand if we want strictly ==1 then one error will be reported 3 times -
                // 1st when ID: 1->3
                // 2nd when ID: 3->2 // correct error
                // 3rd when ID: 2->4
                System.out.println("High priority jumped over each other - it's not nice but it can happen");
                hiPriorityOutOfOrder++;
            }
            lastHiPriorityId = id;
            hiPriorityCnt++;
        }
        else{
            if( (id - lastLowPriorityId) < 1) {
                System.out.println("LowPriorityLoop request swapped - you are a bad programmer :((((((");
                System.exit(-1);
            }
            lastLowPriorityId = id;
            loPriorityCnt++;
        }
        if (clashDetector.addAndGet(1) > 1) {
            System.out.println("Clash detected - you are a bad programmer :((((((");
            System.exit(-1);
        }
        sleepRandom(5);
        clashDetector.getAndAdd(-1);
    }

    public void sleepRandom(long maxMiliSec) {
        mySleep((long) (Math.random() * maxMiliSec));
    }

    public void mySleep(long miliSec) {
        try {
            Thread.sleep(miliSec);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    void prn(String s) {
        System.out.println(s);
    }

    public void test() {
        int idHiPriority = 0;
        LowPriorityContinuousThread lowPriorThrd = new LowPriorityContinuousThread();
        lowPriorThrd.start();
        for (long i = 0; i < (long) 1e3; i++) {
            dblTurnstile.masterLowPriorityRunEn(true);
            for (int j = 0; j < Math.random() * 100; j++) {
                sleepRandom(10);
                new PrioritySingleTaskThread(idHiPriority++).start();
            }
            //sleepRandom(20);
            dblTurnstile.masterLowPriorityRunEn(false);

            for (int j = 0; j < Math.random() * 20; j++) {
                sleepRandom(10);
                new PrioritySingleTaskThread(idHiPriority++).start();
            }
            //sleepRandom(20);
        }
        mySleep(500);
        boolean testOk = true;
        if(hiPriorityCnt != idHiPriority){
            System.out.println(String.format("Error hiPriorityCnt(%d) != idHiPriority(%d)",
                    hiPriorityCnt, idHiPriority));
            testOk = false;
        }
        if(loPriorityCnt != lowPriorThrd.id){
            System.out.println(String.format("Error loPriorityCnt(%d) != lowPriorThrd.id(%d)",
                    loPriorityCnt, lowPriorThrd.id));
            testOk = false;
        }
        System.out.println("High priority tasks performed: "+hiPriorityCnt);
        System.out.println("High priority out of order: "+hiPriorityOutOfOrder);
        System.out.println("Low priority tasks performed: "+loPriorityCnt);
        if(testOk){
            System.out.println("Test2 OK :)))))))))))))))))))))");
        }else{
            System.out.println("Test Failed :(((((((((((((((((((((");
        }
        mySleep(200);
        System.exit(0);
    }

    public static void main(String[] args) throws Exception {
        new Main2().test();
    }
}

锁的逻辑并没有太大变化。如果您认为我的DoubleTurnstile类在所有条件下都是线程安全的,那么我仍然会感激您。测试表明如此。