public class ThreadTest implements Runnable {
private int counter;
private Date mydate = new Date();
public void upCounter1() {
synchronized (mydate ) {
for (int i = 0; i < 5; i++) {
counter++;
System.out.println("1 " + counter);
}
}
}
public void upCounter2() {
synchronized (mydate ) {
for (int i = 0; i < 5; i++) {
counter++;
System.out.println("2 " + counter);
}
}
}
public void upCounter3() {
synchronized (mydate ) {
for (int i = 0; i < 5; i++) {
counter++;
System.out.println("3 " + counter);
}
}
}
@Override
public void run() {
upCounter1();
upCounter2();
upCounter3();
}
public static void main(String[] args) {
Threadtest mtt = new Threadtest();
Thread t1 = new Thread(mtt);
Thread t2 = new Thread(mtt);
Thread t3 = new Thread(mtt);
t1.start();
t2.start();
t3.start();
}
}
我使用各种同步技术尝试了此代码,并想确保自己得到了正在发生的事情。我已经阅读了很多关于此的文章,但是没有一篇文章对我足够详尽。
所以这是我观察到的:
synchronised (this)
:仅当我将Threadtest的SAME实例提供给所有线程时,此方法才有效,因为如果我给每个线程提供自己的实例,则每个线程都将获得该实例的固有锁,并且可以在不中断其他线程的情况下访问方法。 synchronised (getClass())
,因为这样我就获得了synchronised (mydate)
,其中适用于同步的(this)的规则相同。但这具有不公开的优势。 >我真的不明白这一点。使用this
的“危险”是什么? synchronised (getClass())
,我还可以使用一个私有(private)静态字段。但是,我不能做
synchronised(Date.class)
。 我的解释和观察正确吗? (我基本上想确保我从控制台输出中得出正确的结论)
最佳答案
您的代码就算是慢速线程,也可以说是线程安全的(在按住锁的同时向控制台写入数据)-但是正确和慢速比错误和快速要好!
无论哪种情况,您的代码都是线程安全的-也就是说,每次都会给出完全相同的结果。如果将同一个实例传递给三个不同的线程,则输出的最后一行将为“3 45”(因为只有一个计数器变量),如果为每个线程提供自己的实例,则将有三行显示为“3 15”。对我来说听起来像您了解这一点。
如果这样做,您的代码仍然是线程安全的,但是您将得到三行,如上所述,读为“3 15”。请注意,由于以下原因,您还将更容易出现活跃和死锁问题。
您应该尝试在可能的地方使用私有(private)锁。如果您使用全局可见的对象(例如this
或getClass
或private
或Intern String
以外的可见性字段或从工厂获得的对象),则可能会导致某些其他代码也尝试锁定在您要锁定的对象上。您可能最终等待的时间比获取锁定( liveness 问题)的时间长,甚至在死锁的情况下。
有关可能出问题的详分割析,请参见secure coding guidelines for Java-但请注意,这不仅是安全问题。
由于上述原因,private static
字段比getClass()
或Date.class
更可取。
差不多(当前有一些insignificant byte code differences),但是同样,您应该更喜欢私有(private)锁。
是的,您可能会遇到竞争状况,并且您的代码不再是线程安全的(尽管您没有下面提到的可见性问题)
您不应该这样做,应该始终使用相同的锁来访问变量。除了可以让多个线程同时读取/写入同一个变量以提供竞争条件这一事实外,线程间可见性还存在一个微妙的问题。当另一个线程获得相同的锁时,将在另一个线程中看到Java内存模型guarantees,该Java内存模型ojit_a在释放锁之前由一个线程完成。因此,执行upCounter2
的线程2可能会或可能不会看到执行upCounter1
的线程1的结果。
而不是思考“我需要执行哪些代码块?”您应该考虑“我需要访问哪些状态?”。
是的,但这与您用于同步的对象无关,而是因为您创建了三个不同的ThreadTest
对象,因此具有三个不同的计数器,正如我在对第一个问题的回答中所解释的那样。
确保您了解在一个对象上运行的三个线程与在三个不同对象上运行的一个线程之间的区别。然后,您将可以通过在三个不同对象上运行的三个线程来了解正在观察的行为。