成员变量与线程安全

经过集中状况来观察成员变量对线程安全的影响:
java

1.数据不共享

线程类代码以下:
web

package com.feng.example;

public class MyThread extends Thread {

	private int count = 5;
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(count > 0)
		{
			count--;
			System.out.println(Thread.currentThread().getName()+"==="+count);
		}
	}	
}

测试类代码以下数组

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		Thread a = new MyThread();
		Thread b = new MyThread();
		Thread c = new MyThread();

		a.start();
		b.start();
		c.start();
	}

}

分析:在主程序中分别建立了三个线程实例对象,三个实例对象有本身的内容空间,有本身的成员变量,内存模型以下图所示:安全

三个线程a.b.c都有各自的count成员变量,三者运行互不影响。所以在数据不共享的状况下是不会出现线程安全问题的。多线程

程序输出:ide

Thread-0===4
Thread-0===3
Thread-0===2
Thread-0===1
Thread-0===0
Thread-1===4
Thread-1===3
Thread-1===2
Thread-1===1
Thread-1===0
Thread-2===4
Thread-2===3
Thread-2===2
Thread-2===1
Thread-2===0

2.数据共享

在数据共享这一部分分为两个部分来说:测试

(1)多个线程实例(同一个类的实例)的成员变量指向同一个对象,那就将成员变量改成static类型

线程类改写为:this

package com.feng.example;

public class MyThread extends Thread {

	private static int  count = 5;
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(count > 0)
		{
			count--;
			System.out.println(Thread.currentThread().getName()+"==="+count);
		}
	}	
}

测试类不变:spa

分析:三个实例对象的成员变量都是使用指向的同一个成员变量,内存结构以下图所示:线程

三个线程修改的是同一个count变量,那么执行结果就再也不是每个线程都会循环5次了。除此以外,当线程a执行到了count--时,cpu切换去执行线程b,线程一样执行到count--而后输出,就会出现输出两次3,而没有结果4。这就出现了线程安全问题。其余的线程修改了本线程中还未处理完的数据(这里指的是输出)。

输出结果为:

Thread-0===3
Thread-0===2
Thread-0===1
Thread-0===0
Thread-1===3

由结果能够看出,Thread-0和Thread-1都输出了3,而正确的结果应该是输出4,3,2,1,0这几个数组都有的

解决方案:能够将方法定义为同步方法,在方法前加synchronized关键字??这种解决固然不正确,由于三个线程实例是三个对象,方法级别的synchronized是对对象加锁,因此对象各不相同所以在方法上加同步是没有任何效果的。正确的作法是使用synchronized语句块对MyThread的class文件加锁,程序修改以下:

package com.feng.example;

public class MyThread extends Thread {

	private static int  count = 5;
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		synchronized(MyThread.class)
		{
			while(count > 0)
			{
				count--;
				System.out.println(Thread.currentThread().getName()+"==="+count);
			}
		}
	}	
}

测试类不修改,执行结果以下:

Thread-2===4
Thread-2===3
Thread-2===2
Thread-2===1
Thread-2===0

固然解决的方案有不少,这里不细讲解决方案。

有的书本中的讲解都会说起count++, count--会被分红三步操做的问题,这里我我的认为存在这一方面的缘由,但也存在count--后时间片到了的状况,去执行其余线程的代码块,致使了count的值不许确。验证这一说法的办法就是将count--修改成--count,--count课时寄存器自减操做不会分红三步操做了吧。结果一样会出现相同的值。这个验证你们自行测试。

(2)不一样的线程(不一样的线程类)引用同一对象做为成员变量

定义两个线程MyThreadA,MyThreadB,自定义类MyNum用于计数

package com.feng.example;

public class MyNum {
	
	int count;

	public int getCount() {
		return count;
	}

	public void setCount(int count) {
		this.count = count;
	}
}
package com.feng.example;

public class MyThreadA extends Thread{

	private MyNum count;
	
	public MyThreadA(MyNum count)
	{
		this.count = count;
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(count.getCount()  >0)
		{
			count.setCount(count.getCount()-1);
			System.out.println(Thread.currentThread().getName()+"====="+count.getCount());
		}
	}

	
}
package com.feng.example;

public class MyThreadB extends Thread{

	private MyNum count;
	
	public MyThreadB(MyNum count)
	{
		this.count = count;
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(count.getCount()  >0)
		{
			count.setCount(count.getCount()-1);
			System.out.println(Thread.currentThread().getName()+"====="+count.getCount());
		}
	}

	
}

测试类:

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		MyNum count = new MyNum();
		count.setCount(5);
		Thread a = new MyThreadA(count);
		Thread b = new MyThreadB(count);

		a.start();
		b.start();
	}

}

经过传递同一个对象给两个线程,这两个线程共用这一个计数器。由于没有同步操做,这个线程执行还会出现线程安全问题。

输出结果:

Thread-1=====3
Thread-0=====3
Thread-1=====2
Thread-0=====1
Thread-1=====0

解决方案:可使用LocalThread解决,也可使用sychronized解决,此处不细讲。

若是此处将计数器简单的使用Integer类型,观察会有什么不一样?为何?

经过以上实验能够得出,不论是同一个class文件产生的多个线程实例仍是多个class文件产生的多个线程实例,只要对同一个对象进行处理就会出现线程安全问题。

3.浅谈web中的Servlet

Servlet是单例的,意思就是说无论多少个请求,若是请求的是同一个Servlet,那么他们都会使用同一个Servlet对象。

若是不慎在Servlet中使用成员变量保存前台传输过来的数据,那么后台数据将会产生错乱(为何?查看共享数据中的第二个例子,将MyNum想象成Servlet,使用count接收前台的数据)。所以在Servlet中都是在doGet或者doPost方法中使用局部变量来接收前台的数据,由于每次调用方法时,都会为这次方法调用开辟空间,方法中的各个局部变量之间没有影响。

所以在Servlet中不多使用成员变量。我将单独列出一个模块讨论多线程和单例之间的关系,这里就不深刻研究了。

用本身的话总结一下:线程安全问题就是指应该成为原子操做的模块没有完整的执行。

相关文章
相关标签/搜索