什么'同步'的意思吗?

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

  • synchronized关键字的意义是什么?
  • 什么时候方法应该是synchronized?
  • 它在程序上和逻辑上意味着什么?
538565 次浏览

以我的理解,同步基本上意味着编译器编写一个监视器。进入并监控。在方法周围退出。因此,它可能是线程安全的,这取决于它是如何使用的(我的意思是,你可以编写一个具有同步方法的对象,它不是线程安全的,这取决于你的类做什么)。

synchronized关键字是关于不同的线程读写相同的变量、对象和资源。这在Java中不是一个微不足道的话题,但这里引用Sun的一段话:

synchronized方法启用简单的防止线程的策略干扰和记忆一致性错误:如果对象是可见的多于一个线程,全部读取或写入该对象的变量为

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

同样,这在Java中是一个重要的主题。要了解更多,请在这里探索关于SO和互联网的主题:

继续探索这些话题,直到在你的脑海中“Brian Goetz”这个名字和“并发”这个词永久地联系在一起。

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

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

考虑一下:

 if (vector.isEmpty()){vector.add(data);}

尽管所涉及的方法是同步的,但由于它们分别被锁定和解锁,两个不幸计时的线程可以创建具有两个元素的向量。

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

因为方法级同步是a)当你不需要它的时候很昂贵,b)当你需要同步的时候不够用,所以现在有了不同步的替换(Vector的情况下是ArrayList)。

最近,并发包已经发布,其中包含许多处理多线程问题的聪明实用程序。

你可以把它想象成一个转门,就像你在足球场看到的那样。有平行的人群想要进入,但在旋转门他们是“同步的”。一次只能有一个人通过。所有想要通过的人都可以,但他们可能要等到他们能够通过。

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

第二个线程将等待第一个线程完成它的执行。开销是速度,但好处是保证了数据的一致性。

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

synchronized关键字防止多个线程并发访问一个代码块或对象。Hashtable的所有方法都是synchronized,所以一次只有一个线程可以执行它们中的任何一个。

当使用非synchronized结构(如HashMap)时,必须在代码中构建线程安全特性以防止一致性错误。

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

注意,另一个线程可以访问同一对象的方法,该方法没有被定义为同步。线程可以通过调用来释放锁

Object.wait()

好了,我认为我们已经有了足够多的理论解释,所以考虑一下这段代码

public class SOP {public static void print(String s) {System.out.println(s+"\n");}}
public class TestThread extends Thread {String name;TheDemo theDemo;public TestThread(String name,TheDemo theDemo) {this.theDemo = theDemo;this.name = name;start();}@Overridepublic 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'类的一个实例),它获得了该对象的锁,任何新的线程都不能调用同一对象的任何同步方法,只要之前获得锁的线程没有释放锁。

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

输出同步

THREAD 1 :: 0THREAD 1 :: 1THREAD 1 :: 2THREAD 1 :: 3THREAD 1 :: 4THREAD 1 :: 5THREAD 1 :: 6THREAD 1 :: 7THREAD 1 :: 8THREAD 1 :: 9THREAD 3 :: 0THREAD 3 :: 1THREAD 3 :: 2THREAD 3 :: 3THREAD 3 :: 4THREAD 3 :: 5THREAD 3 :: 6THREAD 3 :: 7THREAD 3 :: 8THREAD 3 :: 9THREAD 2 :: 0THREAD 2 :: 1THREAD 2 :: 2THREAD 2 :: 3THREAD 2 :: 4THREAD 2 :: 5THREAD 2 :: 6THREAD 2 :: 7THREAD 2 :: 8THREAD 2 :: 9

输出未同步

THREAD 1 :: 0THREAD 2 :: 0THREAD 3 :: 0THREAD 1 :: 1THREAD 2 :: 1THREAD 3 :: 1THREAD 1 :: 2THREAD 2 :: 2THREAD 3 :: 2THREAD 1 :: 3THREAD 2 :: 3THREAD 3 :: 3THREAD 1 :: 4THREAD 2 :: 4THREAD 3 :: 4THREAD 1 :: 5THREAD 2 :: 5THREAD 3 :: 5THREAD 1 :: 6THREAD 2 :: 6THREAD 3 :: 6THREAD 1 :: 7THREAD 2 :: 7THREAD 3 :: 7THREAD 1 :: 8THREAD 2 :: 8THREAD 3 :: 8THREAD 1 :: 9THREAD 2 :: 9THREAD 3 :: 9

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

概述

Java中的Synchronized关键字与线程安全有关,即多个线程对同一个变量进行读写这可以直接发生(通过访问相同的变量)或间接发生(通过使用使用访问相同变量的另一个类的类)

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

更深层次的

语法方面,synchronized关键字接受Object作为参数(称为锁对象),然后后面跟着{ block of code }

  • 当执行遇到这个关键字时,当前线程尝试“锁定/获取/拥有”(选)锁对象,并在获得锁后执行相关的代码块。

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

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

同步的方法:

synchronized关键字添加到方法定义中等于将整个方法体包装在同步代码块中,其中锁对象this (对于实例方法)ClassInQuestion.getClass() (对于类方法)

< br > < p > # 1—类方法是一个有static关键字的方法

技术

如果没有同步,就不能保证读取和写入的顺序,可能会让变量变成垃圾(例如,一个变量可能最终由一个线程写入一半的比特,另一个线程写入一半的比特,使变量处于两个线程都没有尝试写入的状态,而是两个线程都试图写入的混乱状态。

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

结论

因此,在Java的情况下,你必须遵循Java内存模型,以确保线程错误不会发生换句话说:在幕后使用同步、原子操作或为你使用它们的类

来源

< p > # 0 < br >Java®语言规范,2015-02-13

synchronized关键字是什么?

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

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

方法何时同步?

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

它在语法上和逻辑上意味着什么?

这意味着只有一个线程可以通过获取锁来访问临界区。除非这个线程释放这个锁,否则所有其他线程将不得不等待获得一个锁。他们不能在没有获得锁的情况下访问临界区

这不是魔法能办到的。程序员有责任识别应用程序中的临界段(s)并相应地保护它。Java提供了一个框架来保护你的应用程序,但是在哪里以及保护哪些部分是程序员的责任。

更多细节请参阅java文档页面

内在锁和同步:

同步是围绕一个称为内在锁或监视器锁的内部实体构建的。内在锁在同步的两个方面都发挥作用:强制对对象状态的独占访问,并建立对可见性至关重要的happens-before关系。

# 0。按照惯例,需要独占和一致访问对象字段的线程必须在访问对象字段之前获得对象的内在锁,然后在使用完对象字段时释放内在锁。

线程在获得锁和释放锁之间拥有内在锁。当另一个线程试图获取锁时,它将阻塞。

当线程释放一个内在锁时,在该操作和后续获得的任何相同锁之间建立happens-before关系。

使方法同步有两个影响:

首先,对同一对象的同步方法的两次调用不可能交织。

当一个线程正在为一个对象执行同步方法时,所有为同一对象调用同步方法的其他线程将暂停执行,直到第一个线程处理完该对象。

其次,当同步方法退出时,它自动与同一对象的同步方法的任何后续调用建立happens-before关系。

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

寻找同步的其他替代方案:

在Java中避免同步(这)?< / >

下面是第0条的解释。

考虑下面的代码:

public class SynchronizedCounter {private int c = 0;
public synchronized void increment() {c++;}
public synchronized void decrement() {c--;}
public synchronized int value() {return c;}}

如果countSynchronizedCounter的实例,那么使这些方法同步有两个效果:

  • 首先,对同一对象的同步方法的两次调用不可能交织。当一个线程正在为一个对象执行同步方法时,所有为同一对象调用同步方法的其他线程将暂停执行,直到第一个线程处理完该对象。
  • 其次,当同步方法退出时,它自动与同一对象的同步方法的任何后续调用建立happens-before关系。这保证了对对象状态的更改对于所有线程都是可见的。

Synchronized的意思是,如果在特定对象上使用Synchronized块,那么与单个对象关联的多个线程可以防止脏读写。为了让你更清楚,让我们举个例子:

class MyRunnable implements Runnable {int var = 10;@Overridepublic 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();}}
我们已经创建了两个MyRunnable类对象,runnable1与线程1和线程3共享。Runnable2只与线程2共享。现在,当t1和t3启动而没有使用synchronized时,PFB输出表明线程1和线程3同时影响var值,其中对于线程2,var有自己的内存
Without Synchronized keyword
Current Thread Thread -1 var value 11Current Thread Thread -2 var value 11Current Thread Thread -2 var value 12Current Thread Thread -2 var value 13Current Thread Thread -2 var value 14Current Thread Thread -1 var value 12Current Thread Thread -3 var value 13Current Thread Thread -3 var value 15Current Thread Thread -1 var value 14Current Thread Thread -1 var value 17Current Thread Thread -3 var value 16Current Thread Thread -3 var value 18

使用Synchronzied,线程3在所有场景中等待线程1完成。有两个锁,一个在runnable1上,由线程1和线程3共享,另一个在runnable2上,仅由线程2共享。

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

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

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

简而言之:“内存同步”发生在线程启动、线程结束、读取volatile变量、解锁监视器(留下同步块/函数)等情况下。这种“同步”影响(在某种意义上“刷新”)在特定操作之前完成的所有写入。在前面提到的ConcurrentLinkedDeque的情况下,文档“说”:

内存一致性影响:与其他并发集合一样,对象放入线程之前的操作ConcurrentLinkedDeque 之前动作之后的访问或者从另一个ConcurrentLinkedDeque中删除该元素线程。< / p >

这种隐式行为在某种程度上是有害的,因为大多数没有太多经验的Java程序员会因此而得过其实。然后突然被这个线程绊倒,因为Java在生产中没有做它“应该”做的事情,因为有不同的工作负载——并且很难测试并发性问题。

Synchronized normal method等效于Synchronized statement(使用这个)

class A {public synchronized void methodA() {// all function code}
equivalent to
public void methodA() {synchronized(this) {// all function code}}}

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

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

同步语句(使用变量)

class A {private Object lock1 = new Object();
public void methodA() {synchronized(lock1 ) {// all function code}}}

对于synchronized,我们有Synchronized MethodsSynchronized Statements。然而,第一条和第二条很相似,所以我们只需要理解第二条。

基本上,我们会有

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

以下两点有助于理解第0条

  • 每个对象/类都有一个与之相关的intrinsic lock
  • 当线程调用synchronized statement时,它会自动为synchronized statement's对象获取intrinsic lock,并在方法返回时释放它。只要线程拥有intrinsic lock没有其他的线程就可以获得相同 lock =>线程安全。
< p > = >当一个thread A调用synchronized(this){// code 1} =>时,所有的块代码(类内部),其中有synchronized(this)和所有synchronized normal method(类内部)被锁定,因为相同锁定。它将在thread A解锁("// code 1"完成)后执行。< / p >

这种行为类似于synchronized(a variable){// code 1}synchronized(class)

同一个锁 =>锁(不依赖于哪个方法?或者哪些陈述?)

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

我更喜欢synchronized statements,因为它的可扩展性更强。例如,在将来,你只需要同步方法的一部分。例如,你有两个同步的方法和它没有了彼此相关,但是当一个线程运行一个方法时,它会阻塞另一个方法(它可以通过使用synchronized(a variable)来防止)。

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

请注意

(它与synchronized没有太大关系,它是对象和类或非静态和静态之间的区别)。

  • 当你使用synchronized或普通方法或synchronized(this)synchronized(non-static variable)时,它将基于每个对象实例进行同步。
  • 当你使用synchronized或静态方法或synchronized(class)synchronized(static variable)时,它将基于类进行同步

参考

https://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html# 0 < / p >

希望能有所帮助

Java同步

# 0 # 2 =比;# 1

synchronized块在Java中是多线程中的监视器。同一个对象/类的synchronized块只能由单个线程执行,其他的都在等待。当几个线程试图更新同一个变量时,它可以帮助解决race condition的情况。

Java 5通过支持happens-before<一口>[对]< /一口>扩展了synchronized

监视器的解锁(同步块或方法退出)发生在同一监视器的每个后续锁定(同步块或方法进入)之前。

下一步是java.util.concurrent

在java中,为了防止多个线程操纵一个共享变量,我们使用synchronized关键字。让我们通过下面的例子来理解它:

在这个例子中,我定义了两个线程,并将它们命名为increment和decincrement。增量线程增加共享变量(counter)的值与递减线程减少共享变量(counter)的值相同,即增加5000次(结果是5000 + 0 = 5000),减少5000次(结果是5000 - 5000 = 0)。

没有synchronized关键字的程序:

class SynchronizationDemo {
public static void main(String[] args){
Buffer buffer = new Buffer();
MyThread incThread = new MyThread(buffer, "increment");MyThread decThread = new MyThread(buffer, "decrement");
incThread.start();decThread.start();       
try {incThread.join();decThread.join();}catch(InterruptedException e){ }
System.out.println("Final counter: "+buffer.getCounter());}}
class Buffer {private int counter = 0;public void inc() { counter++; }public void dec() { counter--; }public int getCounter() { return counter; }}
class MyThread extends Thread {
private String name;private Buffer buffer;
public MyThread (Buffer aBuffer, String aName) {buffer = aBuffer;name = aName;}
public void run(){for (int i = 0; i <= 5000; i++){if (name.equals("increment"))buffer.inc();elsebuffer.dec();}}}

如果我们运行上面的程序,我们期望缓冲区的值是相同的,因为缓冲区的增量和减量相同,将会得到我们开始时的初始值,对吗?让我们看看输出:

enter image description here

正如你所看到的,无论我们运行这个程序多少次,我们都会得到不同的结果,因为每个线程同时操作了counter。如果我们能设法让一个线程先增加共享变量,然后再减少它,反之亦然,我们就会得到正确的结果,这正是synchronized关键字所能做到的,只需在Bufferincdec方法之前添加synchronized关键字,如下所示:

关键字为synchronized的程序:

// rest of the code
class Buffer {private int counter = 0;// added synchronized keyword to let only one thread// be it inc or dec thread to manipulate data at a timepublic synchronized void inc() { counter++; }public synchronized void dec() { counter--; }public int getCounter() { return counter; }}
// rest of the code

输出:

enter image description here

不管我们运行多少次,我们得到的输出都是0