我正在尝试下面的代码片段,它给出了预期的输出,但是我很好奇我要创建1或2个锁定对象吗?
package com.practice;
class A{
String lock="";
A(String lock){
this.lock = lock;
}
A(){ }
public void printMe(int x) {
int counter = x;
synchronized (lock) {
System.out.println(++counter);
}
}
}
public class MultiThreadingPractice {
public static void main(String[] args) {
A a = new A("1");
Thread t1=new Thread(() -> {
a.printMe(1);
});
a.lock = new String();
Thread t2=new Thread(() -> {
a.printMe(1);
});
t1.start();
t2.start();
}
}
答案 0 :(得分:2)
多线程中的锁应始终保持不变吗?是的。
您正在使用字符串(任何对象都可以)作为锁。当您为锁分配一个新值(新字符串)时,这意味着您周围有多个锁实例。只要所有线程都在同一个锁实例上同步就可以,但是您的class A
代码中没有任何公开的内容可以确保这种情况。
在示例中的实际使用中,您是安全的。由于只有在将锁设置为第三个实例和最后一个实例后,才会启动线程,因此直到稳定之前,都不会尝试对锁进行同步。 (3个实例:第一个是对空字符串的初始化;第二个是对提供的构造函数参数“ 1”的初始化;第三个是对另一个空字符串的显式赋值)。因此,尽管此代码“有效”,但只能通过我所说的“巧合”来起作用,即,它在设计上不是线程安全的。
但是,让我们假设您在构造每个线程后立即启动每个线程的情况。这意味着您将在t1运行之后但甚至在t2创建之前重新分配锁定成员。
过一会儿,两个线程将在新的锁实例上同步,但是在您切换锁的那一刻左右,线程t1可能并且可能在使用旧锁的synchronized(lock) { ... }
子句中实例。大约在那个时候,线程t2可以执行并尝试在新的锁实例上进行同步。
简而言之,您已经在打算用来消除计时窗口的机制中创建了计时窗口(种族危险)。
您可以安排进一步的同步级别,以使您可以替换锁,但是我无法想象任何直接的情况都是必要的,有用的或明智的。最好在发生任何争用之前分配一个锁,然后坚持使用该锁。
P.S。 “我要制造多少把锁?” 3.尽管前两个从未使用过。
答案 1 :(得分:0)
想知道我要创建1个或2个锁定对象吗?
程序中的这一行在加载程序时会创建一个String
实例,并且您的两个A
实例均以对同一String
的引用开头。
String lock="";
此行创建第二个String
实例,因为new String(...)
总是会创建一个新对象,而不管是否有其他具有相同值的String都已经 interned 。
a.lock = new String();
答案 2 :(得分:0)
多线程中的锁应始终保持不变吗?否
典型的同步方法是使用同步方法,而不是单独的锁。这种方法不太容易出错,因此是首选。等效于将当前对象用作锁:
synchronized(this) {
...
}
并且由于this
对象通常不是不可变的,因此我们可以得出结论,将可变对象用作锁是一种常见的做法。
当您更改对锁定对象的引用(是的,您要更改引用而不是锁定对象本身)时,您在代码中所做的工作是非常糟糕的方法。它会引发编程错误。