何时使用volatile和synchronized

我知道有很多问题,但我仍然不太明白。 我知道这两个关键字的作用,但我无法确定在某些情况下使用哪个。 这里有几个例子,我试图确定哪个是最好用的。

例1:

import java.net.ServerSocket; public class Something extends Thread { private ServerSocket serverSocket; public void run() { while (true) { if (serverSocket.isClosed()) { ... } else { //Should this block use synchronized (serverSocket)? //Do stuff with serverSocket } } } public ServerSocket getServerSocket() { return serverSocket; } } public class SomethingElse { Something something = new Something(); public void doSomething() { something.getServerSocket().close(); } } 

例2:

 public class Server { private int port;//Should it be volatile or the threads accessing it use synchronized (server)? //getPort() and setPort(int) are accessed from multiple threads public int getPort() { return port; } public void setPort(int port) { this.port = port; } } 

任何帮助是极大的赞赏。

易变的关键字:

如果您正在使用multithreading编程,那么volatile关键字将更有用。 当多个线程使用相同的变量时,每个线程将拥有自己的该变量的本地缓存副本。

因此,当它更新值时,它实际上在本地缓存中更新而不是在主变量内存中。 使用相同变量的另一个线程对另一个线程更改的值一无所知。

要避免此问题,如果将变量声明为volatile,则它不会存储在本地缓存中。 每当线程更新值时,它都会更新到主存储器。 因此,其他线程可以访问更新的值。

挥发性示例:

 class ExampleThread extends Thread { private volatile int testValue; public ExampleThread(String str){ super(str); } public void run() { for (int i = 0; i < 3; i++) { try { System.out.println(getName() + " : "+i); if (getName().equals("Thread 1")) { testValue = 10; } if (getName().equals("Thread 2")) { System.out.println( "Test Value : " + testValue); } Thread.sleep(1000); } catch (InterruptedException exception) { exception.printStackTrace(); } } } } public class VolatileExample { public static void main(String args[]) { new ExampleThread("Thread 1").start(); new ExampleThread("Thread 2").start(); } } 

同步关键字:

synchronized关键字可以应用于语句块或方法。 synchronized关键字为关键部分提供保护,这些部分一次只能由一个线程执行。 synchronized关键字避免了关键代码一次被多个线程执行。

它限制其他线程同时访问资源。 如果synchronized关键字应用于静态方法,因为我们将通过下面的示例使用具有SyncStaticMethod方法的类来显示它,整个类在执行和控制一个线程的方法时被锁定。

当synchronized关键字应用于实例方法时,正如我们在下面给出的示例中使用SyncMethod一样,实例在被访问时被锁定,并且在执行和一次控制一个线程时被锁定。

当synchronized关键字应用于对象时,该对象被锁定,尽管与其关联的代码块一次由一个线程执行。

同步示例:

 public class Class1{ public synchronized static String SyncStaticMethod(){ } public synchronized String SyncMethod(){ } { public class Class2{ Object Obj; public String Method2(){  synchronized (Obj){  } } } 

一个简单的答案如下:

  • synchronized可以始终用于为您提供线程安全/正确的解决方案,

  • volatile可能会更快,但只能用于在有限的情况下为您提供线程安全/正确。

如有疑问,请使用synchronized 。 正确性比表现更重要。

表征可以安全使用volatile的情况涉及确定每个更新操作是否可以作为单个volatile变量的单个primefaces更新来执行。 如果操作涉及访问其他(非最终)状态或更新多个共享变量,则只能使用volatile来安全地完成操作。 你还需要记住:

  • 对非易失性longdouble可能不是primefaces的,并且
  • +++=这样的Java运算符不是primefaces的。

术语:如果操作完全发生,或者根本不发生,则操作是“primefaces的”。 术语“不可分割的”是同义词。

当我们谈论primefaces性时,我们通常从外部观察者的角度来看primefaces性; 例如,与执行操作的线程不同的线程。 例如,从另一个线程的角度来看, ++不是primefaces的,因为该线程可能能够观察到在操作过程中递增的字段的状态。 实际上,如果场是longdouble ,甚至可能观察到既不是初始状态也不是最终状态的状态!

synchronized关键字

synchronized表示变量将在多个线程之间共享。 它用于通过“锁定”对变量的访问来确保一致性,以便一个线程无法修改它而另一个线程使用它。

经典示例:更新指示当前时间的全局变量
incrementSeconds()函数必须能够不间断地完成,因为它在运行时会在全局变量time的值中创建临时不一致。 如果没有同步,另一个函数可能会看到“12:60:00”的time ,或者在标有>>>的注释中,当时间真的是“12:00:00”时,它会看到“11:00:00”因为时间还没有增加。

 void incrementSeconds() { if (++time.seconds > 59) { // time might be 1:00:60 time.seconds = 0; // time is invalid here: minutes are wrong if (++time.minutes > 59) { // time might be 1:60:00 time.minutes = 0; // >>> time is invalid here: hours are wrong if (++time.hours > 23) { // time might be 24:00:00 time.hours = 0; } } } 

volatile关键字

volatile只是告诉编译器不要对变量的常量做出假设,因为它可能会在编译器通常不期望它时发生变化。 例如,数字恒温器中的软件可能具有指示温度的变量,其值由硬件直接更新。 它可能会在正常变量不会发生变化的地方发生变化。

如果未将degreesCelsius声明为volatile ,则编译器可以自由优化:

 void controlHeater() { while ((degreesCelsius * 9.0/5.0 + 32) < COMFY_TEMP_IN_FAHRENHEIT) { setHeater(ON); sleep(10); } } 

进入这个:

 void controlHeater() { float tempInFahrenheit = degreesCelsius * 9.0/5.0 + 32; while (tempInFahrenheit < COMFY_TEMP_IN_FAHRENHEIT) { setHeater(ON); sleep(10); } } 

通过将degreesCelsius声明为volatile ,您告诉编译器每次运行循环时都必须检查它的值。

概要

简而言之, synchronized允许您控制对变量的访问,因此您可以保证更新是primefaces的(即,一组更改将作为一个单元应用;没有其他线程可以在半更新时访问该变量)。 您可以使用它,以确保数据的一致性。 另一方面, volatile是承认变量的内容超出了你的控制范围,所以代码必须假设它可以随时改变。

您的post中没有足够的信息来确定发生了什么,这就是为什么您获得的所有建议都是关于volatilesynchronized一般信息。

所以,这是我的一般建议:

在编写 – 编译 – 运行程序的循环中,有两个优化点:

  • 在编译时,编译器可能会尝试重新排序指令或优化数据缓存。
  • 在运行时,当CPU有自己的优化时,比如缓存和乱序执行。

所有这些意味着指令很可能不会按照您编写它们的顺序执行,无论是否必须维护此顺序以确保multithreading环境中的程序正确性。 您经常会在文献中找到一个经典的例子:

 class ThreadTask implements Runnable { private boolean stop = false; private boolean work; public void run() { while(!stop) { work = !work; // simulate some work } } public void stopWork() { stop = true; // signal thread to stop } public static void main(String[] args) { ThreadTask task = new ThreadTask(); Thread t = new Thread(task); t.start(); Thread.sleep(1000); task.stopWork(); t.join(); } } 

根据编译器优化和CPU架构,上述代码可能永远不会在多处理器系统上终止。 这是因为stop的值将被缓存在CPU运行线程t的寄存器中,这样线程将永远不会再次从主内存读取值,即使主线程已经同时更新了它。

为了对抗这种情况,引入了记忆围栏 。 这些是特殊说明,在围栅之后不允许定期指示,并在围栏之后重新排序。 一个这样的机制是volatile关键字。 标记为volatile变量未由编译器/ CPU优化,并且将始终直接写入/读取主存储器。 简而言之, volatile确保了跨CPU核心的变量值的可见性

可见性很重要,但不应与primefaces性相混淆。 即使变量声明为volatile两个递增相同共享变量的线程也可能产生不一致的结果。 这是因为在某些系统上,增量实际上被转换为可以在任何点上中断的汇编指令序列。 对于这种情况,需要使用关键部分,例如synchronized关键字。 这意味着只有一个线程可以访问synchronized块中包含的代码。 关键部分的其他常见用途是对共享集合的primefaces更新,当通常迭代集合而另一个线程正在添加/删除项目时将导致抛出exception。

最后两点有趣:

  • synchronized和一些其他构造如Thread.join将隐式引入内存栅栏。 因此,在synchronized块内增加变量不需要变量也是volatile ,假设它是唯一被读/写的地方。
  • 对于诸如值交换,递增,递减之类的简单更新,您可以使用非阻塞primefaces方法,如AtomicIntegerAtomicLong等中的那些。这些方法比synchronized快得多,因为它们不会在锁定时触发上下文切换已经被另一个线程占用了。 它们在使用时也会引入内存栅栏。

注意:在第一个示例中,字段serverSocket实际上从未在您显示的代码中初始化。

关于同步,它取决于ServerSocket类是否是线程安全的。 (我假设它是,但我从未使用它。)如果是,你不需要同步它。

在第二个例子中, int变量可以primefaces地更新,因此volatile可能就足够了。

volatile解决了CPU内核的“可见性”问题。 因此,刷新本地寄存器的值并与RAM同步。 但是,如果我们需要一致的值和primefaces操作,我们需要一种机制来保护关键数据。 这可以通过synchronized块或显式锁来实现。