经过集中状况来观察成员变量对线程安全的影响:
java
线程类代码以下:
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
在数据共享这一部分分为两个部分来说:测试
线程类改写为: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课时寄存器自减操做不会分红三步操做了吧。结果一样会出现相同的值。这个验证你们自行测试。
定义两个线程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文件产生的多个线程实例,只要对同一个对象进行处理就会出现线程安全问题。
Servlet是单例的,意思就是说无论多少个请求,若是请求的是同一个Servlet,那么他们都会使用同一个Servlet对象。
若是不慎在Servlet中使用成员变量保存前台传输过来的数据,那么后台数据将会产生错乱(为何?查看共享数据中的第二个例子,将MyNum想象成Servlet,使用count接收前台的数据)。所以在Servlet中都是在doGet或者doPost方法中使用局部变量来接收前台的数据,由于每次调用方法时,都会为这次方法调用开辟空间,方法中的各个局部变量之间没有影响。
所以在Servlet中不多使用成员变量。我将单独列出一个模块讨论多线程和单例之间的关系,这里就不深刻研究了。
用本身的话总结一下:线程安全问题就是指应该成为原子操做的模块没有完整的执行。