“同步”是什么意思?


993

我对synchronized关键字的用法和重要性有一些疑问。

  • synchronized关键字的意义是什么?
  • 什么时候应该使用方法synchronized
  • 从程序上和逻辑上是什么意思?


1
哈希图与哈希表之间以及同步之间的有益讨论:stackoverflow.com/questions/40471/java-hashmap-vs-hashtable
limc 2011年


1
我从第一条评论开始浏览了整个文档链接,直到我进入最后一段才明白。代替粘贴链接和不引用任何内容,可以粘贴链接和添加引号更为有用。
拉基布

Answers:


878

synchronized关键字是所有不同的线程读取和写入相同的变量,对象和资源。这不是Java中的琐碎话题,但以下是Sun的引文:

synchronized 方法提供了一种防止线程干扰和内存一致性错误的简单策略:如果一个对象对多个线程可见,则对该对象变量的所有读取或写入均通过同步方法完成。

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

同样,这是Java中不平凡的主题。要了解更多信息,请在SO和Interwebs上探索有关以下主题:

继续探索这些主题,直到名称“ Brian Goetz”与大脑中的“并发”一词永久关联。


71
因此,基本上,这个Synchronized关键字使您的方法具有线程安全性吗?
Rigo Vides

82
synced关键字是使代码线程安全的工具之一。仅对方法或变量本身使用同步可能不会成功。对Java内存模型有基本的了解对于正确并发非常重要。
斯图·汤普森

28
除非您是Brian Goetz(或者也许是Jon Skeet),否则仅使用语言原语(同步,易失)几乎不可能获得Java并发性。对于初学者,请使用java.util.concurrent包并在此基础上构建。
Thilo

12
更清楚地讲:不能从多个线程中同时调用同步方法。
彼得-恢复莫妮卡

2
@peterhsynced还不止于此,因此更冗长的解释
Stu Thompson

293

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

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();
    }
    @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”类的实例)时,它获取该对象的锁,任何新线程都不能调用同一对象的任何同步方法,只要前一个线程已获得锁的不会释放锁。

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

同步输出

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

输出不同步

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

7
很好的例子,知道理论是件好事,但是代码总是更加具体和完整。
Santi Iglesias

2
@SantiIglesias“完成”吗?不。此示例演示的锁定行为synchronized,但是内存一致性被忽略。
斯图汤普森

2
@Stu Thompson内存一致性是锁定的结果
Dheeraj Sachan

@DheerajSachan通过该逻辑,然后使用ReentrantLock将导致其内存一致性。没有。
Stu Thompson

3
@boltup_im_coding:start()方法将线程置于“ RUNNABLE”状态,这意味着它已准备好执行或已经执行。处于Runnable状态的另一个线程(通常但不一定具有更高的优先级)可能会跳出队列并开始执行。在上面的例子中,线3发生THREAD 2之前获得CPU
萨赫勒Ĵ

116

synchronized关键字防止由多个线程的代码或目标块的并发访问。的所有方法Hashtable都是synchronized,因此一次只能有一个线程执行任何一个。

当使用synchronized像这样的非结构时HashMap,必须在代码中构建线程安全功能,以防止一致性错误。


81

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

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

但是,如果您的应用程序是单线程的,则synchronized块不会带来任何好处。


54

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

这通常被称为使类具有线程安全性,但是我会说这是委婉的说法。虽然同步确实可以保护Vector的内部状态免于损坏,但这通常对Vector的用户没有多大帮助。

考虑一下:

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

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

因此,实际上,您还必须同步您的应用程序代码。

因为方法级同步a)不需要时很昂贵,并且b)需要同步时不够,所以现在有不同步的替换(对于Vector,是ArrayList)。

最近,并发程序包已发布,其中包含许多巧妙的实用程序,可解决多线程问题。


26

总览

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

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

更深层次的

在语法上,synchronized关键字以Object参数作为参数(称为锁定对象),然后跟随一个参数{ block of code }

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

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

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

同步方法:

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

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

技术

如果没有同步,则无法保证读取和写入的顺序,可能会使变量带有垃圾。
(例如,一个变量可能以一个线程写入的一半位和另一线程写入的一半位结尾,从而使变量处于两个线程都未尝试写入的状态,而两个线程的合并则混乱了。)

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

结论

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

资料来源

http://docs.oracle.com/javase/specs/jls/se8/html/index.htmlJava®
语言规范,2015年2月13日


抱歉,但是我有这个示例,我不明白它的含义:`Integer i1 = Arrays.asList(1,2,3,4,5).stream()。findAny()。get(); 同步(i1){整数i2 = Arrays.asList(6,7,8,9,10).parallelStream().sorted().findAny()。get(); System.out.println(i1 +“” + i2); }`1.为什么要在第一个实例上调用该块,而此调用对代码没有影响?2.第二个实例将是线程安全的,尽管在第一个实例上调用了该块?
Adryr83 '19

1
@ Adryr83如果您有问题,可以通过发布新问题来提出。但是由于我们在这里,所以我将解析我能做的(您的问题有点难以理解)。从我能告诉的那段代码来看,它似乎不包含任何需要同步的内容。这是没有上下文的。建议:如果可以,请尝试将代码分成较小的部分,然后搜索有关这些问题的答案。尝试理解小的孤立问题要比找出一大段代码容易得多。
Gima

21

可以将其视为一种旋转门,就像您在足球场上可能会发现的那样。有很多人想进入,但是在旋转门上他们是“同步的”。一次只能一个人通过。所有想要通过的人都会做,但是他们可能必须等到可以通过。


16

什么是synced关键字?

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

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

方法何时同步?

当您添加synchronized到方法定义或声明时,方法将同步。您也可以使用方法来同步特定的代码块。

在程序上和逻辑上是什么意思?

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

魔术无法做到这一点。程序员有责任识别应用程序中的关键部分并进行相应的保护。Java提供了一个框架来保护您的应用程序,但是所有需要保护的部分和位置由程序员负责。

来自Java文档页面的更多详细信息

内在锁和同步:

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

每个对象都有一个与之关联的固有锁。按照约定,需要对对象的字段进行独占且一致的访问的线程必须在访问对象之前先获取对象的固有锁,然后在完成对它们的锁定后释放固有锁。

据说线程在获得锁和释放锁之间拥有内部锁。只要一个线程拥有一个内在锁,其他任何线程都无法获得相同的锁。另一个线程在尝试获取锁时将阻塞。

当线程释放内在锁时,该动作与该锁的任何后续获取之间将建立事前发生关系。

使方法同步具有两个作用

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

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

其次,当同步方法退出时,它会与随后对同一对象的同步方法的任何调用自动建立先发生关系。

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

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

避免在Java中同步(this)?


11

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 Methods类似Synchronized Statements所以我们只需要了解Synchronized Statements

=>基本上,我们将有

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

这是2认为有助于理解 synchronized

  • 每个对象/类都有一个intrinsic lock关联。
  • 当一个线程调用synchronized statement时,它自动获取intrinsic locksynchronized statement's对象并将其释放的方法返回时。只要一个线程拥有一个intrinsic lock其他任何线程都无法获得SAME lock =>线程安全的。

=>当thread A调用synchronized(this){// code 1}=>时,具有SAME锁定的所有块代码(内部类)synchronized(this)和所有synchronized normal method(内部类)被锁定。它将执行后解锁(“//代码1”成品)。 thread A

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

SAME LOCK => lock(不取决于哪个方法?或哪个语句?)

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

我更喜欢,synchronized statements因为它更可扩展。例如,将来,您仅需要同步方法的一部分。例如,您有2个同步方法,并且它们之间没有任何关联,但是,当线程运行一个方法时,它将阻塞另一个方法(可以通过use阻止synchronized(a variable))。

但是,应用同步方法很简单,代码看起来很简单。对于某些类,只有1个同步方法,或该类中所有彼此相关的同步方法=>我们可以使用它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 https://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html

希望对你有帮助


11

这是Java教程中的说明。

考虑以下代码:

public class SynchronizedCounter {
    private int c = 0;

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

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

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

如果count是的实例SynchronizedCounter,则使这些方法同步具有两个作用:

  • 首先,不可能对同一对象的两次同步方法调用进行交织。当一个线程正在执行对象的同步方法时,所有其他线程调用同一对象块的同步方法(挂起执行),直到第一个线程对该对象完成。
  • 其次,当同步方法退出时,它会与随后对同一对象的同步方法的任何调用自动建立先发生关系。这保证了对象状态的更改对所有线程都是可见的。

9

据我了解,同步基本上意味着编译器在您的方法周围编写了monitor.enter和monitor.exit。因此,根据使用方式的不同,它可能是线程安全的(我的意思是,您可以使用同步方法编写对象,而同步方法根据类的用途不是线程安全的)。


5

其他答案遗漏的一个重要方面是:内存障碍。线程同步基本上包括部分:序列化和可见性。我建议每个人都在Google上寻找“ jvm内存屏障”,因为这是一个不重要且极为重要的主题(如果您修改了多个线程访问的共享数据)。完成此操作后,我建议查看java.util.concurrent包的类,这些类有助于避免使用显式同步,从而有助于保持程序简单高效,甚至可以防止死锁。

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

简而言之:当启动线程,线程结束,读取易失性变量,解锁监视器(保留同步的块/功能)等时,“内存同步”会隐式发生。这种“同步”会(在某种意义上说“刷新” “)在该特定操作之前完成的所有写入。对于上述ConcurrentLinkedDeque,文档“说”:

内存一致性影响:与其他并发集合一样,在将对象放入ConcurrentLinkedDeque中之前在线程中执行的操作在发生之前操作发生在访问或从另一个线程中的ConcurrentLinkedDeque中删除该元素操作。

这种隐式行为在某种程度上是一个有害的方面,因为大多数Java程序员没有太多的经验,因此会花很多时间。然后,在Java没有执行工作负载不同的生产中应该做的事情之后,突然在该线程上绊倒了,这很难测试并发问题。


3

同步只是意味着,如果在特定对象上使用了同步块,则多个线程(如果与单个对象相关联)可以防止脏读和写。为了让您更清楚,让我们举个例子:

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();
    }
}

我们创建了两个MyRunnable类对象,runnable1与线程1共享,线程3和runnable2仅与线程2共享。现在,当t1和t3在不使用同步的情况下启动时,PFB输出表明线程1和3同时影响var值,而对于线程2来说,var具有自己的内存。

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

在所有情况下,使用Synchronzied,线程3等待线程1完成。获取了两个锁,一个在线程1和线程3共享的runnable1上,另一个在仅线程2共享的runnable2上。

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

同步的意义远不止于此:它对内存壁垒具有深远的影响。
user1050755

1

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

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

Object.wait()

0

synchronizedJava中的block是多线程中的监视器。synchronized具有相同对象/类的块只能由单个线程执行,所有其他线程都在等待。race condition当多个线程尝试更新同一变量时,它可以提供帮助(第一步是使用volatileAbout

Java 5synchronized通过支持[关于]扩展happens-before

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

下一步是 java.util.concurrent

易失与同步


-6

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.