2.4变动和最终变量(Volatile and Final Variables)
2017-07-16 18:32
260 查看
在前面的学习同步锁显示了两个属性:互斥和可见性。同步关键字连接着这两个属性。Java提供一个弱引用的仅仅用于同步可见性。它也连接着一个volatile的关键字。
假设你设计一个机制去停止一个线程(因为你不能用Thread的stop()方法,在这个任务中它是不安全的)。Listing2-2显现出了代码资源并于ThreadStoping的应用,和展示了你可能完成的任务。
Listing2-2
package com.owen.thread.chapter2;
public class SynThreadStopping
{
public static void main(String[] args)
{
class StoppableThread extends Thread
{
private boolean stopped; // defaults to false
@Override
public void run()
{
while (!stopped)
System.out.println("running");
}
void stopThread()
{
stopped = true;
}
}
StoppableThread thd = new StoppableThread();
thd.start();
try
{
Thread.sleep(1000); // sleep for 1 second
} catch (InterruptedException ie)
{
}
thd.stopThread();
}
}
Listing2-2main()方法声明了一个局部的类,全名为StoppableThread,它是Thread的子类。之后实例化StoppableThread,这个默认主线程开启一个线程连接着Thread对象。在线程死亡之前,它会先休眠一秒钟,然后调用StoppableThread的stop()方法。
StoppableThread在实例域声明一个stopped的变量,并且初始时的值是false,一个stopThread()方法设置stopped的值是true,一个run()方法在每一次循环时都会去检查stopped的值是否改变为true。
运行上在的程序,你需要观察运行信息的顺序。
当你的程序运行在单处理器或单核的机器上,你将可能观察到应用停止。
在一个多处理器机器或多核的唯一处理器上,你可能不会看到这个停止,因为每一个处理器或核可能有它自己的副本stopped在缓存中。当一个线程修改它自己域的副本,其它线程拷贝的stopped将为不会改变。
你可能决定使用同步关键字去确保这个拷贝的stopped,在仅仅一个主要的内存调用中。之后你给在一对临界区的资源代码中结束这个同步,这个可能出现在下面的例子Listing2-3
Listing2-3通过同步关键字去停止一个线程
Listing2-3是一个不好的思想,表现在两点:第一,尽管你仅仅需要去解决可见性问题,同步也解决了互斥问题(在这个例子中没有看到)。更重要的,在这个例子中你已经面临着一系列问题。
你要正确通过stopped来同步,但是你看同步阻塞在run()方法。注意while的循环。这个循环是不会结束的,因为线程执行循环体时,需要请求锁给当前的StoppableThread对象(通过synchronized(this)),和任何企图通过主线程去请求stopThread()中的对象,都会导致主线程的阻塞,因为主线程也需要同样的锁。
你可以通过局部变量来解决这个问题,在同步阻塞中注册stopped的值给这个变量,如下代码:
然而,这个问题是混乱的和浪费资源的,因为当企图去请求锁时,应用需要花销,和每一次循环时都要去完成这个任务。Listing2-4的例子会更高效和更清晰。
因为stopped被标记为volatile,每个线程都会通过主内存拷贝这个变量的副本,而不是通过缓存副本。这个应用将会停止,尽管在多处理器机器或多核机器。
Caution 使用volatile仅仅在这里可见性上发挥作用。你也可以声明这个字节在上下文领域(如果你试图让一个局部变量中加入volatile,那么你将会收到错误信息。)最后,你可以声明double和long域的volatitle,避免在32位的JVM,因为它需要两个操作访问double或long的变量值,和互斥(通过同步)需要访问他们的安全价值。
当全局的变量声明为volatile,它就不能再声明为final。然而,这并不是个问题,因为Java也可以让你安全通过final的局部却不需要同步。为了去解决缓存的问题,在DeadlockDemo的例子,我标记了两个lock1和lock2为final,当然我也可以标记为volatile。
你将会经常使用final去帮助你在一个不变的上下文类中确保线程安全。思考Listing2-5.
Listing2-5创建一个不变的和线程安全的类。
Listing2-5显现一个不变的Planets的类,它的对象存储一个Set的集合中。尽管Set集合是可以改变的,这个类的设计防止在构造函数退出后修改集合。通过声明planets为final,这个引用存储在这个局部并且不能改变。然而,这个引用不能被缓存,所以这个缓存变量问题不存在。
Java提供一个特殊的安全线程确保不变量对象。这个对象可以在多线程中安全被调用,即使不使用同步来显示它们的引用,只要遵守以下规则:
不变对象不允许状态改变。
所有的全局都要声明为final.
对象必须正常被构造,这样“this”引用也不会从构造器逃逸。
最后一点问题可能会有点混淆,下面的例子说明了“this”引用从构造器中逃逸现象。
假设你设计一个机制去停止一个线程(因为你不能用Thread的stop()方法,在这个任务中它是不安全的)。Listing2-2显现出了代码资源并于ThreadStoping的应用,和展示了你可能完成的任务。
Listing2-2
package com.owen.thread.chapter2;
public class SynThreadStopping
{
public static void main(String[] args)
{
class StoppableThread extends Thread
{
private boolean stopped; // defaults to false
@Override
public void run()
{
while (!stopped)
System.out.println("running");
}
void stopThread()
{
stopped = true;
}
}
StoppableThread thd = new StoppableThread();
thd.start();
try
{
Thread.sleep(1000); // sleep for 1 second
} catch (InterruptedException ie)
{
}
thd.stopThread();
}
}
Listing2-2main()方法声明了一个局部的类,全名为StoppableThread,它是Thread的子类。之后实例化StoppableThread,这个默认主线程开启一个线程连接着Thread对象。在线程死亡之前,它会先休眠一秒钟,然后调用StoppableThread的stop()方法。
StoppableThread在实例域声明一个stopped的变量,并且初始时的值是false,一个stopThread()方法设置stopped的值是true,一个run()方法在每一次循环时都会去检查stopped的值是否改变为true。
运行上在的程序,你需要观察运行信息的顺序。
当你的程序运行在单处理器或单核的机器上,你将可能观察到应用停止。
在一个多处理器机器或多核的唯一处理器上,你可能不会看到这个停止,因为每一个处理器或核可能有它自己的副本stopped在缓存中。当一个线程修改它自己域的副本,其它线程拷贝的stopped将为不会改变。
你可能决定使用同步关键字去确保这个拷贝的stopped,在仅仅一个主要的内存调用中。之后你给在一对临界区的资源代码中结束这个同步,这个可能出现在下面的例子Listing2-3
Listing2-3通过同步关键字去停止一个线程
package com.owen.thread.chapter2; public class SynThreadStopping2_3 { public static void main(String[] args) { class StoppableThread extends Thread { private boolean stopped; // defaults to false @Override public void run() { synchronized(this) { while(!stopped) System.out.println("running"); } } synchronized void stopThread() { stopped = true; } } StoppableThread thd = new StoppableThread(); thd.start(); try { Thread.sleep(1000); // sleep for 1 second } catch (InterruptedException ie) { } thd.stopThread(); } }
Listing2-3是一个不好的思想,表现在两点:第一,尽管你仅仅需要去解决可见性问题,同步也解决了互斥问题(在这个例子中没有看到)。更重要的,在这个例子中你已经面临着一系列问题。
你要正确通过stopped来同步,但是你看同步阻塞在run()方法。注意while的循环。这个循环是不会结束的,因为线程执行循环体时,需要请求锁给当前的StoppableThread对象(通过synchronized(this)),和任何企图通过主线程去请求stopThread()中的对象,都会导致主线程的阻塞,因为主线程也需要同样的锁。
你可以通过局部变量来解决这个问题,在同步阻塞中注册stopped的值给这个变量,如下代码:
@Override public void run() { boolean _stopped = false; while (!_stopped) { synchronized (this) { _stopped = stopped; } System.out.println("running"); } }
然而,这个问题是混乱的和浪费资源的,因为当企图去请求锁时,应用需要花销,和每一次循环时都要去完成这个任务。Listing2-4的例子会更高效和更清晰。
package com.owen.thread.chapter2; public class SynThreadStopping2_4 { public static void main(String[] args) { class StoppableThread extends Thread { private volatile boolean stopped; // defaults to false @Override public void run() { while (!stopped) System.out.println("running"); } void stopThread() { stopped = true; } } StoppableThread thd = new StoppableThread(); thd.start(); try { Thread.sleep(1000); // sleep for 1 second } catch (InterruptedException ie) { } thd.stopThread(); } }
因为stopped被标记为volatile,每个线程都会通过主内存拷贝这个变量的副本,而不是通过缓存副本。这个应用将会停止,尽管在多处理器机器或多核机器。
Caution 使用volatile仅仅在这里可见性上发挥作用。你也可以声明这个字节在上下文领域(如果你试图让一个局部变量中加入volatile,那么你将会收到错误信息。)最后,你可以声明double和long域的volatitle,避免在32位的JVM,因为它需要两个操作访问double或long的变量值,和互斥(通过同步)需要访问他们的安全价值。
当全局的变量声明为volatile,它就不能再声明为final。然而,这并不是个问题,因为Java也可以让你安全通过final的局部却不需要同步。为了去解决缓存的问题,在DeadlockDemo的例子,我标记了两个lock1和lock2为final,当然我也可以标记为volatile。
你将会经常使用final去帮助你在一个不变的上下文类中确保线程安全。思考Listing2-5.
Listing2-5创建一个不变的和线程安全的类。
package com.owen.thread.chapter2; import java.util.Set; import java.util.TreeSet; public final class Planets { private final Set<String> planets = new TreeSet<>(); public Planets() { planets.add("Mercury"); planets.add("Venus"); planets.add("Earth"); planets.add("Mars"); planets.add("Jupiter"); planets.add("Saturn"); planets.add("Uranus"); planets.add("Neptune"); } public boolean isPlanet(String planetName) { return planets.contains(planetName); } }
Listing2-5显现一个不变的Planets的类,它的对象存储一个Set的集合中。尽管Set集合是可以改变的,这个类的设计防止在构造函数退出后修改集合。通过声明planets为final,这个引用存储在这个局部并且不能改变。然而,这个引用不能被缓存,所以这个缓存变量问题不存在。
Java提供一个特殊的安全线程确保不变量对象。这个对象可以在多线程中安全被调用,即使不使用同步来显示它们的引用,只要遵守以下规则:
不变对象不允许状态改变。
所有的全局都要声明为final.
对象必须正常被构造,这样“this”引用也不会从构造器逃逸。
最后一点问题可能会有点混淆,下面的例子说明了“this”引用从构造器中逃逸现象。
public class ThisEscapeDemo { private static ThisEscapeDemo lastCreatedInstance; public ThisEscapeDemo() { lastCreatedInstance = this; } }源码下载:git@github.com:owenwilliam/Thread.git
相关文章推荐
- [译]5.11. Functions and Variables Featured in This Chapter 本章的函数,变量和特性
- volatile 与 final 变量
- history and its relevant variables in Linux/GNU and Mac OS history命令以及相关环境变量
- Rails Constant and Global Variables(Rails的常量和全局变量)
- Python学习19:函数和变量 Function and variables
- 引用变量和变量的值(Referencing Variables and Variable Value)
- 涉及到复制和二进制日志中的选项和变量-Replication and Binary Logging Options and Variables
- 1.2 C++变量和数据类型 (Variables and Data types )
- 找错题:最终变量final
- variables and types(变量和数据类型)
- java内存模型与volatile变量与Atomic的compareAndSet
- SAP BW QUERY VARIABLES (BW增强变量)
- .NET/C#中的隐式类型局部变量(Implicitly Typed Local Variables)
- Java学习笔记(八)abstract,final,变量及接口
- 获取请求 Web 服务器变量的集合Request.ServerVariables 参数大全[转]
- final:修饰后,就是最终的意思。不能再被赋值,
- svn rm 时报错 svn: E205007: None of the environment variables SVN_EDITOR, VISUAL or EDITOR are set, and
- Java 理论与实践: 正确使用 Volatile 变量
- static变量 方法 类 和final
- 静态final变量的初始化