并发中几个常出现的问题

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

猜你喜欢

转载自blog.csdn.net/chenkaibsw/article/details/80295213