多线程的安全性问题

来源:互联网 发布:php删除数组指定元素 编辑:程序博客网 时间:2024/04/30 11:18

上一节中,虽然我们已经完成了卖票程序,但是其实隐藏了一个非常严重的安全问题,我们看代码这段代码:

class Ticket implements Runnable{    private  int tick=100;    public void run(){        while (true){             if(tick>0){   System.out.println(Thread.currentThread().getName()+"....sale"+tick--);            }        }    }}

当票剩余一张时,在我们的if(tick>0)的时候,我们的0线程进入,这时候0线程进入暂时状态,1线程进入,因为此时tick还未变化, 如果这个时候1线程也进入暂时状态。这个时候就会出现卖出0张票和-1张票的情况。

我们可以模拟下这个情况:

class Ticket implements Runnable{    private  int tick=100;    public void run(){        while (true){             if(tick>0){            try{                 Thread.sleep(100);                   }catch(Exception e){                }            }           System.out.println(Thread.currentThread().getName()+"....sale"+tick--);            }        }    }}

此时我们运行发现:
这里写图片描述

多线程的运行出了安全问题。
当多个线程在操作同一个共享数据时, 一个线程对多余语句只执行了一部分,还没执行完,另一个线程参与进来执行,导致共享数据的错误。

解决办法: 对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
java对于多线的安全问题提供了专业的解决方式, 就是同步代码块。

synchronized(对象){    需要同步的代码块}

改正后的代码

class Ticket implements Runnable {    private int tick = 100;    Object o=new Object(); //锁    public void run() {        while (true) {            synchronized (o) {                if (tick > 0) {                    try {                        Thread.sleep(100);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    System.out.println(Thread.currentThread().getName() + "....sale" + tick--);                }            }        }    }}

原理,锁对象具有一个标志位,当线程进来时,将标志位置成0,其他线程进不来,出去后,标志位重新置成1,其他线程就可以进来了。
对象如同锁,持有锁的线程可以在同步中执行,没有持有锁的线程即使获取了cup的执行权,也进不去, 因为没有获取锁。

同步的前提:
1.必须要有两个或者两个以上的线程。
2.必须是多个线程使用同一个锁。
必须保证同步中只有一个线程在运行。
好处:解决了多线程的安全问题;
弊端:多个线程需要判断锁,较为消耗资源。

同步函数:
我们可以让函数具有同步的功能,只需要在定义函数的时候加上synchronized,此时该函数就具备同步代码块的功能,线程安全。

public synchronized void add (int n){    sum=sum+n;}

同步函数用的是哪一个锁呢?
函数需要被对象调用,那么函数都有一个所属对象引用,就是this。所以同步函数使用的锁是this。