我知道
同步代码块时,指定哪个对象的锁定 你想用作锁,所以你可以使用一些 第三方对象作为此段代码的锁。那给了你 能够在其中使用多个锁进行代码同步 单个对象。
但是,我不明白需要将参数传递给块。因为我是否传递String的实例并不重要,无论传递给块的参数如何,一些随机类的实例作为同步块的同步块都能正常工作。
所以我的问题是如果无论如何同步阻止两个线程同时进入临界区。那么为什么需要传递一个论点呢? (我的意思是默认获取一些随机对象的锁定。)
我希望我正确地解决了我的问题。
我尝试了以下示例,其中随机参数是同步块。
public class Launcher {
public static void main(String[] args) {
AccountOperations accOps=new AccountOperations();
Thread lucy=new Thread(accOps,"Lucy");
Thread sam=new Thread(accOps,"Sam");
lucy.start();
sam.start();
}
}
使用非静态同步块:
public class AccountOperations implements Runnable{
private Account account = new Account();
public void run(){
for(int i=0;i<5;i++){
makeWithdrawal(10);
}
}
public void makeWithdrawal(int amount){
String str="asd"
synchronized (str /* pass any non-null object the synchronized block works*/) {
if(account.getAmount()>10){
try{
Thread.sleep(5000);
}catch(InterruptedException e){
e.printStackTrace();
}
account.withdraw(amount);
System.out.println(Thread.currentThread().getName()+" has withdrawn 10, current balance "+ account.getAmount());
}else{
System.out.println("Insufficient funds "+account.getAmount());
}
}
}
}
使用静态同步块:
public class AccountOperations implements Runnable{
private static Account account = new Account();
public void run(){
for(int i=0;i<5;i++){
makeWithdrawal(10);
}
}
public static void makeWithdrawal(int amount){
synchronized (String.class /* pass any class literal synchronized block works*/) {
if(account.getAmount()>10){
try{
Thread.sleep(5000);
}catch(InterruptedException e){
e.printStackTrace();
}
account.withdraw(amount);
System.out.println(Thread.currentThread().getName()+" has withdrawn 10, current balance "+ account.getAmount());
}else{
System.out.println("Insufficient funds "+account.getAmount());
}
}
}
}
答案 0 :(得分:23)
因为无论我是否传递String的实例,无论传递给块的参数如何,一些随机类的实例作为同步块的同步块都能正常工作。
参数的目的是双重的:
它可以在同一个对象上同步其他块,这样如果你有两个代码块可能会改变同一个对象的状态,它们就不会干扰彼此。
例如:
public void getSum() {
int sum = 0;
synchronized (this.list) {
for (Thingy t : this.list) {
sum += t.getValue();
}
}
return sum;
}
public void addValue(int value) {
synchronized (this.list) {
this.list.add(new Thingy(value));
}
}
在那里,我们必须跨线程同步对list
的访问。当另一个线程正在调用addValue
时,我们无法调用getSum
并在列表上踩踏。
它可以确保您以正确的粒度进行同步。如果您正在序列化对特定于实例的资源的访问,那么跨实例执行此操作是没有意义的;如果它们在不同的实例上运行,你应该允许多个线程进入块。这就是为什么你要在this
(或更常见的this
的某个字段)上同步特定于实例的资源,或者如果它是一个静态资源而在类(或更常见的某些类字段)上同步。同样,如果您只需要保护其中的特定字段,则无需在this
上进行同步。
例如:
// (In MyClass)
public void getThingySum() {
int sum = 0;
synchronized (this.thingyList) {
for (Thingy t : this.thingyList) {
sum += t.getValue();
}
}
return sum;
}
public void addThingy(Thingy t) {
synchronized (this.thingyList) {
this.thingyList.add(t);
}
}
public void getNiftySum() {
int sum = 0;
synchronized (this.niftyList) {
for (Nifty n : this.niftyList) {
sum += n.getValue();
}
}
return sum;
}
public void addNifty(Nifty n) {
synchronized (this.niftyList) {
this.niftyList.add(t);
}
}
在那里,我们会同步this.thingyList
上的this.thingyList
,而不是this
或MyClass.class
。如果一个线程正在调用getThingySum
而另一个线程调用addNifty
,那就没问题了,因此在this
上进行同步会有点矫枉过正。
重新启动str
示例:
public void makeWithdrawal(int amount){
String str="asd"
synchronized (str /* pass any non-null object the synchronized block works*/) {
if(account.getAmount()>10){
try{
Thread.sleep(5000);
}catch(InterruptedException e){
e.printStackTrace();
}
account.withdraw(amount);
System.out.println(Thread.currentThread().getName()+" has withdrawn 10, current balance "+ account.getAmount());
}else{
System.out.println("Insufficient funds "+account.getAmount());
}
}
}
评论不正确,任何非null
实例不都会充分保护该代码。上面似乎有效的原因是 string interning :所有线程都使用相同的String
实例,因为字符串文字会自动放入字符串intern
池中。 (这意味着你过度同步;它是JVM范围的,而不是特定于实例的。)所以它可以工作,但不是因为它只是任何对象。如果你改为:
String str = "asd";
到
Object o = new Object();
并对其进行同步,它不会对序列化帐户的访问权做任何操作。
在您的示例中,要同步的正确内容是this.account
。
答案 1 :(得分:6)
如果反正同步阻止两个线程同时进入临界区。那么为什么需要传递论证?
同步块根据传递给它的对象决定要停止的线程。您传递的对象充当由synchronized块保护的临界区的标识符。
您的程序中可能有许多关键部分,所有这些部分可以彼此同时执行。例如,如果必须同时访问两个不相关的集合,则可以为每个集合设置单独的临界区。这样,只有当其他线程已经访问同一个集合时,才会停止线程;访问两个不同集合的两个不同线程将被允许同时进行。
你的第一个例子是非平凡的。它起作用的原因是字符串对象被初始化为字符串文字。由于文字的实习,进入函数的所有线程都将获得相同的String
对象,因此同步块将正确保护关键部分。