线程安全问题当多个线程操作读/写同一份数据时可能会出现线程安全问题进程的内存图如图所示在代码运行时每一个线程并不会对堆内存中的变量本身进行操作而是先复制一个副本放在本地变量表中加载到自己的工作内存中随后对这个副本进行操作操作完毕后再将这个副本的内容赋给堆内存中的对象本体。出现线程安全问题的原因CPU在进行操作时并不是一个线程完全执行完在进行下一个而是为了提高CPU的计算效率防止CPU将大部分时间浪费在等待上采用时间片轮转的方法即每个线程执行一小段时间就换下一个的方式来计算。在这种情况下执行count的操作时线程1和线程2会对堆内存中的数据不停的进行修改而且会不停的覆盖对方的计算结果导致计算结果不准确引发线程安全问题。代码模拟如下packagecom.nwu.by0204_ThreadSafe;classThreadDemo2extendsThread{Overridepublicvoidrun(){for(inti0;i10000;i){ThreadDemo1.count;}}}publicclassThreadDemo1extendsThread{staticintcount0;Overridepublicvoidrun(){for(inti0;i10000;i){count;}}}classMain{publicstaticvoidmain(String[]args){ThreadDemo1threadDemo1newThreadDemo1();ThreadDemo2threadDemo2newThreadDemo2();threadDemo1.start();threadDemo2.start();try{Thread.sleep(1000);}catch(InterruptedExceptione){thrownewRuntimeException(e);}System.out.println(ThreadDemo1.count);}}代码中用到的static关键字static意思是静态的静态变量只在类加载的时候获取一次内存空间因此代码中的任何对象的修改都会被保留。同时静态变量的访问需要用类名来访问。代码中使用sleep方法的解释线程的进行需要时间在调用线程对象的start方法后程序会立即打印输出语句此时的线程还处于就绪状态因此此时打印出来的count就是0。解决方案使用sleep方法让程序等待一秒在打印count的值运行结果如下结果并非是预期的20000这就是线程的安全问题。线程安全问题的解决方案原子性一个或多个操作要么在执行时不会被打断要么就不执行原子操作不会被线程调度所打断的操作可见性当多线程访问同一变量时一个线程修改该变量的值另一个线程能立刻看见修改的值为了保证可见性可以给变量加一个修饰词volatile加上这个关键字后这个变量就具备了可见性为了保证线程安全Java中有内置锁synchronized 同步锁synchronized(){ }参数 必须是一个当前所有线程都可以访问的唯一对象当前线程在执行代码块中的内容时其他所有线程必须等待直到代码块中的内容执行完毕等代码块执行完成后会解锁其他线程继续与该线程进行竞争代码修改方案给两个线程的run方法里要执行的代码都加上一把synchronized同步锁即可同时还需要在new一个静态的对象让两个线程都可以访问packagecom.nwu.by0204_ThreadSafe;classThreadDemo2extendsThread{Overridepublicvoidrun(){synchronized(ThreadDemo1.obj){for(inti0;i10000;i){ThreadDemo1.count;}}}}publicclassThreadDemo1extendsThread{staticintcount0;staticObjectobjnewObject();Overridepublicvoidrun(){synchronized(obj){for(inti0;i10000;i){count;}}}}classMain{publicstaticvoidmain(String[]args){ThreadDemo1threadDemo1newThreadDemo1();ThreadDemo2threadDemo2newThreadDemo2();threadDemo1.start();threadDemo2.start();try{Thread.sleep(1000);}catch(InterruptedExceptione){thrownewRuntimeException(e);}System.out.println(ThreadDemo1.count);}}执行结果代码的最终优化在源代码中为了保证线程执行完后在进行打印输出语句采用的方法是调用Thread类中的sleep方法让程序停一秒等待线程执行。更规范的做法是采用join()方法join方法是Thread类中的实例方法它的作用是让其他线程都要等待这个线程执行完毕后在进行下一步操作需要在start方法后执行。采用join方法而不是sleep方法的好处sleep方法是我们人为的猜出一个等待方法规定程序需要等待多少ms而join方法是系统自己判断的只要该线程执行完毕就可以开始下一个线程比认为规定更加精准、规范代码修改后packagecom.nwu.by0204_ThreadSafe;classThreadDemo2extendsThread{Overridepublicvoidrun(){synchronized(ThreadDemo1.obj){for(inti0;i100000;i){ThreadDemo1.count;}}}}publicclassThreadDemo1extendsThread{staticintcount0;staticObjectobjnewObject();Overridepublicvoidrun(){synchronized(obj){for(inti0;i100000;i){count;}}}}classMain{publicstaticvoidmain(String[]args){ThreadDemo1threadDemo1newThreadDemo1();ThreadDemo2threadDemo2newThreadDemo2();threadDemo1.start();threadDemo2.start();try{threadDemo1.join();threadDemo2.join();}catch(InterruptedExceptione){thrownewRuntimeException(e);}System.out.println(ThreadDemo1.count);}}在修改后的代码中只有把threadDemo1和threadDemo2两个线程都执行完才会进行下一步两个线程是并行等待的也就是执行输出语句。比人为规定sleep时间更精准、更规范运行截图