经典plc程序设计计

面试题:线程安全的单例模式 - Let It Go - ITeye技术网站
博客分类:
面试被问到一个线程安全的单例模式问题,想拿出来讨论一下,我通常会使用的这样的写法来实现单例:
public class Singleton {
private Singleton() {}
private static Singleton instance =
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
单例的目的是为了保证运行时Singleton类只有唯一的一个实例,最常用的地方比如拿到数据库的连接,Spring的中创建BeanFactory这些开销比较大的操作,而这些操作都是调用他们的方法来执行某个特定的动作。
面试官的问题是:单例会带来什么问题?
我第一反映就是如果多个线程同时调用这个实例,会有线程安全的问题,当时就这么说了,然后他问:“怎么实现一个线程安全的单例模式呢?”
这个问题我没有回答上来,当时脑子里闪了一下如果用synchronized来锁定可能会有一些问题,至于是什么问题没有想明白,就选择没有回答。
这里请问各位高手,
1、如果不执行修改对象的操作的情况下,单单执行一个读取操作,还有没有进行同步的必要?
2、保证单例的线程安全使用synchronized会产生什么样的问题?
3、不使用synchronized,有什么方式来保证线程安全?
4、假如下次再面试遇到这种情形,用什么方式回答会使面试官感到比较满意?
--------------------------------------------------------------------------------------------------------------------------------------------------------------
感谢大家的讨论与支持,总结一下:
实际上使用什么样的单例实现取决于不同的生产环境,懒汉式也就是我在上面举得那个例子,这种方式适合于单线程程序,多线程情况下需要保护getInstance()方法,否则可能会产生多个Singleton对象的实例。
在此基础上确保getInstance()方法一次只能被一个线程调用就需要在getInstance()方法之前加上 synchronized 关键字,锁定整个方法,
public class Singleton{
private static Singleton instance=
private Singleton(){}
public static synchronized Singleton getInstance(){
if(instance==null){
instance=new Singleton();
但很多时候我们通常会认为锁定整个方法的是比较耗费资源的,代码中实际会产生多线程访问问题的只有 instance = new Singleton(); 这一句,
为了降低 synchronized 块性能方面的影响,只锁定instance = new Singleton(); 这一句,“weishuang”回帖中使用的就是这种方式:
public class Singleton{
private static Singleton instance=
private Singleton(){}
public static Singleton getInstance(){
if(instance==null){
synchronized(Singleton.class){
instance=new Singleton();
分析这种实现方式,两个线程可以并发地进入第一次判断instance是否为空的if 语句内部,第一个线程执行new操作,第二个线程阻断,当第一个线程执行完毕之后,第二个线程没有进行判断就直接进行new操作,所以这样做也并不是安全的。
为了避免第二次进入synchronized块没有进行非空判断的情况发生,添加第二次条件判断,就像“tomorrow009”在帖子中回复的示例一样
public static Singleton getInstance(){
if(instance == null){
synchronize{
if(instance == null){
instance =
new Singleton();
这样就产生了二次检查,但是二次检查自身会存在比较隐蔽的问题,查了在DeveloperWorks上的一篇文章,对二次检查的解释非常的详细:
其实找到这篇文章之后,我的问题基本上就已经可以解决了,但是看到回帖的同学们也有一些和我一样的问题,还想把这个问题继续梳理一遍。
使用二次检查的方法也不是完全安全的,原因是 java 平台内存模型中允许所谓的“无序写入”会导致二次检查失败,所以使用二次检查的想法也行不通了。
在最后提出这样的观点:“无论以何种形式,都不应使用双重检查锁定,因为您不能保证它在任何 JVM 实现上都能顺利运行。”
"netrice"在回复中提到了使用“java5以后的volatile关键字”,用volatile关键字来声明变量,声明成 volatile 的变量被认为是顺序一致的,即,不是重新排序的。但是volatile关键字的特性并不适用于这篇帖子所讨论的问题关键。
通过上面的分析,可以看到使用懒汉式的lazy方式实现单例弯弯绕太多,在单线程编程的情况下懒汉式单例实现是没有任何问题的,如果在多线程的情况下,我们需要比较小心,对getInstances()方法加上synchronized关键字,这样虽然可能有一些性能上的牺牲,但是更加的安全。绕了这么大的一个弯,又回来了:
/* 安全的方式 1 */
public class Singleton{
private static Singleton instance=
private Singleton(){}
public static synchronized Singleton getInstance(){
if(instance==null){
instance=new Singleton();
提到的另外一种实现方式是这样的,放弃使用 synchronized 关键字,而使用 static 关键字:
/* 安全的方式 2 */
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
这种方式没有使用同步,并且确保了调用static getInstance()方法时才创建Singleton的引用(static 的成员变量在一个类中只有一份)。
还有“keshin”提到的方式则更加灵巧,没有使用同步但保证了只有一个实例,还同时具有了Lazy的特性(出自)
/* 安全的方式 3 */
public class ResourceFactory {
private static class ResourceHolder {
public static Resource resource = new Resource();
public static Resource getResource() {
return ResourceFactory.ResourceHolder.
static class Resource {
上面的方式是值得借鉴的,在ResourceFactory中加入了一个私有静态内部类ResourceHolder ,对外提供的接口是 getResource()方法,也就是只有在ResourceFactory .getResource()的时候,Resource对象才会被创建,
这种写法的巧妙之处在于ResourceFactory 在使用的时候ResourceHolder 会被初始化,但是ResourceHolder 里面的resource并没有被创建,
这里隐含了一个是static关键字的用法,使用static关键字修饰的变量只有在第一次使用的时候才会被初始化,而且一个类里面static的成员变量只会有一份,这样就保证了无论多少个线程同时访问,所拿到的Resource对象都是同一个。
饿汉式的实现方式虽然貌似开销比较大,但是不会出现线程安全的问题,也是解决线程安全的单例实现的有效方式。
至于ThreadLocal,我认为还是应该由使用场景来决定。
在《Java与模式》中,作者提出:“饿汉式单例类可以在Java语言实现,但不易在C++内实现,因为静态初始化在C++里没有固定的顺序,因而静态的instance变量的初始化与类的加载顺序没有保证,可能会出问题。这就是为什么GoF在提出单例类的概念时,举的例子是懒汉式的。他们的书影响之大,以致Java语言中单例类的例子也大多是懒汉式的。实际上,本书认为饿汉式单例类更符合Java语言本身的特点。”
由此可见在应用设计模式的同时,分析具体的使用场景来选择合适的实现方式是非常必要的。
寻找问题解决过程中找的一些参考资料:
因为在精华帖中没有找到很流畅解释这个问题的内容才发了这个帖子,还是很不幸的被评为了新手帖,但如果下次有面试官问有关线程安全的单例模式问题,我想我知道该怎么回答了。
论坛回复 /
(45 / 28482)
viei 写道我一般这样写
public class Singleton{
static class SingletonHolder{
& static Singleton instance=new Singleton();
public static Singleton getInstance(){
& return Singleton.instance();
最早从google的一个叫lee的人那里学来的,现在基本都这样写
恩,这样写确实精妙,避免了对静态数据域直接赋值所带来的浪费。不用在不用的时候创建对象。
呵呵,我自己说的拗口了。
Singleton.instance();
的instance()方法哪里定义的?》》》》》》》》》》》》》》》
我一般这样写
public class Singleton{
static class SingletonHolder{
& static Singleton instance=new Singleton();
public static Singleton getInstance(){
& return Singleton.instance();
最早从google的一个叫lee的人那里学来的,现在基本都这样写
恩,这样写确实精妙,避免了对静态数据域直接赋值所带来的浪费。不用在不用的时候创建对象。
呵呵,我自己说的拗口了。
xl10230 写道tomorrow009 写道Singleton模式分两种,“懒汉”和“恶汉” 恶汉模式也就是前面很多人提到的
public class Singleton{
private static Singleton instance = new Singleton();
private Singleton(){
//私有化构造函数.
public static Singleton getInstance(){
由于实例在类加载时就已经创建,所以不存在线程同步问题,如果该Singleton初始化时不需要很大的开销(比如io操作/数据库连接)之类的,通常用这种办法就可以了.
某些情况下我们希望实例只有被第一次用到的时候才创建,那么这时候就使用“懒汉”模式,初学者通常会采用LZ的写法,正如面试官所说,这样会有线程同步问题,而导致多个实例被创建。如果在 getInstance()方法前面加上synchronize,又会大大降低系统性能。其实我们需要考虑的是,究竟要同步哪里? 我们只需要同步new Singleton()这个部分,保证只有一个实例被创建出来即可,而无须同步整个getInstance()方法,那么就可以考虑这样做
public static Singleton getInstance(){
if(instance == null){
synchronize{
if(instance == null){
instance =
new Singleton();
& 应该就是这样了。
顶一个double checked模式
真搞不懂,这样跟
&&& public static Singleton getInstance(){
&&&&&&&&&&& synchronize{
&&&&&&&&&&&&&& if(instance == null){
&&&&&&&&&&&&&&&&& instance =& new Singleton();
&&&&&&&&&&&&&& }
&&&&&&&&&&&&&
&&&&&&&&&&& }
&&& }
有什么区别,多此一举吗?
public class ResourceFactory {
private static class ResourceHolder {
public static Resource resource = new Resource();
public static Resource getResource() {
return ResourceFactory.ResourceHolder.
static class Resource {
java concurrency in practice中建议的方式
又学习了单例模式一种新的写法
通常有两种常见的策略实现单例,一如lz所言,即所谓lazy形式的。如果害怕线程安全问题,而又不想用synchronized影响性能的话,不如用另一种:
public class Singleton {&
&&& private Singleton() {}&
&&& // 载入class时立即初始化
&&& private static Singleton instance = new Singleton();&
&&& public static Singleton getInstance() {&
当然这种也有缺点,instance会立即初始化,而不管是否实际用到。:)
这个缺点根本就不是缺点。你用不到这个类为什么访问它呢,访问它获得实例必须初始化。说白了这个所谓缺点仔细想明白,你会发现这是最佳实现方式。
bencode 写道keshin 写道
public class ResourceFactory {
private static class ResourceHolder {
public static Resource resource = new Resource();
public static Resource getResource() {
return ResourceFactory.ResourceHolder.
static class Resource {
java concurrency in practice中建议的方式
这个好, 延迟初始化,线程安全,效率高(没有使用同步锁,而由类加载器保证) 简洁
凤舞凰杨都跟你们说了多看看书,还是有人搞出什么double check啊之类的来.
上面这个是目前最简单有效的单例方式.
就是一堆人视而不见
我也看过developerworks上讨论的double-check的问题。但据说那个问题在jdk1.5已经解决了。/blog/259991
应该可以像/developerworks/cn/java/j-dcl.html中所说的那样分析出来的,但我电脑上目前没安装Visual Studio,先留言在这,回去试出来了再说。
……楼上已经说了。
bencode 写道keshin 写道
public class ResourceFactory {
private static class ResourceHolder {
public static Resource resource = new Resource();
public static Resource getResource() {
return ResourceFactory.ResourceHolder.
static class Resource {
java concurrency in practice中建议的方式
这个好, 延迟初始化,线程安全,效率高(没有使用同步锁,而由类加载器保证) 简洁
凤舞凰杨都跟你们说了多看看书,还是有人搞出什么double check啊之类的来.
上面这个是目前最简单有效的单例方式.
就是一堆人视而不见
是的上面是最简单有效的 Lazy Loading Singletons实现方法,关于几种Singleton实现方法,Google工程师Bob Lee有个很好的帖子 http://crazybob.org/2007/01/lazy-loading-singletons.html。
总结成以下三点:
1. 使用Synchronized同步getInstance方法, 简单有效适合所有的JVM版本,但Lock contention带来性能开销
2. 使用Double-checked Locking 和只同步create instance的部分,同时必须声明单列变量为volatile,否则同样不是完全线程安全的。同样由于Java Memory Model的对volatile的模糊定义,这个模式无法使用在5之前的JVM。新的JMM对volatile定义更明确,compound operation (比如++, get-set)也是原子性的,所以DCL可以放心使用在Java 5中。使用在5以后版本,可以提升10%性能(bob lee测试)
3. 最快的方法还是Lazy Loading Singletongs, 它从Initialization on Demand Holder (IODH) 模式演化而来, 针对这个模式Effective Java 第48条也有很详细的描述。
最后还是要看情况来合理使用各种技巧, 很多时候其实最老土的发法一还是很好很管用的
tomorrow009 写道Singleton模式分两种,“懒汉”和“恶汉” 恶汉模式也就是前面很多人提到的
public class Singleton{
private static Singleton instance = new Singleton();
private Singleton(){
//私有化构造函数.
public static Singleton getInstance(){
由于实例在类加载时就已经创建,所以不存在线程同步问题,如果该Singleton初始化时不需要很大的开销(比如io操作/数据库连接)之类的,通常用这种办法就可以了.
某些情况下我们希望实例只有被第一次用到的时候才创建,那么这时候就使用“懒汉”模式
第一种方式(加载时创建实例)有什么问题?类的加载机制是第一次调用static方法时类才加载,也就是在调用getInstance()方法时Singleton才被加载,这样跟第二种的加载时间有什么不同?不都是第一次使用时才加载吗?
以上是我的困惑,希望楼下有人给我解惑
同问。
只在调用静态方法getInstance时,instance 才会初始化。至少在JDK1.6是如此的。又何必去再里面定义一个内部类,加一个静态变量,在getInstance方法里面调用此变量呢。很疑惑。
PS 越看越觉得书读的少啊。。。
Hibernate的文档时看到了关于使ThreadLocal管理多线程访问的部分。具体代码如下
1.& public static final ThreadLocal session = new ThreadLocal();
2.& public static Session currentSession() {
3.&&&&& Session s = (Session)session.get();
4.&&&&& //open a new session,if this session has none
5.&& if(s == null){
6.&&&&& s = sessionFactory.openSession();
7.&&&&& session.set(s);
8.&& }
&&&&&
9. }
我们逐行分析
1。 初始化一个ThreadLocal对象,ThreadLocal有三个成员方法 get()、set()、initialvalue()。
&&& 如果不初始化initialvalue,则initialvalue返回null。
3。session的get根据当前线程返回其对应的线程内部变量,也就是我们需要的net.sf.hibernate.Session(相当于对应每个数据库连接).多线程情况下共享数据库链接是不安全的。ThreadLocal保证了每个线程都有自己的s(数据库连接)。
5。如果是该线程初次访问,自然,s(数据库连接)会是null,接着创建一个Session,具体就是行6。
6。创建一个数据库连接实例 s
7。保存该数据库连接s到ThreadLocal中。
8。如果当前线程已经访问过数据库了,则从session中get()就可以获取该线程上次获取过的连接实例。
LZ这个也没谈到Hibernate 你们干嘛要说Hibernate中的ThreadLocal啊 - - 崩溃啊啊!!!!!
就是一个管理Sessin的类
keshin 写道
public class ResourceFactory {
private static class ResourceHolder {
public static Resource resource = new Resource();
public static Resource getResource() {
return ResourceFactory.ResourceHolder.
static class Resource {
java concurrency in practice中建议的方式
这个好, 延迟初始化,线程安全,效率高(没有使用同步锁,而由类加载器保证) 简洁
凤舞凰杨都跟你们说了多看看书,还是有人搞出什么double check啊之类的来.
上面这个是目前最简单有效的单例方式.
就是一堆人视而不见
tomorrow009 写道Singleton模式分两种,“懒汉”和“恶汉” 恶汉模式也就是前面很多人提到的
public class Singleton{
private static Singleton instance = new Singleton();
private Singleton(){
//私有化构造函数.
public static Singleton getInstance(){
由于实例在类加载时就已经创建,所以不存在线程同步问题,如果该Singleton初始化时不需要很大的开销(比如io操作/数据库连接)之类的,通常用这种办法就可以了.
某些情况下我们希望实例只有被第一次用到的时候才创建,那么这时候就使用“懒汉”模式,初学者通常会采用LZ的写法,正如面试官所说,这样会有线程同步问题,而导致多个实例被创建。如果在 getInstance()方法前面加上synchronize,又会大大降低系统性能。其实我们需要考虑的是,究竟要同步哪里? 我们只需要同步new Singleton()这个部分,保证只有一个实例被创建出来即可,而无须同步整个getInstance()方法,那么就可以考虑这样做
public static Singleton getInstance(){
if(instance == null){
synchronize{
if(instance == null){
instance =
new Singleton();
& 应该就是这样了。
顶一个double checked模式
& 上一页 1
xiaozhi7616
浏览: 135393 次
来自: 北京
very good,
jiht594 写道楼主你好:这个第一段js代码+那一行htm ...
楼主你好:这个第一段js代码+那一行html代码我试的时候为什 ...
多谢 还有别的方法 例如取消linksbuilder 不 ...
嗯,写的不错,明白啥意思了。。。。。。就是和配置助手里面配的那 ...进程: 进程就是正在运行的应用程序。 进程了负责了内存空间划分。
线程: 一个进程中的 代码是由线程去执行的,线程也就是进程中一个执行路径。
多线程: 一个进程中有多个线程可以同时执行任务。
多线程 的好处:
1. 解决一个进程中可以同时执行多个任务的问题。
2. 提高了资源利用率。
多线程的弊端:
1. 增加了cpu的负担。
2. 降低了一个进程中线程 的执行概率。
3. 出现了线程 安全问题。
4. 会引发死锁现象。
自定义线程 的实现方式:
1. 自定义一个类继承Thread类。
2. 重写Thread类的run方法,把自定义线程的任务代码写在run方法上。
3. 创建Thread的子类对象,并且调用start方法启动一个线程。
注意:千万不要直接调用run方法,调用start方法的时候线程就会开启,线程一旦开启就会执行run方法中代码,如果直接调用
run方法,那么就 相当于调用了一个普通的方法而已。
线程安全问题:
线程安全出现 的根本原因:
1. 存在两个或者两个以上 的线程对象共享同一个资源。
2. 多线程操作共享资源的代码 有多句。
线程安全问题的解决方案:
方式一: 可以使用同步代码块去解决。
synchronized(锁对象){
需要被同步的代码
同步代码块要注意的事项:
1. 锁对象可以是任意的一个对象。
2. 一个线程在同步代码块中sleep了,并不会释放锁对象。
3. 如果不存在着线程安全问题,千万不要使用同步代码块,因为会降低效率。
4. 锁对象必须是多线程共享的一个资源,否则锁不住。
方式二:同步函数
同步函数就是使用synchronized修饰一个函数。
同步函数要注意的事项 :
1. 如果是一个非静态的同步函数的锁 对象是this对象,如果是静态的同步函数的锁 对象是当前函数所属的类的字节码文件(class对象)。
2. 同步函数的锁对象是固定的,不能由你来指定 的。
推荐使用: 同步代码块。
1. 同步代码块的锁对象可以由我们随意指定,方便控制。同步函数的锁对象是固定 的,不能由我们来指定。
2. 同步代码块可以很方便控制需要被同步代码的范围,同步函数必须是整个函数 的所有代码都被同步了。
需求: 一个银行账户5000块,两夫妻一个拿着 存折,一个拿着卡,开始取钱比赛,每次只能取一千块,要求不准出现线程安全问题。
class BankThread extends Thread{
int count = 5000;
public BankThread(String name){
super(name);
public synchronized
void run() {
while(true){
synchronized ("锁") {
if(count&0){
System.out.println(Thread.currentThread().getName()+"取走了1000块,还剩余"+(count-1000)+"元");
count= count - 1000;
System.out.println("取光了...");
//静态的函数----&函数所属 的类的字节码文件对象---&BankThread.class
public static synchronized
void getMoney(){
public class Demo1 {
public static void main(String[] args) {
//创建两个线程对象
BankThread thread1 = new BankThread("老公");
BankThread thread2 = new BankThread("老婆");
//调用start方法开启线程取钱
thread1.start();
thread2.start();
阅读(...) 评论()java中,如何安全的结束一个正在运行的线程?
我的图书馆
java中,如何安全的结束一个正在运行的线程?
  问题  Java中提供了很多调度线程的方法,上一节介绍了其中一种控制线程的方法:如何等待一个线程结束。那么如果不希望等待线程结束,而是根据问题的需要随时都要中断线程使其结束,这种对线程的控制方法该如何实现呢?  解决思路  首先必须先明确“中断”这个概念的实际含义,这里的中断是指一个线程在其任务完成之前被强行停止,提前消亡的过程。查阅JDK的帮助文档,可以找到这样一个和中断有关的方法:interrupt()。  它的语法格式如下所示:  public void interrupt()  该方法的功能是中断一个线程的执行。但是,在实际使用当中发现,这个方法不一定能够真地中断一个正在运行的线程。下面通过一个例子来看一看使用interrput()方法中断一个线程时所出现的结果。程序代码如下所示:  // 例4.4.1 InterruptThreadDemo.java  class MyThread extends Thread  {  public void run()  {  while(true) // 无限循环,并使线程每隔1秒输出一次字符串  {&  System.out.println(getName()+' is running');&  try{&  sleep(1000);  }catch(InterruptedException e){  System.out.println(e.getMessage());  }  }  }  }  class InterruptThreadDemo  {  public static void main(String[] args) throws InterruptedException  {  MyThread m=new MyThread(); // 创建线程对象m  System.out.println('Starting thread...');  m.start(); // 启动线程m  Thread.sleep(2000); //主线程休眠2秒,使线程m一直得到执行  System.out.println('Interrupt thread...');  m.interrupt(); // 调用interrupt()方法中断线程m  Thread.sleep(2000); // 主线程休眠2秒,观察中断后的结果  System.out.println('Stopping application...'); // 主线程结束  }  }  这个程序的本意是希望,当程序执行到m.interrupt()方法后,线程m将被中断并进入消亡状态。然而运行这个程序,屏幕里显示了出人意料的结果,如图4.4.1所示。  通过对结果的分析,可以发现,用户线程在调用了interrupt()方法之后并没有被中断,而是继续执行,直到人为地按下Ctrl+C或者Pause键为止。这个例子说明一个事实,直接使用interrput()方法并不能中断一个正在运行的线程。那么用什么样的方法才能中断一个正在运行的线程呢?    图 4.4.1 对线程调用了interrupt()  通过查阅JDK,有些读者可能会看到Thread类中所提供的stop()方法。但是在这里需要强调的是,虽然该方法确实能够停止一个正在运行的线程,但是该方法是不安全的,因为有时使用它会导致严重的系统错误。例如一个线程正在等待关键的数据结构,并只完成了部分地改变,如果在这一时刻停止该线程,那么数据结构将会停留在错误的状态上。正因为如此,在Java后期的版本中,它将不复存在。因此,使用stop()方法来中断一个线程是不合适的。  这时我们想到了使用共享变量的方式,通过一个共享信号变量来通知线程是否需要中断,如果需要中断,则停止正在执行的任务,否则让任务继续执行。这种方式是如何实现的呢?  具体步骤  在这种方式中,之所以引入共享变量,是因为该变量可以被多个执行相同任务的线程用来作为是否中断的信号,通知中断线程的执行。下面通过在程序中引入共享变量来改进前面例4.4.1,改进后的代码如下所示:  // 例4.4.2 InterruptThreadDemo2.java  class MyThread extends Thread  {  boolean stop = // 引入一个布尔型的共享变量stop  public void run()  {  while(!stop) // 通过判断stop变量的值来确定是否继续执行线程体  {  System.out.println(getName()+' is running');  try  {  sleep(1000);&  }catch(InterruptedException e){  System.out.println(e.getMessage());  }  }  System.out.println('Thread is exiting...');  }  }  class InterruptThreadDemo2  {  public static void main(String[] args) throws InterruptedException  {  MyThread m=new MyThread();  System.out.println('Starting thread...');  m.start();  Thread.sleep(3000);&  System.out.println('Interrupt thread...');  m.stop= // 修改共享变量  Thread.sleep(3000); // 主线程休眠以观察线程中断后的情况  System.out.println('Stopping application...');  }  }  在使用共享变量来中断一个线程的过程中,线程体通过循环来周期性的检查这一变量的状态。如果变量的状态改变,说明程序发出了立即中断该线程的请求,此时,循环体条件不再满足,结束循环,进而结束线程的任务。程序执行的结果如图4.4.2所示:    图4.4.2 引入共享变量来中断线程  其中,主程序中的第二个Thread.sleep(3000);语句就是用来使程序不提早结束,以便观察线程m的中断情况。结果是一旦将共享变量stop设置为true,则中断立即发生。  为了更加安全起见,通常需要将共享变量定义为volatile类型或者将对该共享变量的一切访问封装到同步的代码或者同步方法中去。后者所提到的技术将在第4.5节中介绍。  在多线程的程序中,当出现有两个或多个线程共享同一实例变量的情况时,每一个线程可以保持这个实例变量自己的私有副本,变量的实际备份在不同时间被更新。而问题就是变量的主备份总是需要反映它的当前状态,此时反而使效率降低。为保证效率,只需要简单地指定变量为volatile类型即可,它可以告诉编译器必须总是使用volatile变量的主备份(或者至少总是保持任何私有的备份和最新的备份一样,反之亦然)。同样,对主变量的访问必须同任何私有备份一样,精确地顺序执行。  如果需要一次中断所有由同一线程类创建的线程,该怎样实现呢?有些读者可能马上就想到了对每一个线程对象通过设置共享变量的方式来中断线程。这种方法当然可以,那么有没有更好的方法呢?  此时只需将共享变量设置为static类型的即可。然后在主程序中当需要中断所有同一个线程类创建的线程对象时,使用MyThread.stop=语句就可实现对所有同一个线程类创建的线程对象的中断操作,而且效率明显提高。读者不妨试一试。  专家说明  通过本节介绍了如何中断一个正在执行的线程,既不是用stop()方法,也不是用interrupt()方法,而是通过引入了共享变量的形式有效地解决了线程中断的问题。其实这种方法有很多好处,它避免了一些无法想象的意外情况的发生,特别是将共享变量所访问的一切代码都封装到同步方法中以后,安全性将更高。在本节中,还可以尝试创建多个线程来检验这种中断方式的好处。此外,还介绍了volatile类型说明符的作用,这更加有助于提高中断线程的效率,值得提倡。  专家指点  本小节不仅要掌握如何使用共享变量的方法来中断一个线程,还要明白为什么使用其他方法来中断线程就不安全。其实,在多线程的调度当中还会出现一个问题,那就是死锁。死锁的出现将导致线程间均无法向前推进,从而陷入尴尬的局面。因此,为减少出现死锁的发生,Java 1.2以后的版本中已经不再使用Thread类的stop(),suspend(),resume()以及destroy()方法。特别是不安全的stop()方法,原因就是它会解除由线程获取的所有锁定,而且一旦对象处于一种不连贯的状态,那么其他线程就能在那种状态下检查和修改它们,结果导致很难再检查出问题的真正所在。因此最好的方法就是,用一个标志来告诉线程什么时候应该退出自己的run()方法,并中断自己的执行。通过后面小节的学习将会更好的理解这个问题。  相关问题  如果一个线程由于等待某些事件的发生而被阻塞,又该如何实现该线程的中断呢?比如当一个线程由于需要等候键盘输入而被阻塞,处于不可运行状态时,即使主程序中将该线程的共享变量设置为true,但该线程此时根本无法检查循环标志,当然也就无法立即中断。  其实,这种情况经常会发生,比如调用Thread.join()方法,或者Thread.sleep()方法,在网络中调用ServerSocket.accept()方法,或者调用了DatagramSocket.receive()方法时,都有可能导致线程阻塞。即便这样,仍然不要使用stop()方法,而是使用Thread提供的interrupt()方法,因为该方法虽然不会中断一个正在运行的线程,但是它可以使一个被阻塞的线程抛出一个中断异常,从而使线程提前结束阻塞状态,退出堵塞代码。  下面看一个例子来说明这个问题:  // 例4.4.3 InterruptThreadDemo3.java  class MyThread extends Thread  {  volatile boolean stop =  public void run()  {  while(!stop)  {  System.out.println(getName()+' is running');  try  {  sleep(1000);  }catch(InterruptedException e){&  System.out.println('week up from blcok...');  stop= // 在异常处理代码中修改共享变量的状态  }  }  System.out.println(getName()+' is exiting...');  }  }  class InterruptThreadDemo3  {  public static void main(String[] args) throws InterruptedException  {  MyThread m1=new MyThread();  System.out.println('Starting thread...');  m1.start();  Thread.sleep(3000);&  System.out.println('Interrupt thread...:'+m1.getName());  m1.stop= // 设置共享变量为true  m1.interrupt(); // 阻塞时退出阻塞状态  Thread.sleep(3000); // 主线程休眠3秒以便观察线程m1的中断情况  System.out.println('Stopping application...');  }  }  程序中如果线程m1发生了阻塞,那么虽然执行了m1.stop=语句,但是stop的值并未改变。为了能够中断该线程,必须在异常处理语句中对共享变量的值进行重新设置,从而实现了在任何情况下都能够中断线程的目的。  一定要记住,m1.interrupt();语句只有当线程发生阻塞时才有效。它的作用就是抛出一个InterruptedException类的异常对象,使try…catch语句捕获异常,并对其进行处理。请读者仔细研究这个程序,以便能够看出其中的巧妙之处。
TA的最新馆藏[转]&

我要回帖

更多关于 经典plc程序设计 的文章

 

随机推荐