多线程安全问题

来源:互联网 发布:山东直销软件开发 编辑:程序博客网 时间:2024/05/18 20:49
---------------------- android培训、java培训、期待与您交流! ----------------------

对于一个多线程的程序,在其并发的执行过程中可能会因为资源的竞争而出现安全问题,比如对于一个售票系统,当几个窗口并发的去运行时可能会出现与实际不符合的现象,如下面的卖票程序:

class Ticket implements Runnable
{
         private int tick = 100;
         public void run()
         {
                 while (true)
                 {
                          if (tick>0)
                          {                                           System.out.println(currentThread().getName()+"sale:"+tick--);
                          }
                 }
         }
}
class ThreadDemo
{
         public static void main(String[] args) 
         {        
                 Ticket t = new Ticket();
                 Thread t1 = new Thread (t);//创建一个线程,并将ticket对象传递给thread的构造函数
                 Thread t2 = new Thread (t);
                 Thread t3 = new Thread (t);
                 Thread t4 = new Thread (t);
 
                 t1.start();
                 t2.start();
                 t3.start();
                 t4.start();               
         }
}

在上面的程序执行中,可能会出现这样的情况:当tick = 1时,四个线程就有可能出现下面的情况:

0号线程判断后,进入循环内部后阻塞,cpu切换到其他程序,此时tick=1;

1号线程判断后,进入循环内部后阻塞,cpu切换到其他程序,此时tick仍然等于1;

2号线程判断后,进入循环内部后阻塞,cpu切换到其他程序,此时tick还是等于1;

3号线程判断后,进入循环内部后阻塞,cpu切换到其他程序,此时tick继续保持为1的状态。

当4个线程都被阻塞后,cpu可能会切换至0号线程(因为cpu总是会切换至线程池前面的等待线程),运行后自然打印出1号票并将tick置为0,而后续的等待线程依次被唤醒,执行后就会打印出-1号-2号-3号票,这显然是与现实生活中的实际情况是不相符的。

问题原因:

为什么会出现这样的问题呢?分析代码后很容易就找出了原因所在,当每个线程在对某一共享数据操作时用到了多条语句时,就会出现只执行了多条语句的部分语句后就被阻塞的情况,而就在这时其他线程又参与进来执行,从而导致了共享数据的错误,发生了与现实不符合的情况。

解决的方法:

如果我们让有多条语句的线程在执行时不被其他线程“所打扰”,就能实现某一线程对共享数据的单独操作,待其操作完成后,再让其他线程进入并执行就不会出现上述的情况了,那么我们如何用代码实现这一思想呢?对此,java提供了专业的解决办法,这就是同步代码块。其格式为:

       Synchronized(对象)

{

       //需要执行的代码(运行时共享的代码)

}

上面的卖票程序操作共享数据的代码部分可以做如下的修改:

public void run()
{
         While(true)
         {
                 Object obj = new Object();//
                 Synchronized(obj)
                 {
                          if (tick>0)
                                                                              System.out.println(currentThread().getName()+"sale:"+tick--);
                 }
         }
}
 
同步代码块的对象就如同锁,持有锁的线程可以在同步代码块中执行,没有持有锁的线程即便是获得了cpu的执行权,也执行不下去,因为他并没有锁。在使用同步代码块时,必须要有两个或两个以上的线程,必须是多个线程使用同一个锁。
通过同步代码块的加入就能解决安全问题,但同时也有了一个弊端,那就是多个线程需要判断锁,较为消耗资源。
 
如何在一个多线程的代码中来找可能出现问题的代码呢?基于此,我们应按以下三步来做:
1,  明确哪些代码是多线程运行的代码。
2,  明确哪些是共享数据
3,  明确多线程运行代码中哪些语句是操作共享数据的。
明确了上面的3个问题,就知道哪些代码应置于同步代码块中,也即是用同步代码块来封装共享数据的代码,这样就能解决多线程的安全问题。同样,函数也是封装代码的,如果我们使某一函数具有同步性,那也可以解决安全问题,如上面的问题用同步函数来解决的简化代码如下:
 
public Synchronized void run()
{
         if (tick>0)                                                           System.out.println(currentThread().getName()+"sale:"+tick--);
}
不同与同步代码块,同步函数使用时是不需要对象的,它所使用的锁是this,也即当某一线程进入时就会拿到本线程对应的锁。 
---------------------- android培训、java培训、期待与您交流! ----------------------