我有一些关于同步关键字的用法和意义的问题。
synchronized关键字有什么意义? 什么时候应该同步方法? 它在程序上和逻辑上意味着什么?
我有一些关于同步关键字的用法和意义的问题。
synchronized关键字有什么意义? 什么时候应该同步方法? 它在程序上和逻辑上意味着什么?
当前回答
在java中,为了防止多个线程操纵一个共享变量,我们使用synchronized关键字。让我们通过下面的例子来理解它:
在这个例子中,我定义了两个线程,并将它们命名为increment和decincrement。增量线程增加共享变量(计数器)的值,增量线程减少共享变量(计数器)的值,即增加5000次(结果是5000 + 0 = 5000),减少5000次(结果是5000 - 5000 = 0)。
没有同步关键字的程序:
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();
}
}
}
如果我们运行上面的程序,我们期望缓冲区的值是相同的,因为缓冲区的增量和减量相同,将会得到我们开始时的初始值,对吗?让我们看看输出:
正如你所看到的,无论我们运行程序多少次,我们都会得到不同的结果,因为每个线程都在同一时间操纵计数器。如果我们能设法让一个线程先增加共享变量,然后再减少它,反之亦然,我们就会得到正确的结果,这正是synchronized关键字所能做到的,只需在Buffer的inc和dec方法之前添加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 time
public synchronized void inc() { counter++; }
public synchronized void dec() { counter--; }
public int getCounter() { return counter; }
}
// rest of the code
输出:
不管我们运行多少次,我们得到的输出都是0
其他回答
Synchronized的意思是,如果在特定对象上使用Synchronized块,那么与单个对象关联的多个线程可以防止脏读写。为了让你更清楚,让我们举个例子:
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启动时没有使用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
Java同步
volatile[About] => synchronized
synchronized块在Java中是多线程中的监视器。同一对象/类的同步块只能由单个线程执行,其他的都在等待。它可以帮助解决竞赛状况。
Java 5通过支持happens-before扩展了同步[关于]
监视器的解锁(同步块或方法退出)发生在同一监视器的每个后续锁定(同步块或方法进入)之前。
下一步是java.util.concurrent
以我的理解,同步基本上意味着编译器编写一个监视器。进入并监控。在方法周围退出。因此,它可能是线程安全的,这取决于它是如何使用的(我的意思是,你可以编写一个具有同步方法的对象,它不是线程安全的,这取决于你的类做什么)。
What the other answers are missing is one important aspect: memory barriers. Thread synchronization basically consists of two parts: serialization and visibility. I advise everyone to google for "jvm memory barrier", as it is a non-trivial and extremely important topic (if you modify shared data accessed by multiple threads). Having done that, I advise looking at java.util.concurrent package's classes that help to avoid using explicit synchronization, which in turn helps keeping programs simple and efficient, maybe even preventing deadlocks.
ConcurrentLinkedDeque就是这样一个例子。与命令模式一起,它允许通过将命令填充到并发队列中来创建高效的工作线程——不需要显式同步,不可能发生死锁,不需要显式sleep(),只需通过调用take()轮询队列。
简而言之:“内存同步”隐式地发生在线程启动、线程结束、读取volatile变量、解锁监视器(留下同步块/函数)等时候。这种“同步”影响(在某种意义上“刷新”)在该特定操作之前完成的所有写操作。在前面提到的ConcurrentLinkedDeque的情况下,文档“说”:
内存一致性影响:与其他并发集合一样, 对象放入线程之前的操作 ConcurrentLinkedDeque访问之后的happens -before操作 或者从另一个ConcurrentLinkedDeque中删除该元素 线程。
这种隐式行为在某种程度上是有害的,因为大多数没有太多经验的Java程序员会因此而得过其实。然后突然被这个线程绊倒,因为Java在生产中没有做它“应该”做的事情,因为有不同的工作负载——并且很难测试并发性问题。
在java中,为了防止多个线程操纵一个共享变量,我们使用synchronized关键字。让我们通过下面的例子来理解它:
在这个例子中,我定义了两个线程,并将它们命名为increment和decincrement。增量线程增加共享变量(计数器)的值,增量线程减少共享变量(计数器)的值,即增加5000次(结果是5000 + 0 = 5000),减少5000次(结果是5000 - 5000 = 0)。
没有同步关键字的程序:
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();
}
}
}
如果我们运行上面的程序,我们期望缓冲区的值是相同的,因为缓冲区的增量和减量相同,将会得到我们开始时的初始值,对吗?让我们看看输出:
正如你所看到的,无论我们运行程序多少次,我们都会得到不同的结果,因为每个线程都在同一时间操纵计数器。如果我们能设法让一个线程先增加共享变量,然后再减少它,反之亦然,我们就会得到正确的结果,这正是synchronized关键字所能做到的,只需在Buffer的inc和dec方法之前添加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 time
public synchronized void inc() { counter++; }
public synchronized void dec() { counter--; }
public int getCounter() { return counter; }
}
// rest of the code
输出:
不管我们运行多少次,我们得到的输出都是0