线程安全问题以及解决方法

什么是线程安全问题?

我们通过代码来了解一下,下面是一个模拟卖票的案例,
首先我们先定义一个票Ticked类实现Runnable接口:

class Ticket implements Runnable{

    int ticked = 50;//一共50张票

    @Override
    public void run() {
        while (true) {
            if(ticked > 0) {
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticked--;
                System.out.println("卖了一张票,还剩" + ticked + "张票");
            }else{
                System.out.println("票已售罄!");
                break;
            }
        }
    }
}

然后定义一个测试类:模拟三个窗口卖票

public class Test1 {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        Thread t1 = new Thread(ticket,"窗口一");
        Thread t2 = new Thread(ticket,"窗口一");
        Thread t3 = new Thread(ticket,"窗口一");
        t1.start();
        t2.start();
        t3.start();
    }
}

运行一下看效果:
线程安全问题以及解决方法_第1张图片
可以看到我们的余票有两次剩余两张,还出现了余票为负数的情况,这就是线程安全问题,那么为什么会出现线程安全问题呢?

这是因为:多线程操作共享数据时,导致共享数据出错。

那么怎么解决这个问题呢?
有三种方法:

  • 同步代码块
  • 同步方法
  • Lock锁

我们先看第一种:使用同步代码块
只需要将操作共享数据的代码放在 synchronized 里面就可以了

class Ticket implements Runnable{

    int ticked = 50;

    @Override
    public void run() {
        while (true) {
            synchronized (""){//这里小括号里面放的是 锁 对象,任何对象都可以是锁,但这个对象要是唯一的
                if(ticked > 0) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticked--;
                    System.out.println("卖了一张票,还剩" + ticked + "张票");
                }else{
                    System.out.println("票已售罄!");
                    break;
                }
            }
        }
    }
}

同步锁是谁?
对于非 static 方法,同步锁就是 this
对于 static 方法,我们使用当前方法所在类的字节码对象(类名.class)

再来看一下运行结果:完全没有问题
线程安全问题以及解决方法_第2张图片
第二种:将操作共享数据的代码抽取出来放到一个方法里面就可以了

class Ticket implements Runnable{

    int ticked = 50;

    @Override
    public void run() {
        while (true) {
            sellTicked();
        }
    }

    private synchronized void sellTicked() {
        if(ticked > 0) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticked--;
            System.out.println("卖了一张票,还剩" + ticked + "张票");
        }
    }
}

运行看效果:是不是也完全没问题
线程安全问题以及解决方法_第3张图片
第三种:使用Lock锁,用法其实与第一种差不多,我们看代码:

提示:这里讲一下Lock的两个方法:

  • public void lock() :加同步锁。
  • public void unlock() :释放同步锁。
class Ticket implements Runnable{

    int ticked = 50;

    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            lock.lock();
                if(ticked > 0) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticked--;
                    System.out.println("卖了一张票,还剩" + ticked + "张票");
                }else{
                    System.out.println("票已售罄!");
                    break;
                }
            lock.unlock();
        }
    }
}

运行看效果:完全没问题
线程安全问题以及解决方法_第4张图片

你可能感兴趣的:(javaSE)