关于Java:”同步”synchronized意味着什么?

What does 'synchronized' mean?

我对synchronized关键字的用法和意义有一些疑问。

  • synchronized关键字的意义是什么?
  • 方法应该在何时使用synchronized
  • 它在程序和逻辑上意味着什么?


synchronized关键字是关于不同线程对相同变量、对象和资源进行读写的。这不是爪哇的一个小话题,但这是Sun的一句话:

synchronized methods enable a simple
strategy for preventing thread
interference and memory consistency
errors: if an object is visible to
more than one thread, all reads or
writes to that object's variables are
done through synchronized methods.

简而言之:当您有两个线程在读写同一个"资源"时,比如一个名为foo的变量,您需要确保这些线程以原子方式访问该变量。如果没有synchronized关键字,线程1可能看不到对foo所做的更改线程2,或者更糟的是,它可能只更改了一半。这不是你逻辑上所期望的。

同样,这是爪哇的一个不平凡的话题。要了解更多信息,请浏览SO和InterWebs上的主题:

  • 并发性
  • Java内存模型

继续探索这些主题,直到"BrianGoetz"这个名字与你大脑中的"并发性"这个词永久联系在一起。


好吧,我认为我们有足够的理论解释,所以考虑一下这个代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class SOP {
    public static void print(String s) {
        System.out.println(s+"
"
);
    }
}

public class TestThread extends Thread {
    String name;
    TheDemo theDemo;
    public TestThread(String name,TheDemo theDemo) {
        this.theDemo = theDemo;
        this.name = name;
        start();
    }
    @Override
    public void run() {
        theDemo.test(name);
    }
}

public class TheDemo {
    public synchronized void test(String name) {
        for(int i=0;i<10;i++) {
            SOP.print(name +" ::"+i);
            try{
                Thread.sleep(500);
            } catch (Exception e) {
                SOP.print(e.getMessage());
            }
        }
    }
    public static void main(String[] args) {
        TheDemo theDemo = new TheDemo();
        new TestThread("THREAD 1",theDemo);
        new TestThread("THREAD 2",theDemo);
        new TestThread("THREAD 3",theDemo);
    }
}

注意:只要前一个线程的执行没有完成,synchronized就会阻止下一个线程对方法test()的调用。线程可以一次访问一个方法。没有synchronized所有线程都可以同时访问这个方法。

当线程调用对象的同步方法"test"(此处对象是"thedemo"类的实例)时,它获取该对象的锁,任何新线程都不能调用同一对象的任何同步方法,只要以前获取该锁的线程不释放该锁。

当调用类的任何静态同步方法时,也会发生类似的事情。线程获取与该类关联的锁(在这种情况下,该类实例的任何非静态同步方法都可以由任何线程调用,因为该对象级锁仍然可用)。只要当前持有锁的线程没有释放类级锁,任何其他线程都将无法调用类的任何静态同步方法。

同步输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
THREAD 1 :: 0
THREAD 1 :: 1
THREAD 1 :: 2
THREAD 1 :: 3
THREAD 1 :: 4
THREAD 1 :: 5
THREAD 1 :: 6
THREAD 1 :: 7
THREAD 1 :: 8
THREAD 1 :: 9
THREAD 3 :: 0
THREAD 3 :: 1
THREAD 3 :: 2
THREAD 3 :: 3
THREAD 3 :: 4
THREAD 3 :: 5
THREAD 3 :: 6
THREAD 3 :: 7
THREAD 3 :: 8
THREAD 3 :: 9
THREAD 2 :: 0
THREAD 2 :: 1
THREAD 2 :: 2
THREAD 2 :: 3
THREAD 2 :: 4
THREAD 2 :: 5
THREAD 2 :: 6
THREAD 2 :: 7
THREAD 2 :: 8
THREAD 2 :: 9

不同步输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
THREAD 1 :: 0
THREAD 2 :: 0
THREAD 3 :: 0
THREAD 1 :: 1
THREAD 2 :: 1
THREAD 3 :: 1
THREAD 1 :: 2
THREAD 2 :: 2
THREAD 3 :: 2
THREAD 1 :: 3
THREAD 2 :: 3
THREAD 3 :: 3
THREAD 1 :: 4
THREAD 2 :: 4
THREAD 3 :: 4
THREAD 1 :: 5
THREAD 2 :: 5
THREAD 3 :: 5
THREAD 1 :: 6
THREAD 2 :: 6
THREAD 3 :: 6
THREAD 1 :: 7
THREAD 2 :: 7
THREAD 3 :: 7
THREAD 1 :: 8
THREAD 2 :: 8
THREAD 3 :: 8
THREAD 1 :: 9
THREAD 2 :: 9
THREAD 3 :: 9


synchronized关键字防止多个线程同时访问代码块或对象。默认情况下,Hashtablesynchronized,因此一次只能有一个线程访问表。

在使用non-synchronized构造(如HashMap构造)时,必须在代码中构建线程安全特性,以防止内存一致性错误。


synchronized是指在多线程环境中,具有synchronized方法/块的对象不允许两个线程同时访问synchronized方法/块代码。这意味着当另一个线程更新它时,一个线程无法读取。

第二个线程将等待直到第一个线程完成其执行。开销是速度,但优点是保证数据的一致性。

如果您的应用程序是单线程的,那么synchronized块并不能提供好处。


synchronized关键字使线程在进入方法时获得锁,以便只有一个线程可以同时执行该方法(对于给定的对象实例,除非它是静态方法)。

这通常被称为使类线程安全,但我会说这是一种委婉的说法。虽然同步确实可以保护向量的内部状态不被破坏,但这通常对向量的用户没有多大帮助。

考虑一下:

1
2
3
 if (vector.isEmpty()){
     vector.add(data);
 }

尽管所涉及的方法是同步的,因为它们是单独锁定和解锁的,但不幸的是,两个定时线程可以用两个元素创建一个向量。

所以实际上,您也必须在应用程序代码中进行同步。

因为方法级同步在a)不需要时代价高昂,b)在需要同步时不够,所以现在有未同步的替换项(向量的情况下是arraylist)。

最近,Concurrency软件包已经发布了,其中有一些聪明的实用程序可以处理多线程问题。


概述

爪哇中的同步关键字与线程安全有关,也就是说,当多个线程读取或写入相同的变量时。这可以直接(通过访问同一个变量)或间接(通过使用使用另一个访问同一个变量的类)发生。

synchronized关键字用于定义一个代码块,多个线程可以安全地访问同一个变量。

更深的

从语法上讲,synchronized关键字将Object作为参数(称为锁对象),然后是{ block of code }

  • 当执行遇到此关键字时,当前线程将尝试"锁定/获取/拥有"(选择)锁定对象,并在获取锁后执行关联的代码块。

  • 对同步代码块内变量的任何写入都保证对使用相同锁对象在同步代码块内类似执行代码的每个其他线程都可见。

  • 一次只能有一个线程持有锁,在此期间,所有试图获取同一锁对象的其他线程都将等待(暂停执行)。当执行退出同步代码块时,锁将被释放。

同步方法:

在方法定义中添加synchronized关键字等于整个方法体被一个同步的代码块包裹,锁对象为this(例如方法)和ClassInQuestion.getClass()(对于类方法)。

-实例方法是一种没有static关键字的方法。-类方法是一种具有static关键字的方法。

技术

如果没有同步,就不能保证按哪个顺序进行读写,可能会将变量留下垃圾。(例如,一个变量可能以一个线程写入的一半位和另一个线程写入的一半位结束,使变量处于两个线程都不尝试写入的状态,但两者的组合混乱。)

在另一个线程读取某个线程之前(墙时钟时间),完成该线程中的写入操作是不够的,因为硬件可能缓存了变量的值,而读取线程将看到缓存的值,而不是写入的值。

结论

因此,在Java的情况下,必须遵循Java内存模型,以确保线程错误不会发生。换言之:使用同步、原子操作或在防护罩下为您使用它们的类。

Sources

http://docs.oracle.com/javase/specs/jls/se8/html/index.html
Java? Language Specification, 2015-02-13


把它想象成足球场上的旋转栅门。有平行的人想进去,但在旋转栅门,他们是"同步的"。一次只能有一个人通过。所有那些想通过的人都会这样做,但他们可能不得不等到他们能通过。


What is the synchronized keyword?

线程主要通过共享对字段和对象引用字段的访问来进行通信。这种形式的通信非常有效,但有两种错误是可能的:线程干扰和内存一致性错误。防止这些错误所需的工具是同步。

同步块或方法可以防止线程干扰,并确保数据一致。在任何时候,只有一个线程可以通过获取锁来访问同步块或方法(关键部分)。其他线程将等待释放锁以访问关键部分。

When are methods synchronized?

在方法定义或声明中添加synchronized时,方法是同步的。您还可以在方法中同步特定代码块。

What does it mean pro grammatically and logically?

这意味着只有一个线程可以通过获取锁来访问关键部分。除非此线程释放此锁,否则所有其他线程都必须等待获取锁。他们没有进入关键区域的权限,无法获取锁。

这不能用魔法来完成。程序员的职责是识别应用程序中的关键部分并相应地保护它。Java提供了一个框架来保护您的应用程序,但是在哪里和什么部分要被保护是程序员的责任。

更多来自Java文档页的细节

内部锁和同步:

Synchronization is built around an internal entity known as the intrinsic lock or monitor lock. Intrinsic locks play a role in both aspects of synchronization: enforcing exclusive access to an object's state and establishing happens-before relationships that are essential to visibility.

每个对象都有一个与之相关联的内部锁。按照惯例,需要对对象字段进行独占和一致访问的线程必须在访问前获取对象的内部锁,然后在完成对其的访问后释放内部锁。

据说线程在获取锁和释放锁之间拥有内部锁。只要一个线程拥有一个内部锁,其他线程就不能获取相同的锁。另一个线程在试图获取锁时将阻塞。

When a thread releases an intrinsic lock, a happens-before relationship is established between that action and any subsequent acquisition of the same lock.

使方法同步有两种效果:

First, it is not possible for two invocations of synchronized methods on the same object to interleave.

当一个线程正在为一个对象执行同步方法时,对同一对象块调用同步方法的所有其他线程(挂起执行),直到第一个线程完成对该对象的操作。

Second, when a synchronized method exits, it automatically establishes a happens-before relationship with any subsequent invocation of a synchronized method for the same object.

这保证了对对象状态的更改对所有线程都是可见的。

在以下位置查找同步的其他替代方法:

在Java中避免同步(这个)吗?


下面是Java教程的一个解释。

请考虑以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class SynchronizedCounter {
    private int c = 0;

    public synchronized void increment() {
        c++;
    }

    public synchronized void decrement() {
        c--;
    }

    public synchronized int value() {
        return c;
    }
}

if count is an instance of SynchronizedCounter, then making these methods synchronized has two effects:

  • First, it is not possible for two invocations of synchronized methods on the same object to interleave. When one thread is executing a synchronized method for an object, all other threads that invoke synchronized methods for the same object block (suspend execution) until the first thread is done with the object.
  • Second, when a synchronized method exits, it automatically establishes a happens-before relationship with any subsequent invocation of a synchronized method for the same object. This guarantees that changes to the state of the object are visible to all threads.

Synchronized normal method相当于Synchronized statement(用这个)

1
2
3
4
5
6
7
8
9
10
11
12
13
class A {
    public synchronized void methodA() {
        // all function code
    }

    equivalent to

    public void methodA() {
        synchronized(this) {
             // all function code
        }
    }
}

Synchronized static method相当于Synchronized statement(使用类别)

1
2
3
4
5
6
7
8
9
10
11
12
13
class A {
    public static synchronized void methodA() {
        // all function code
    }

    equivalent to

    public void methodA() {
        synchronized(A.class) {
             // all function code
        }
    }
}

同步语句(使用变量)

1
2
3
4
5
6
7
8
9
class A {
    private Object lock1 = new Object();

    public void methodA() {
        synchronized(lock1 ) {
             // all function code
        }
    }
}

对于synchronized,我们有Synchronized MethodsSynchronized Statements。但是,Synchronized MethodsSynchronized Statements很相似,所以我们只需要了解Synchronized Statements

=>基本上,我们会

1
2
3
synchronized(object or class) { // object/class use to provides the intrinsic lock
   // code
}

以下是2种有助于理解synchronized的想法

  • 每个对象/类都有一个与之相关联的intrinsic lock
  • 当线程调用Synchronized statement时,它自动获取该synchronized statement's对象的intrinsic lock并在方法返回时释放它。只要一个线程拥有一个intrinsic lock,其他线程就不能获得相同的锁=>线程安全。

= >当一个thread A调用synchronized(this){// code 1}时=>所有有synchronized(this)和所有Synchronized normal method的块代码(内部类)都因为同一锁而被锁定。它将在thread A解锁后执行("//code 1"完成)。

这种行为与synchronized(a variable){// code 1}synchronized(class)相似。

相同的锁=>锁(不取决于哪种方法?还是什么陈述?)

使用同步方法还是同步语句?

我更喜欢Synchronized Statements,因为它更容易扩展。例如,在将来,您只需要同步方法的一部分。例如,您有两个synchronized方法,它们之间没有任何关联,但是当一个线程运行一个方法时,它会阻塞另一个方法(它可以通过使用synchronized(a variable))。

然而,apply-synchronized方法很简单,代码看起来也很简单。对于某些类,只有一个同步方法,或者类中所有相关的同步方法=>我们可以使用synchronized method来缩短代码并使代码易于理解。

注释

(它与synchronized关系不大,它是对象和类之间的区别,或者不是静态的和静态的)。

  • 当您使用synchronized或normal方法或synchronized(this)synchronized(non-static variable)时,它将根据每个对象实例进行同步。
  • 当您使用synchronized或static方法或synchronized(class)synchronized(static variable)时,它将根据类进行同步。

参考文献

https://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.htmlhttps://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html

希望有帮助


根据我的理解,synchronized基本上意味着编译器在方法周围编写monitor.enter和monitor.exit。因此,它可能是线程安全的,这取决于它是如何使用的(我的意思是,您可以使用同步方法编写一个对象,而非线程安全,这取决于您的类所做的操作)。


其他答案缺失的是一个重要方面:记忆障碍。线程同步基本上由两部分组成:序列化和可见性。我建议大家去谷歌搜索"JVM内存屏障",因为这是一个非常重要的话题(如果修改多线程访问的共享数据)。这样做之后,我建议查看java.util.concurrent包中帮助避免使用显式同步的类,这反过来有助于保持程序的简单和高效,甚至可以防止死锁。

一个这样的例子是ConcurrentLinkedSeque。与命令模式一起,它允许通过将命令填充到并发队列中来创建高效的工作线程——不需要显式同步,不可能出现死锁,不需要显式sleep(),只需调用take()来轮询队列。

简而言之:"内存同步"是在启动线程、线程结束、读取易失性变量、解锁监视器(保留同步块/函数)等时隐式发生的。这种"同步"会影响(在某种意义上说是"刷新")在该特定操作之前完成的所有写入操作。在上述同时链接的情况下,文档"说明"如下:

Memory consistency effects: As with other concurrent collections,
actions in a thread prior to placing an object into a
ConcurrentLinkedDeque happen-before actions subsequent to the access
or removal of that element from the ConcurrentLinkedDeque in another
thread.

这种隐含的行为是一个有害的方面,因为大多数没有经验的Java程序员会因为它而付出很多。然后在Java没有执行在生产中有不同工作负载的"假设"时,突然遇到了这个问题,而且很难测试并发问题。


synchronized简单地说,如果在特定对象上使用synchronized块,那么多个线程(如果与单个对象关联)可以防止脏的读写。为了让您更清楚地了解,让我们举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class MyRunnable implements Runnable {
    int var = 10;
    @Override
    public void run() {
        call();
    }

    public void call() {
        synchronized (this) {
            for (int i = 0; i < 4; i++) {
                var++;
                System.out.println("Current Thread" + Thread.currentThread().getName() +" var value"+var);
            }
        }
    }
}

public class MutlipleThreadsRunnable {
    public static void main(String[] args) {
        MyRunnable runnable1 = new MyRunnable();
        MyRunnable runnable2 = new MyRunnable();
        Thread t1 = new Thread(runnable1);
        t1.setName("Thread -1");
        Thread t2 = new Thread(runnable2);
        t2.setName("Thread -2");
        Thread t3 = new Thread(runnable1);
        t3.setName("Thread -3");
        t1.start();
        t2.start();
        t3.start();
    }
}

我们创建了两个MyUnnable类对象,runnable1与线程1共享,线程3&runnable2仅与线程2共享。现在,当T1和T3在不使用同步的情况下启动时,pfb输出表明线程1和3同时影响var值,其中对于线程2,var有自己的内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Without Synchronized keyword

    Current Thread Thread -1 var value 11
    Current Thread Thread -2 var value 11
    Current Thread Thread -2 var value 12
    Current Thread Thread -2 var value 13
    Current Thread Thread -2 var value 14
    Current Thread Thread -1 var value 12
    Current Thread Thread -3 var value 13
    Current Thread Thread -3 var value 15
    Current Thread Thread -1 var value 14
    Current Thread Thread -1 var value 17
    Current Thread Thread -3 var value 16
    Current Thread Thread -3 var value 18

使用同步,线程3等待线程1在所有场景中完成。获取了两个锁,一个位于线程1和线程3共享的runnable1上,另一个位于仅由线程2共享的runnable2上。

1
2
3
4
5
6
7
8
9
10
11
12
Current Thread Thread -1 var value 11
Current Thread Thread -2 var value 11
Current Thread Thread -1 var value 12
Current Thread Thread -2 var value 12
Current Thread Thread -1 var value 13
Current Thread Thread -2 var value 13
Current Thread Thread -1 var value 14
Current Thread Thread -2 var value 14
Current Thread Thread -3 var value 15
Current Thread Thread -3 var value 16
Current Thread Thread -3 var value 17
Current Thread Thread -3 var value 18


同步简单意味着没有两个线程可以同时访问块/方法。当我们说类的任何块/方法都是同步的,这意味着一次只有一个线程可以访问它们。在内部,尝试访问它的线程首先在该对象上获取一个锁,并且只要该锁不可用,其他线程就不能访问该类实例的任何同步方法/块。

注意:另一个线程可以访问同一个对象的方法,该方法未定义为要同步。线程可以通过调用

1
Object.wait()

同步是爪哇中的一个关键字,用于在多线程环境中发生关系之前,以避免内存不一致和线程干扰错误。