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的线程安全问题,分析与之类似。