2.4变动和最终变量(Volatile and Final Variables)

在前面的学习同步锁显示了两个属性:互斥和可见性。同步关键字连接着这两个属性。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通过同步关键字去停止一个线程

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



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值