Java同步方法锁定对象还是方法?
Posted
技术标签:
【中文标题】Java同步方法锁定对象还是方法?【英文标题】:Java synchronized method lock on object, or method? 【发布时间】:2011-03-04 02:49:44 【问题描述】:如果我在同一个类中有 2 个同步方法,但每个方法访问不同的变量,2 个线程可以同时访问这 2 个方法吗?锁定是否发生在对象上,或者它是否与同步方法中的变量一样具体?
例子:
class X
private int a;
private int b;
public synchronized void addA()
a++;
public synchronized void addB()
b++;
两个线程能否同时访问执行x.addA(
) 和x.addB()
的X 类的同一个实例?
【问题讨论】:
【参考方案1】:来自synchronized methods 上的“Java™ 教程”:
首先,对同一对象同步方法的两次调用是不可能交错的。当一个线程正在为一个对象执行同步方法时,所有其他为同一对象调用同步方法的线程都会阻塞(暂停执行),直到第一个线程处理完该对象。
来自synchronized blocks 上的“Java™ 教程”:
同步语句对于通过细粒度同步提高并发性也很有用。例如,假设 MsLunch 类有两个从未一起使用的实例字段 c1 和 c2。这些字段的所有更新都必须同步,但没有理由阻止 c1 的更新与 c2 的更新交错 - 这样做会通过创建不必要的阻塞来降低并发性。 我们不使用同步方法或以其他方式使用与此关联的锁,而是创建两个对象来提供锁。
(强调我的)
假设您有 2 个非交错变量。因此,您希望同时从不同的线程访问每个线程。您需要定义 lock 不是在对象类本身上,而是在类 Object 上,如下所示(来自第二个 Oracle 链接的示例):
public class MsLunch
private long c1 = 0;
private long c2 = 0;
private Object lock1 = new Object();
private Object lock2 = new Object();
public void inc1()
synchronized(lock1)
c1++;
public void inc2()
synchronized(lock2)
c2++;
【讨论】:
这是可行的,因为线程有自己的堆栈,但共享实例成员,包括任何声明的锁定对象。【参考方案2】:如果您将方法声明为 已同步(就像您通过键入 public synchronized void addA()
所做的那样),您将在 whole 对象上进行同步,因此两个线程访问不同的变量无论如何,来自同一个对象会相互阻挡。
如果您想一次只同步一个变量,以便两个线程在访问不同变量时不会相互阻塞,您可以在synchronized ()
块中分别同步它们。如果 a
和 b
是您将使用的对象引用:
public void addA()
synchronized( a )
a++;
public void addB()
synchronized( b )
b++;
但是因为它们是原始的,所以你不能这样做。
我建议您改用 AtomicInteger:
import java.util.concurrent.atomic.AtomicInteger;
class X
AtomicInteger a;
AtomicInteger b;
public void addA()
a.incrementAndGet();
public void addB()
b.incrementAndGet();
【讨论】:
如果同步锁定整个对象的方法,那么从同一个对象访问不同变量的两个线程无论如何都会相互阻塞。 这有点误导。在方法上同步在功能上等同于在方法主体周围有一个synchronized (this)
块。对象“this”不会被锁定,而是对象“this”被用作互斥体,并且主体被阻止与其他也在“this”上同步的代码部分同时执行。它对“this”的其他未同步的字段/方法没有影响。
是的,这确实具有误导性。举个实际的例子——看这个——***.com/questions/14447095/…——总结:锁定只在同步方法级别,对象的实例变量可以被其他线程访问
第一个例子从根本上被打破了。如果 a
和 b
是对象,例如Integer
s,在应用 ++
运算符时,您正在用不同对象替换的实例上进行同步。
修正你的答案并初始化 AtomicInteger:AtomicInteger a = new AtomicInteger(0);
也许这个anwser应该被更新为另一个关于同步对象本身的解释:***.com/a/10324280/1099452【参考方案3】:
在java同步中,如果一个线程想要进入同步方法,它将获取该对象的所有同步方法的锁,而不仅仅是该线程正在使用的一个同步方法。 因此,执行 addA() 的线程将在 addA() 和 addB() 上获得锁,因为两者都是同步的。因此具有相同对象的其他线程无法执行 addB()。
【讨论】:
【参考方案4】:是的,它会阻塞其他方法,因为同步方法适用于所指出的 WHOLE 类对象......但无论如何它会阻塞其他线程执行 ONLY在它输入的任何方法 addA 或 addB 中执行求和时,因为当它完成时......一个线程将 FREE 对象,另一个线程将访问另一个方法,依此类推。
我的意思是“同步”是为了阻止另一个线程在特定代码执行时访问另一个线程。所以最后这段代码可以正常工作了。
最后一点,如果有一个 'a' 和 'b' 变量,而不仅仅是一个唯一变量 'a' 或任何其他名称,则无需同步此方法,因为访问其他 var 是完全安全的(其他内存位置)。
class X
private int a;
private int b;
public void addA()
a++;
public void addB()
b++;
同样有效
【讨论】:
【参考方案5】:这个例子(虽然不是很漂亮)可以提供对锁定机制的更多了解。如果 incrementA 同步,而 incrementB 不同步,则 incrementB 将尽快执行,但如果 incrementB 也是同步,那么它必须在 incrementB之前“等待”incrementA 完成> 可以完成它的工作。
这两种方法都被调用到单个实例 - 对象上,在这个例子中它是:job,“竞争”线程是 aThread 和 main。
尝试在 incrementB 中使用 'synchronized' 而没有它,您会看到不同的结果。如果 incrementB 是 'synchronized' 然后它必须等待 incrementA() 完成。每个变体运行几次。
class LockTest implements Runnable
int a = 0;
int b = 0;
public synchronized void incrementA()
for (int i = 0; i < 100; i++)
this.a++;
System.out.println("Thread: " + Thread.currentThread().getName() + "; a: " + this.a);
// Try with 'synchronized' and without it and you will see different results
// if incrementB is 'synchronized' as well then it has to wait for incrementA() to finish
// public void incrementB()
public synchronized void incrementB()
this.b++;
System.out.println("*************** incrementB ********************");
System.out.println("Thread: " + Thread.currentThread().getName() + "; b: " + this.b);
System.out.println("*************** incrementB ********************");
@Override
public void run()
incrementA();
System.out.println("************ incrementA completed *************");
class LockTestMain
public static void main(String[] args) throws InterruptedException
LockTest job = new LockTest();
Thread aThread = new Thread(job);
aThread.setName("aThread");
aThread.start();
Thread.sleep(1);
System.out.println("*************** 'main' calling metod: incrementB **********************");
job.incrementB();
【讨论】:
【参考方案6】:方法声明上的同步是这个的语法糖:
public void addA()
synchronized (this)
a++;
在静态方法上,它是语法糖:
ClassA
public static void addA()
synchronized(ClassA.class)
a++;
我认为,如果 Java 设计者当时知道现在对同步的理解,他们就不会添加语法糖,因为它往往会导致并发的错误实现。
【讨论】:
不正确。同步方法生成与同步(对象)不同的字节码。虽然功能相同,但它不仅仅是语法糖。 我不认为“语法糖”被严格定义为字节码等价物。关键是它在功能上是等效的。 如果 Java 设计者知道什么是已经了解监视器,他们会/应该做不同的事情,而不是基本上模仿 Unix 的内部结构。 Per Brinch Hansen said 'clearly I have laboured in vain' when he saw the Java concurrency primitives. 这是真的。 OP给出的示例似乎锁定了每个方法,但实际上它们都锁定在同一个对象上。非常具有欺骗性的语法。在使用 Java 10 多年后,我不知道这一点。因此,出于这个原因,我会避免使用同步方法。我一直以为每一个用synchronized定义的方法都会创建一个不可见的对象。【参考方案7】:来自 oracle 文档link
使方法同步有两个效果:
首先,同一对象上的同步方法的两次调用不可能交错。当一个线程正在为一个对象执行同步方法时,所有其他为同一对象调用同步方法的线程都会阻塞(暂停执行),直到第一个线程处理完该对象。
其次,当一个同步方法退出时,它会自动与任何后续对同一对象的同步方法调用建立起之前的关系。这保证了对象状态的变化对所有线程都是可见的
查看此文档page 以了解内在锁和锁定行为。
这将回答您的问题:在同一个对象 x 上,当同步方法之一正在执行时,您不能同时调用 x.addA() 和 x.addB()。
【讨论】:
【参考方案8】:如果您有一些未同步的方法正在访问和更改实例变量。在您的示例中:
private int a;
private int b;
当其他线程处于同一对象的同步方法中时,任意数量的线程都可以同时访问这些非同步方法,并且可以对实例变量进行更改。 例如:-
public void changeState()
a++;
b++;
您需要避免非同步方法访问实例变量并对其进行更改的情况,否则使用同步方法没有意义。
在以下场景中:-
class X
private int a;
private int b;
public synchronized void addA()
a++;
public synchronized void addB()
b++;
public void changeState()
a++;
b++;
只有一个线程可以在 addA 或 addB 方法中,但同时任意数量的线程可以进入 changeState 方法。没有两个线程可以同时进入 addA 和 addB(因为对象级锁定),但同时任意数量的线程可以进入 changeState。
【讨论】:
【参考方案9】:这可能不起作用,因为从 Integer 到 int 的装箱和自动装箱取决于 JVM,如果两个不同的数字在 -128 和 127 之间,它们很可能会被散列到同一个地址。
【讨论】:
【参考方案10】:访问的锁在对象上,而不是在方法上。在方法中访问哪些变量是无关紧要的。
在方法中添加“同步”意味着运行代码的线程必须在继续之前获取对象上的锁。添加“静态同步”意味着运行代码的线程必须在继续之前获取类对象上的锁。或者,您可以将代码包装在这样的块中:
public void addA()
synchronized(this)
a++;
这样你就可以指定必须获取锁的对象。
如果您想避免锁定包含对象,您可以选择:
using synchronized blocks that specify different locks 使 a 和 b 原子化(使用 java.util.concurrent.atomic)【讨论】:
【参考方案11】:您可以执行以下操作。在这种情况下,您使用 a 和 b 上的锁来同步,而不是“this”上的锁。我们不能使用 int,因为原始值没有锁,所以我们使用 Integer。
class x
private Integer a;
private Integer b;
public void addA()
synchronized(a)
a++;
public synchronized void addB()
synchronized(b)
b++;
【讨论】:
以上是关于Java同步方法锁定对象还是方法?的主要内容,如果未能解决你的问题,请参考以下文章