1.卖票问题中出现的错误及分析:
class Ticket extends Thread { static int ticket = 100; //让四个对象共享100张票(一般不用静态,生命周期太长) public void run() { while(true) { if(ticket>0) { try { Thread.sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+" "+ticket--); } } } } class HelloWorld { public static void main(String[] args) { Ticket t1 = new Ticket(); Ticket t2 = new Ticket(); Ticket t3 = new Ticket(); Ticket t4 = new Ticket(); t1.start(); t2.start(); t3.start(); t4.start(); } }
现象一出现负票,程序输出部分:
Thread-1 1 Thread-0 0 Thread-2 -1 Thread-3 -2
程序执行中出现了负票,分析:代码中开启了四个线程,1线程执行的时候还有1张票,1线程判断if(ticket>0)后还没有来得及减一和打印,线程0抢到了CPU的执行权,此时票数仍然为1,通过了if(tick>0)语句,线程1还没有来得及减一和打印,线程2抢到了CPU的执行权,此时票数仍然为1,线程3抢到了CPU的执行权,此时票数仍然为1,所有线程3也通过f(tick>0)语句向下执行。线程0,1,2,3都逐个执行减一打印操作,就会出现代码中的现象。解决办法是:加上锁。
现象二出现了重复的票数,程序部分输出:
Thread-1 28 Thread-3 27 Thread-0 28 Thread-2 28 Thread-1 26 Thread-2 23分析:假设这样一种情形,主内存中的票数为29,线程一和线程二从主内存中获取29放到两者的工作内存,假设线程一从自己的工作内存取出29送到操作数栈中进行减一操作( 注意加一后还没有来得及写回自己的工作内存),此时线程二获取CPU的执行权,也从自己的工作内存中取出29送到操作数栈中进行减一,然后线程二写回自己的工作内存,此时主内存中28刷新为28,同时线程一的工作内存中的29失效,线程一从住内存读取28到自己的工作内存后,线程一的操作数栈中减一的动作才完成,将28写回自己的工作内存,主内存和线程而的工作内存刷新。最终主内存和线程一,二的工作内存中的内容都为28,此时线程一和线程二都打印输出相同的数28。
2.验证volatile非原子性的典型代码
public class VolatileTest { public static volatile int race = 0; public static void increase() { race++; } private static final int THREADS_COUNT = 20; public static void main(String[] args) { Thread[] threads = new Thread[THREADS_COUNT]; for (int i = 0; i < THREADS_COUNT; i++) { threads[i] = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10000; i++) { increase(); } } }); threads[i].start(); } // 等待所有累加线程都结束 while (Thread.activeCount() > 1) Thread.yield(); System.out.println(race); } }
现象:最后的输出结果是一个小于200000的数,原理跟上面出现重复数字的原因相同,分析(自己的理解):执行race++语句是由多条指令完成,先从线程的工作内存取出内容到操作树栈,在操作数栈中执行加一,再从操作数栈取出数据到工作内存几个步骤。假设这样一种情形,主内存中的数为20,线程一和线程二从主内存中获取20放到两者的工作内存,假设线程一从自己的工作内存取出20送到操作数栈中进行加一操作(注意加一后还没有来得及写回自己的工作内存),此时线程二获取CPU的执行权,也从自己的工作内存中取出20送到操作数栈中进行加一,然后线程二写回自己的工作内存,此时主内存中20刷新为21,同时线程一的工作内存中的20失效,线程一从住内存读取21到自己的工作内存后,线程一的操作数栈中加一的动作完成,将21写回自己的工作内存,主内存和线程而的工作内存刷新。最终主内存和线程一,二的工作内存中的内容都为21,进行了两次加一操作,本应为22,但因为加一操作不是原子性的导致少了一。
与之类似的还有ArrayList的线程安全问题和HashMap的线程安全问题,分析与之类似。