“同步”是什么意思?
-
23-08-2019 - |
题
我对它的用法和意义有一些疑问 synchronized
关键词。
- 有何意义
synchronized
关键词? - 什么时候应该使用方法
synchronized
? - 从程序上和逻辑上来说这意味着什么?
解决方案
这 synchronized
关键字是关于不同的线程读取和写入相同的变量、对象和资源。这在 Java 中不是一个简单的话题,但这里引用了 Sun 的一句话:
synchronized
方法使一个简单的 防止线程的策略 干扰和内存一致性 错误:如果对象对 多个线程,所有读取或 写入该对象的变量是 通过同步方法完成。
简而言之: 当您有两个线程正在读取和写入同一“资源”时,假设有一个名为 foo
, ,您需要确保这些线程以原子方式访问变量。如果没有 synchronized
关键字,您的线程 1 可能看不到线程 2 所做的更改 foo
, ,或者更糟糕的是,它可能只改变了一半。这不是你逻辑上所期望的。
再说一次,这在 Java 中是一个不平凡的话题。要了解更多信息,请探索 SO 和 Interwebs 上的主题:
继续探索这些主题,直到名称 “布莱恩·戈茨” 与该术语永久关联 “并发” 在你的大脑中。
其他提示
好吧,我认为我们已经有了足够的理论解释,所以考虑这段代码
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
这 synchronized
关键字可防止多个线程同时访问代码块或对象。默认情况下,一个 Hashtable
是 synchronized
, ,因此一次只有一个线程可以访问该表。
关于使用 non-synchronized
构造像 HashMap
,您必须在代码中构建线程安全功能以防止内存一致性错误。
synchronized
意味着在多线程环境中,一个对象具有 synchronized
方法/块不允许两个线程访问 synchronized
同时调用方法/代码块。这意味着一个线程在另一个线程更新它时无法读取它。
第二个线程将等待,直到第一个线程完成其执行。开销是速度,但优点是保证数据的一致性。
如果您的应用程序是单线程的, synchronized
块不提供任何好处。
这 synchronized
关键字使线程在进入方法时获得锁,这样同一时间只有一个线程可以执行该方法(对于给定的对象实例,除非是静态方法)。
这通常被称为使类线程安全,但我想说这是一种委婉的说法。虽然同步确实可以保护 Vector 的内部状态不被损坏,但这通常对 Vector 的用户没有多大帮助。
考虑一下:
if (vector.isEmpty()){
vector.add(data);
}
尽管所涉及的方法是同步的,但由于它们是单独锁定和解锁的,不幸的是,两个定时线程可以创建一个具有两个元素的向量。
因此,实际上,您还必须在应用程序代码中进行同步。
因为方法级同步 a) 当您不需要同步时成本高昂,b) 当您需要同步时不足,因此现在有非同步的替代方案(在 Vector 的情况下为 ArrayList)。
最近,并发包已经发布,其中有许多巧妙的实用程序可以解决多线程问题。
概述
Java中的synchronized关键字与线程安全有关,即多个线程读取或写入同一个变量时。
这可以直接发生(通过访问同一变量)或间接发生(通过使用一个类,该类使用另一个访问同一变量的类)。
Synchronized 关键字用于定义一个代码块,多个线程可以在其中以安全的方式访问同一变量。
更深
从语法上看 synchronized
关键字需要一个 Object
因为它的参数(称为 一个锁对象),然后是 { block of code }
.
当执行遇到此关键字时,当前线程尝试“锁定/获取/拥有”(选择) 锁定对象 并在获取锁后执行关联的代码块。
保证对同步代码块内的变量的任何写入对于使用相同的同步代码块内类似地执行代码的每个其他线程都是可见的。 锁定对象.
一次只有一个线程可以持有锁,在此期间所有其他线程都试图获取相同的锁 锁定对象 将等待(暂停其执行)。当执行退出同步代码块时,锁将被释放。
同步方法:
添加 synchronized
方法定义的关键字等于将整个方法体包装在同步代码块中 锁定对象 存在 this
(例如方法) 和 ClassInQuestion.getClass()
(对于类方法).
- 实例方法是没有的方法 static
关键词。
- 类方法是具有 static
关键词。
技术的
如果没有同步,就无法保证读取和写入发生的顺序,可能会导致变量留下垃圾。
(例如,一个变量最终可能有一半的位由一个线程写入,一半的位由另一个线程写入,从而使该变量处于两个线程均未尝试写入的状态,而是两者的混合体。)
在另一个线程读取之前(挂钟时间)在一个线程中完成写入操作是不够的,因为硬件可能已经缓存了变量的值,并且读取线程将看到缓存的值而不是写入的值它。
结论
因此,在 Java 的情况下,您必须遵循 Java 内存模型以确保不会发生线程错误。
换句话说:使用同步、原子操作或在幕后为您使用它们的类。
来源
http://docs.oracle.com/javase/specs/jls/se8/html/index.html
Java® 语言规范,2015-02-13
可以把它想象成一种十字转门,就像你在足球场上看到的那样。有平行的人流想要进去,但在十字转门处他们是“同步”的。一次只有一个人可以通过。所有想要通过的人都可以,但他们可能必须等到他们能够通过为止。
同步关键字是什么?
线程主要通过共享对字段和引用字段所引用的对象的访问来进行通信。这种形式的通信非常有效,但可能会出现两种错误: 线程干扰和内存一致性错误. 。防止这些错误所需的工具是同步。
同步块或方法可以防止线程干扰并确保数据一致。在任何时刻,只有一个线程可以访问同步块或方法(临界区)通过获取锁。其他线程将等待锁释放才能访问 临界区.
方法什么时候同步?
添加时方法会同步 synchronized
方法定义或声明。您还可以在方法中同步特定的代码块。
它在程序上和逻辑上意味着什么?
意思是只有一个线程可以访问 临界区 通过获取锁。除非该线程释放该锁,否则所有其他线程都必须等待获取锁。他们无权进入 临界区 无需获取锁。
这不是用魔法就能做到的。程序员有责任识别 关键部分 在应用中并相应地保护它。Java 提供了一个框架来保护您的应用程序,但是要保护的所有部分的位置和内容是程序员的责任。
来自 java 文档的更多详细信息 页
本质锁和同步:
同步是围绕称为内在锁或监视器锁的内部实体构建的。本质锁在同步的两个方面都发挥着作用:强制对对象状态进行独占访问并建立对可见性至关重要的发生之前关系。
每个对象都有一个与其关联的内在锁. 。按照惯例,需要对对象字段进行独占和一致访问的线程必须在访问对象字段之前获取该对象的内在锁,然后在访问完这些字段后释放该内在锁。
线程在获取锁和释放锁之间被称为拥有固有锁。 只要一个线程拥有固有锁,其他线程就无法获取相同的锁。 当另一个线程尝试获取锁时,它将被阻塞。
当线程释放固有锁时,该操作与同一锁的任何后续获取之间会建立先发生关系。
使方法同步有两个 效果:
首先,同一对象上的同步方法的两次调用不可能交错。
当一个线程正在执行对象的同步方法时,调用同一对象的同步方法的所有其他线程都会阻塞(挂起执行),直到第一个线程完成该对象。
其次,当同步方法退出时,它会自动与同一对象的同步方法的任何后续调用建立发生之前关系。
这保证了对象状态的更改对所有线程都是可见的。
在以下位置寻找同步的其他替代方案:
这是来自的解释 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
, ,那么使这些方法同步有两个效果:
- 首先,同一对象上的同步方法的两次调用不可能交错。当一个线程正在执行对象的同步方法时,调用同一对象的同步方法的所有其他线程都会阻塞(挂起执行),直到第一个线程完成该对象。
- 其次,当同步方法退出时,它会自动与同一对象的同步方法的任何后续调用建立发生之前关系。这保证了对象状态的更改对所有线程都是可见的。
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 Methods
和 Synchronized 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 lock
为了那个原因synchronized statement's
对象并在方法返回时释放它。只要一个线程拥有一个intrinsic lock
, 没有其他 线程可以获取 相同的 锁=>线程安全。
=>
当 thread A
调用 synchronized(this){// code 1}
=> 所有块代码(类内)都有 synchronized(this)
和所有 synchronized normal method
(课堂内)被锁定,因为 相同的 锁。它将在之后执行 thread A
解锁(“//代码1”完成)。
这种行为类似于 synchronized(a variable){// code 1}
或者 synchronized(class)
.
同锁 => 锁定(不依赖于哪个方法?或哪些陈述?)
使用同步方法还是同步语句?
我更喜欢 synchronized statements
因为它更具可扩展性。例如,将来您只需要同步方法的一部分。例如,你有 2 个同步方法,它 没有任何 彼此相关,但是当一个线程运行一个方法时,它会阻塞另一个方法(它可以通过使用来阻止 synchronized(a variable)
).
不过,apply Synchronized方法很简单,代码看起来也很简单。对于某些类,只有 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
希望有帮助
据我了解,同步基本上意味着编译器在您的方法周围编写一个monitor.enter和monitor.exit。因此,它可能是线程安全的,具体取决于它的使用方式(我的意思是,您可以使用同步方法编写一个对象,但该对象不是线程安全的,具体取决于您的类的用途)。
其他答案缺少的是一个重要方面: 记忆障碍. 。线程同步主要包括 二 部分:序列化和可见性。我建议大家去谷歌搜索“jvm内存屏障”,因为这是一个不平凡且极其重要的主题(如果您修改多个线程访问的共享数据)。完成此操作后,我建议查看 java.util.concurrent 包的类,它们有助于避免使用显式同步,这反过来又有助于保持程序简单高效,甚至可能防止死锁。
一个这样的例子是 并发链接双端队列. 。与 命令模式 它允许通过将命令填充到并发队列中来创建高效的工作线程——不需要显式同步,不会出现死锁,不需要显式 sleep(),只需通过调用 take() 轮询队列即可。
简而言之:“内存同步”发生 隐含地 当您启动线程时,线程结束,您读取易失性变量,解锁监视器(留下同步块/函数)等。这种“同步”影响(某种意义上“冲洗”) 全部 写入在该特定操作之前完成。在上述情况下 并发链接双端队列, ,文档“说”:
内存一致性影响:与其他并发集合一样, 在将对象放入 并发LinkedDeque 发生在之前 访问之后的操作 或从另一个 ConcurrentLinkedDeque 中删除该元素 线。
这种隐式行为是一个有点有害的方面,因为大多数没有太多经验的 Java 程序员只会因此而采取很多既定的做法。然后,在 Java 没有执行它在生产环境中“应该”执行的操作(其中存在不同的工作负载)之后,突然被这个线程绊倒——而且测试并发问题非常困难。
同步仅仅意味着如果在特定对象上使用同步块,则与单个对象关联的多个线程可以防止脏读和脏写。为了让您更清楚,让我们举个例子:
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
同步简单意味着没有两个线程可以同时访问块/方法。当我们说类的任何块/方法是同步的时,这意味着一次只有一个线程可以访问它们。在内部,尝试访问它的线程首先会对该对象加锁,只要该锁不可用,其他线程就无法访问该类实例的任何同步方法/块。
请注意,另一个线程可以访问未定义为同步的同一对象的方法。线程可以通过调用来释放锁
Object.wait()
Synchronized是Java中的一个关键字,用于在多线程环境中使关系发生在之前,以避免内存不一致和线程干扰错误。