我一直在努力阅读Herlihy和Shavit的多处理器编程艺术。在第二章中,他们用两种不正确的锁实现来激励Peterson的算法。我试图了解LockTwo类的问题,并以下面的代码结束了死锁。在代码之后给出示例输出,其中线程在死锁之前不打印从1到100的所有值。为了在任一线程中发生死锁,while(victim ==i){}
必须永远运行。但是如果两个线程都在运行这个循环,那么其中一个必须退出,因为victim
不能同时为0和1。我错过了缓存相关的东西吗?
class Counter
{
private int value;
private int maxValue;
public Counter(int value, int maxValue)
{
this.value = value;
this.maxValue = maxValue;
}
public int getValue()
{
return value;
}
public int getMaxValue()
{
return maxValue;
}
public void incrementValue()
{
value++;
}
}
class ThreadID
{
private static volatile int nextID = 0;
private static ThreadLocalID threadID = new ThreadLocalID();
public static int get()
{
return threadID.get();
}
public static void reset()
{
nextID = 0;
}
private static class ThreadLocalID extends ThreadLocal<Integer>
{
protected synchronized Integer initialValue()
{
return nextID++;
}
}
}
class IncrementThread implements Runnable
{
private static Counter c = new Counter(0,100);
private static LockTwo lock2 = new LockTwo();
public IncrementThread()
{
}
public void run()
{
int j = ThreadID.get();
String m = "Thread ID " + j;
//System.out.println(m);
while(c.getValue() < c.getMaxValue())
{
lock2.lock();
c.incrementValue();
String s = "Value in counter is " + c.getValue() + " in thread " + j;
System.out.println(s);
lock2.unlock();
}
}
}
interface Lock
{
public void lock();
public void unlock();
}
class LockTwo implements Lock
{
private int victim;
public LockTwo() {};
public void lock()
{
int i = ThreadID.get();
victim = i;
System.out.println("Trying to acquire lock in thread " + i +" with victim " + victim);
while(victim == i) {}
System.out.println("Lock acquired in thread " + i + " with victim " + victim);
}
public void unlock()
{
int i = ThreadID.get();
//victim = i;
System.out.println("Lock released in thread " + i + " with victim " + victim);
}
}
public class SharedCounter
{
public static void main(String[] args) throws InterruptedException
{
Thread thread[] = new Thread[2];
for (int i = 0; i < thread.length; i++)
{
thread[i] = new Thread(new IncrementThread());
thread[i].start();
}
}
}
示例输出
$java SharedCounter
Trying to acquire lock in thread 0 with victim 1
Trying to acquire lock in thread 1 with victim 1
Lock acquired in thread 0 with victim 1
Value in counter is 1 in thread 0
Lock released in thread 0 with victim 1
Trying to acquire lock in thread 0 with victim 0
Lock acquired in thread 1 with victim 0
Value in counter is 2 in thread 1
Lock released in thread 1 with victim 0
Trying to acquire lock in thread 1 with victim 1
Lock acquired in thread 0 with victim 1
Value in counter is 3 in thread 0
Lock released in thread 0 with victim 1
Trying to acquire lock in thread 0 with victim 0
答案 0 :(得分:3)
我怀疑问题是victim
不易变
java中的变量可以在本地缓存到线程中。
这意味着每个线程都有自己的受害者视图,每个视图都有自己的id
即线程0有victim == 0
,Thread1有victim == 1
使用volatile告诉jvm变量将在线程之间使用,并且不应该缓存它。
答案 1 :(得分:0)
这个答案建立在Jim的答案以及与之相关的评论之上。
如果缺少
volatile
关键字,则线程应立即死锁,它们永远不能递增计数器。 - sarva
这是不正确的,没有办法保证写入的值何时传播到其他线程。
顺便说一句,ArtofMP中的
LockTwo
实施具有与volatile
关联的victim
关键字,但勘误表要求删除此关键字。 - sarva
这只是因为与同一章中提出的其他算法的一致性。 Pragma 2.3.1表示victim
,label
等等需要在实践中声明volatile
- 但是在图2.5中victim
被声明为volatile
反正。
如果我将
volatile
关键字添加到victim
,那么当一个线程通过将计数器递增到100而另一个线程在{{1}中等待完成执行时,最后会出现死锁没有改变受害者的价值。 - sarva
这是您想要的行为。考虑一个单线程程序,程序中的以下行没有意义:
while(victim == i) {}
如果没有额外的线程,这将是一个无限循环。
当只有一个线程尝试获取锁(即调用victim = i;
while (victim == i) {}
方法)时,我们可以在双线程程序中获得基本相同的情况。
如果两个线程都调用lock
方法,我们会得到您在最后发现死锁的行为:
lock
并将lock
设置为0并开始循环victim
并将lock
设置为1,然后开始循环victim
现在等于1并进入关键部分victim
方法,则第二个线程将永远停留在循环中,等待lock
变为1 因此,如果两个线程分别多次调用victim
方法,那么(在某种意义上)最后一次调用将无法完成。