示例:
class X {
private int a;
private int b;
public synchronized void addA(){
a++;
}
public synchronized void addB(){
b++;
}
}
2个线程可以同时访问类X的同一个实例,执行x.addA()
和x.addB()
吗?
class X {
private int a;
private int b;
public synchronized void addA(){
a++;
}
public synchronized void addB(){
b++;
}
}
2个线程可以同时访问类X的同一个实例,执行x.addA()
和x.addB()
吗?
public synchronized void addA()
那样做),你会对整个对象进行同步,因此两个访问同一对象的不同变量的线程将彼此阻塞。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是语法糖,相当于这个:
public void addA() {
synchronized (this) {
a++;
}
}
静态方法中,它是以下语法糖:
ClassA {
public static void addA() {
synchronized(ClassA.class) {
a++;
}
}
我认为如果当时Java的设计者了解现在对于同步的理解,他们就不会添加这种语法糖了,因为它往往导致并发实现出现问题。
来自synchronized方法的Java™教程:
首先,对于同一对象上的两次调用同步方法不可能交错。当一个线程执行一个对象的同步方法时,所有调用该对象的同步方法的其他线程都会阻塞(暂停执行),直到第一个线程完成该对象。
来自同步块的Java™教程:
同步语句还可以用于通过细粒度同步来提高并发性。例如,假设MsLunch类有两个实例字段c1和c2,它们永远不会同时使用。这些字段的所有更新必须同步,但是没有理由防止c1的更新与c2的更新交替进行,这样做会通过创建不必要的阻塞来降低并发性。相反,我们创建两个对象仅提供锁,而不是使用同步方法或以其他方式使用与此关联的锁。(强调在原文中)
假设您有两个互不干扰的变量。因此,您希望同时从不同的线程访问每个变量。您需要将锁定定义在类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++;
}
}
}
锁定的是对象,而不是方法。方法内访问哪些变量是无关紧要的。
将 "synchronized" 添加到方法中意味着运行代码的线程必须在继续之前获取对象上的锁。 将 "static synchronized" 添加到方法中意味着运行代码的线程必须在继续之前获取类对象上的锁。 或者您可以像这样包装代码块:
public void addA() {
synchronized(this) {
a++;
}
}
以便您可以指定必须获取锁的对象。
如果您想避免在包含对象上锁定,您可以选择以下方法之一:
如果您有一些未同步的方法,并且正在访问和更改实例变量。在您的示例中:
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。
这个例子(虽然不太好看)可以更深入地了解锁定机制。如果incrementA是同步的,而incrementB没有同步,则incrementB将立即执行,但如果incrementB也被同步,则它必须“等待”incrementA完成,然后incrementB才能做它的工作。
这两个方法都被调用到单个实例-对象上,在这个例子中是:job,竞争的线程是aThread和main。
尝试在incrementB中使用'同步的'和不使用,您将看到不同的结果。如果incrementB也是'同步的',那么它必须等待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();
}
}
class x{
private Integer a;
private Integer b;
public void addA(){
synchronized(a) {
a++;
}
}
public synchronized void addB(){
synchronized(b) {
b++;
}
}
}
synchronized
方法/块,它将会获取以下锁:
addA()
的线程将会在addA()
和addB()
上获取锁,因为两者都是同步的。addB()
。是的,它会阻塞另一个方法,因为同步方法适用于整个类对象,正如指出的那样...但无论如何,在执行addA或addB方法时,它只会阻塞另一个线程执行,因为当它完成任务后...一个线程将释放对象,另一个线程将访问其他方法,以此类推,完美地工作。
我的意思是,“同步”恰好是为了阻止其他线程在特定代码执行时访问另一个线程。因此,最终这段代码将正常工作。
最后需要注意的是,如果存在'a'和'b'变量,而不仅仅是唯一变量'a'或其他名称,则无需同步这些方法,因为访问其他变量(其他内存位置)是完全安全的。
class X {
private int a;
private int b;
public void addA(){
a++;
}
public void addB(){
b++;
}}
同样有效
synchronized (this)
语句块。对象"this"并没有被锁定,而是将对象"this"用作互斥锁,并且防止该方法与其他也在"this"上同步的代码段同时执行。它对未同步的其他"this"字段/方法没有影响。 - Mark Peters