我已找到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();
}
}
答案 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类在所有条件下都是线程安全的,那么我仍然会感激您。测试表明如此。