'同步'是什么意思?

时间:2009-07-06 06:47:24

标签: java multithreading keyword synchronized

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

  • synchronized关键字的重要性是什么?
  • 方法应该何时synchronized
  • 这在编程和逻辑上意味着什么?

17 个答案:

答案 0 :(得分:827)

synchronized关键字是关于读取和写入相同变量,对象和资源的不同线程。这在Java中不是一个简单的主题,但这里引用了Sun:

  

synchronized方法可以实现简单   防止线程的策略   干扰和记忆一致性   错误:如果对象可见   不止一个线程,所有读取或   写入该对象的变量是   通过同步方法完成。

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

同样,这是Java中一个非常重要的主题。要了解更多信息,请在此处探索有关SO和Interwebs的主题:

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

答案 1 :(得分:275)

嗯,我认为我们有足够的理论解释,所以请考虑这段代码

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,所有线程都可以同时访问此方法。

当一个线程调用对象的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

答案 2 :(得分:109)

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

在使用non-synchronized这样的HashMap构造时,必须在代码中构建线程安全功能,以防止内存一致性错误。

答案 3 :(得分:78)

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

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

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

答案 4 :(得分:52)

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

这经常被称为使类线程安全,但我会说这是一个委婉说法。虽然同步保护Vector的内部状态不会被破坏,但这通常不会帮助Vector的用户。

考虑一下:

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

即使涉及的方法是同步的,因为它们被单独锁定和解锁,两个不幸的定时线程可以创建一个包含两个元素的向量。

因此,您必须同步应用程序代码。

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

最近,并发软件包已经发布,有许多聪明的实用程序可以解决多线程问题。

答案 5 :(得分:24)

概述

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

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

更深

语法 - synchronized关键字将Object作为参数(称为锁定对象),然后是{ block of code }。< / p>

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

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

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

同步方法:

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

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

技术

如果没有同步,则无法保证读取和写入的顺序发生,可能会使变量带有垃圾 (例如,一个变量最终可能会被一个线程写入的一半位和另一个线程写入的一半位,最终使变量处于两个线程都没有尝试写入的状态,而是一个组合的混乱两者。)

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

结论

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

  

来源

     

http://docs.oracle.com/javase/specs/jls/se8/html/index.html
  Java®语言规范,2015-02-13

答案 6 :(得分:20)

把它想象成你可能在足球场上发现的一种旋转门。人们想要进入平行的蒸汽,但在旋转门处他们是“同步的”。一次只能有一个人通过。所有想要通过的人都会这样做,但他们可能要等到他们能够完成。

答案 7 :(得分:15)

  

什么是synchronized关键字?

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

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

  

方法何时同步?

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

  

在语法上和逻辑上是什么意思?

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

这不可能用魔法来完成。它的程序员有责任在应用程序中识别关键部分并相应地保护它。 Java提供了一个框架来保护您的应用程序,但是保护所有部分的位置和内容是程序员的责任。

来自java文档page

的更多详细信息

内在锁定和同步:

  

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

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

一个线程被认为在获得锁定和释放锁定之间拥有内在锁定。 只要一个线程拥有一个内部锁,就没有其他线程可以获得相同的锁。另一个线程在尝试获取锁时会阻塞。

  

当线程释放内部锁时,在该操作与同一锁的任何后续获取之间建立先发生关系。

使方法同步有两个effects

  

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

当一个线程正在为一个对象执行一个synchronized方法时,所有其他线程都会调用同一个对象的同步方法阻塞(暂停执行),直到第一个线程完成该对象为止。

  

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

这可以保证对所有线程都可以看到对对象状态的更改。

在以下方面寻找其他同步替代方案:

Avoid synchronized(this) in Java?

答案 8 :(得分:9)

以下是The Java Tutorials的解释。

请考虑以下代码:

public class SynchronizedCounter {
    private int c = 0;

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

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

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

如果countSynchronizedCounter的实例,那么使这些方法同步会产生两种影响:

     
      
  • 首先,对同一对象的两个同步方法的调用不可能进行交错。当一个线程正在为一个对象执行一个synchronized方法时,所有其他线程都会调用同一个对象的同步方法(暂停执行),直到第一个线程完成该对象为止。
  •   
  • 其次,当同步方法退出时,它会自动与同一对象的同步方法的任何后续调用建立一个先发生关系。这可以保证对所有线程都可以看到对象状态的更改。
  •   

答案 9 :(得分:7)

据我所知,synchronized基本上意味着编译器在你的方法周围写一个monitor.enter和monitor.exit。因此它可能是线程安全的,具体取决于它的使用方式(我的意思是你可以使用同步方法编写一个不是线程安全的对象,具体取决于你的类所做的事情。)

答案 10 :(得分:7)

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 MethodsSynchronized Statements类似,因此我们只需了解Synchronized Statements

=&GT;基本上,我们会有

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

以下是2有助于理解synchronized

  • 每个对象/类都有intrinsic lock与之关联。
  • 当线程调用synchronized statement时,它会自动获取该intrinsic lock对象的synchronized statement's,并在方法返回时释放它。只要线程拥有intrinsic lock没有其他线程就可以获得 SAME lock =&gt;线程安全。

=&GT; 当thread A调用synchronized(this){// code 1} =&gt;时所有包含synchronized(this)和所有synchronized normal method(内部类)的块代码(内部类)都被锁定,因为 SAME 锁定。它将在thread A解锁后执行(“//代码1”完成)。

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

相同锁定 =&gt;锁(不依赖于哪种方法?或哪些语句?)

使用synchronized方法或同步语句?

我更喜欢synchronized statements,因为它更具扩展性。例如,将来,您只需要同步一部分方法。例如,你有2个同步方法,没有任何相互关联,但是当一个线程运行一个方法时,它会阻止另一个方法(它可以通过使用{{1}来阻止})。

但是,应用synchronized方法很简单,代码看起来很简单。对于某些类,只有1个同步方法,或者类中所有同步方法彼此相关=&gt;我们可以使用synchronized(a variable)使代码更简短易懂

注意

(与synchronized method无关,它与对象和类或非静态和静态不同。

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

参考

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

希望有所帮助

答案 11 :(得分:5)

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

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

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

  

内存一致性效果:与其他并发集合一样,   在将对象放入a之前的线程中的动作   访问后的ConcurrentLinkedDeque happen-before操作   或从另一个中的ConcurrentLinkedDeque中删除该元素   线程。

这种隐式行为是一个有点恶毒的方面,因为大多数没有太多经验的Java程序员会因为它而花费很多。然后,在Java没有按照“假设”在生产中执行的工作负载不同的情况下突然发现这个线程 - 并且测试并发问题非常困难。

答案 12 :(得分:4)

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

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&amp; runnable2仅与线程2共享。 现在当t1和t3开始而没有使用synchronized时,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在所有场景中完成。获取了两个锁,一个在runnable1上,由线程1和线程3共享,另一个在runnable2上,仅由线程2共享。

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

答案 13 :(得分:2)

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

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

来释放锁定
Object.wait()

答案 14 :(得分:0)

在Java中,为了防止多个线程操纵共享变量,我们使用synchronized关键字。让我们借助以下示例来了解它:

在该示例中,我定义了两个线程,并将它们分别命名为递增和递减。递增线程将共享变量(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();
            else
                buffer.dec();                           
        }
    }
}

如果我们运行上面的程序,我们期望缓冲区的值是相同的,因为以相同的数量增加和减少缓冲区会导致初始值,我们以?开头。让我们看一下输出:

enter image description here

您可以看到,无论我们运行该程序多少次,我们都会得到不同的结果,原因是每个线程同时操纵counter。如果我们能够让一个线程先递增共享变量,然后再递减该变量,反之亦然,那么我们将得到正确的结果,该结果正是synchronized关键字可以通过添加{{ 1}}关键字放在synchronized的{​​{1}}和inc方法之前,像这样:

使用dec关键字进行编程:

Buffer

和输出:

enter image description here

无论运行多少次,我们都会得到与0相同的输出

答案 15 :(得分:0)

Java中的

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

Java 5通过支持synchronized [About]扩展了happens-before

  

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

下一步是java.util.concurrent

volatile vs synchronized

答案 16 :(得分:-6)

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