同步有两种属性:互斥性和可见性。synchronized关键字与两者都有关系。Java同时也提供了一种更弱的、仅仅包含可见性的同步形式,并且只以volatile关键字关联。

假设你自己设计了一个停止线程的机制(因为无法使用Thread不安全的stop()方法))。清单1中ThreadStopping程序源码展示了该如何完成这项任务。

清单1 尝试停止一个线程

public class ThreadStopping{   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();   } }

清单2中的main()方法声明了一个叫做StoppableThread的本地类,它继承自Thread。在初始化完StoppableThread之后,默认的主线程启动和这个 Thread对象关联的线程。之后它睡眠 1 秒,并且在死亡之前调用StoppableThread的stop()方法。

StoppableThread声明了一个被初始化为false的stopped实例变量,stopThread()方法会将该变量设置为true,同时run()方法中的while循环会在每次迭代中检查stopped的值是否已经修改为true。

照下面编译清单2:

javac ThreadStopping.java

运行程序:

java ThreadStopping 你应该能观测到一系列运行时的消息。

当你在单处理器/单核的机器上运行这个程序的时候,很可能会观测到程序停止。但是在一个多处理器的机器或多核单处理器的机器上,可能就看不到程序停止,因为每个处理器或者核心很可能有自己的一份stopped的拷贝,当一条线程修改了自己的拷贝,其他线程的拷贝并没有被改变。

你或许决定使用synchronized关键字以确保只能访问主存中的stopped变量。然后经过一番思考,你决定在清单3中使用同步访问一对临界区的方式来解决这个问题。

清单3 尝试使用synchronized来停止一个线程

public class ThreadStopping{   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();   }}

出于两个因素考虑,清单3不是一个好主意。尽管你只需解决可见性的问题,synchronized却同时解决了互斥的问题(在该程序中不是个问题)。更重要的是,你还往程序中引进了另一个更严重的问题。

你已经正确地对stopped进行了同步访问,但是进一步观察run()方法中的同步块,尤其是这个while循环。由于正在执行循环的这个线程已经获取了当前StoppableThread对象(通过synchronized(this))的锁,这个循环不会终止。因为默认的主线程需要获取相同的锁,所以它在该对象上调用stopThread()方法的任意尝试都会导致自己被阻塞住。

你可以使用局部变量并在同步块中将stopped的值赋给这个变量来解决这一问题,如下所示:

public void run(){   boolean _stopped = false;   while (!_stopped)   {      synchronized(this)      {         _stopped = stopped;      }      System.out.println("running");   }}

不过,每次循环迭代都要尝试获取锁的方式会存在性能开销(还不如以前),所以这个解决方式是得不偿失的。清单4展示了一个更为高效且整洁的方法。

清单4 尝试通过volatile关键字来停止一个线程

public class ThreadStopping{   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,每条线程都会访问主存中该变量的拷贝而不会访问缓存中的拷贝。这样,即使在多处理器或者多核的机器上,该程序也会停止。

警告

只有可见性导致问题时,才应该使用volatile。而且,你也只能在属性声明处才能使用这个保留字(如果你尝试将局部变量声明成volatitle,会收到一个错误)。最后,你可以将double和long型的属性声明成volatile,但是应该避免在32位的JVM上这样做,原因是此时访问一个double或者long型的变量值需要进行两步操作,若要安全地访问它们的值,互斥(通过synchronized)是必要的。 当一个属性变量声明成volatile,就不能同时被声明final的。不过,由于Java可以让你安全地访问final的属性而无需同步,这也就不能称之为一个问题了。为了克服DeadlockDemo中的缓存变量问题,我把lock1和lock2都标记成final,尽管也能将它们标记成volatile的。

以后,你会经常使用final关键字来确保在不可变(不会发生改变)类的上下文中线程的安全性。参考清单5。

清单5 借助于final创建一个不可变且线程安全的类

import java.util.Set;import java.util.TreeSet;public final class Planets{   private final Set 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);   }}

清单5展示了一个不可变类Planets,其对象存储着星球名字的集合。尽管集合是可变的,但这个类的设计却保证在构造函数退出之后,集合不会再被改变。通过声明planets为final,这个属性的引用不能被更改。而且,该引用也不能被缓存,所以缓存变量的问题也不复存在。

关于不可变对象,Java提供了一种特殊的线程安全的保证。即便没有用同步来发布(暴露)这些对象的引用,它们依然可以被多条线程安全地访问。不可变对象提供了下列易于识别的规则:

  • 不可变对象绝对不允许状态变更。
  • 所有的属性必须声明成final。
  • 对象必须被恰当地构造出来以防this引用脱离构造函数。

最后一点很让人迷惑,所以这里给出一个this显式地脱离构造函数的简单例子:

public class ThisEscapeDemo{   private static ThisEscapeDemo lastCreatedInstance;   public ThisEscapeDemo()   {      lastCreatedInstance = this;   }}

在www.ibm.com/developerworks/library/j-jtp0618/上查看《Java theory and practice:Safe construction techniques》学习更多常见线程风险的相关知识。


本文节选自《Java线程与并发编程实践》

Java线程和并发工具是应用开发中的重要部分,备受开发者的重视,也有一定的学习难度。

本书是针对Java 8中的线程特性和并发工具的快速学习和实践指南。全书共8章,分别介绍了Thread类和Runnable接口、同步、等待和通知、线程组、定时器框架、并发工具、同步器、锁框架,以及高级并发工具等方面的主题。每章的末尾都以练习题的方式,帮助读者巩固所学的知识。附录A给出了所有练习题的解答,附录B给出了一个基于Swing线程的教程。

本书适合有一定基础的Java程序员阅读学习,尤其适合想要掌握Java线程和并发工具的读者阅读参考。

auto.js停止所有线程_Java线程与并发编程实践:深入理解volatile和final变量相关推荐

  1. 并发编程之深入理解java线程

    并发编程之深入理解java线程 一.线程基础知识 1.1 进程和线程 1.1.1 进程 1.1.2 线程 1.1.3 进程与线程的区别 1.1.4 进程间通信的方式 1.2 线程的同步互斥 1.3 上 ...

  2. 《Java线程与并发编程实践》—— 2.3 谨防活跃性问题

    本节书摘来异步社区<Java线程与并发编程实践>一书中的第2章,第2.3节,作者: [美]Jeff Friesen,更多章节内容可以访问云栖社区"异步社区"公众号查看. ...

  3. java并发编程实践(2)线程安全性

    [0]README 0.0)本文部分文字描述转自:"java并发编程实战", 旨在学习"java并发编程实践(2)线程安全性" 的相关知识: 0.1)几个术语( ...

  4. 《Java线程与并发编程实践》—— 2.6 小结

    本节书摘来异步社区<Java线程与并发编程实践>一书中的第2章,第2.6节,作者: [美]Jeff Friesen,更多章节内容可以访问云栖社区"异步社区"公众号查看. ...

  5. 《Java线程与并发编程实践》—— 1.2 操作更高级的线程任务

    本节书摘来异步社区<Java线程与并发编程实践>一书中的第1章,第1.2节,作者: [美]Jeff Friesen,更多章节内容可以访问云栖社区"异步社区"公众号查看. ...

  6. java并发编程实践_Java并发编程实践如何正确使用Unsafe

    一.前言 Java 并发编程实践中的话: 编写正确的程序并不容易,而编写正常的并发程序就更难了.相比于顺序执行的情况,多线程的线程安全问题是微妙而且出乎意料的,因为在没有进行适当同步的情况下多线程中各 ...

  7. auto.js停止所有线程_Java多线程编程基础知识 概念介绍,以及线程状态

    一.进程 进程是操作系统结构的基础:是一次程序的执行:是一个程序及其数据在处理机上顺序执行时所发生的活动.操作系统中,几乎所有运行中的任务对应一条进程(Process).一个程序进入内存运行,即变成一 ...

  8. auto.js停止所有线程_十年架构师带你快速上手多线程

    这世上有三样东西是别人抢不走的:一是吃进胃里的食物,二是藏在心中的梦想,三是读进大脑的书 多线程快速入门 1.线程与进程区别 每个正在系统上运行的程序都是一个进程.每个进程包含一到多个线程.线程是一组 ...

  9. auto.js停止所有线程_多线程快速入门(二)及面试题

    获取线程对象以及名称 常用线程api方法 start()启动线程 currentThread()获取当前线程对象 getID()获取当前线程ID Thread-编号 该编号从0开始 getName() ...

最新文章

  1. aws lambda使用_使用AWS Lambda安排Slack消息
  2. 标称变量(Categorical Features)或者分类变量(Categorical Features​​​​​​​)缺失值填补、详解及实战
  3. 企业级的开发组件02 - DevExpress DXperience Universal 2011.2.5 Final
  4. 当我们不为了拿奖,而在做事情的时候
  5. C++ code:数值计算之矩形法求解积分问题
  6. Sea Battle
  7. 作者:桑基韬(1985-),男,博士,中国科学院自动化研究所副研究员
  8. matlab的三维数组(三维矩阵)
  9. 使用自定义端口连接SQL Server 2008的方法
  10. NWT失败反省:买个流量卡汝也看不顺眼,业务搞了几个?
  11. Linux 文件夹压缩命令总结
  12. ITOP4412开发板学习前的准备2 -- 安装ADB驱动
  13. ThreeJs 数据可视化学习扫盲
  14. 社会网络分析法SNA
  15. [实战]爬取网抑云音乐评论
  16. Dart语言中的Isolate
  17. 一键解锁,2022阿里顶会创新技术前沿进展
  18. 锂矿之龙战于野:天齐锂业
  19. 2019——区块链从业者的集体冬眠
  20. 基于 HTML+CSS+JS 的纸牌记忆游戏

热门文章

  1. npm升级package.json依赖包
  2. redhat相关配置
  3. 【读书笔记】《深入浅出Webpack》
  4. asyn4j -- java 异步方法调用框架
  5. Codeforces 722C. Destroying Array
  6. switch…case语句注意事项
  7. javascript --- 将DOM结构转换成虚拟DOM 虚拟DOM转换成真实的DOM结构
  8. Istio流量管理实践之(5): 使用cert-manager部署Istio自定义入口网关及进行证书管理...
  9. SqlServer图形数据库初体验
  10. PLSQL DBMS_DDL.ANALYZE_OBJECT