Java线程安全(卖票案例) 如何解决线程安全(synchronized ,显示锁Lock)

线程安全

经典问题:卖票问题,多个线程一起执行该任务,当余票只有1一张时,三个线程都进入了卖票,这时就会发生错误。导致了余票<0 的问题

public class ThreadDemo1 {
    public static void main(String[] args) throws InterruptedException {
        // 线程安全问题
        Runnable r = new MyRunnable();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        Thread t3 = new Thread(r);
        t1.start();
        t2.start();
        t3.start();
    }
}
class MyRunnable implements Runnable{
    // 票数
    private int ticket = 10;
    @Override
    public void run() {
        // 卖票
        while(ticket > 0){
            System.out.println(Thread.currentThread().getName()+"正在准备卖票");
            try {
                Thread.sleep(1000); //让发生问题的错误几率加大
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticket--;
            System.out.println(Thread.currentThread().getName()+"出票成功,余票:" + ticket);
        }
    }
}

运行结果:卖票出现了错误

Thread-0正在准备卖票
Thread-1正在准备卖票
Thread-2正在准备卖票
Thread-2出票成功,余票:8
Thread-2正在准备卖票
Thread-1出票成功,余票:8
Thread-1正在准备卖票
Thread-0出票成功,余票:8
Thread-0正在准备卖票
Thread-1出票成功,余票:7
Thread-0出票成功,余票:7
Thread-0正在准备卖票
Thread-2出票成功,余票:6
Thread-2正在准备卖票
Thread-1正在准备卖票
Thread-0出票成功,余票:4
Thread-2出票成功,余票:3
Thread-2正在准备卖票
Thread-1出票成功,余票:5
Thread-1正在准备卖票
Thread-0正在准备卖票
Thread-0出票成功,余票:2
Thread-0正在准备卖票
Thread-1出票成功,余票:2
Thread-1正在准备卖票
Thread-2出票成功,余票:2
Thread-2正在准备卖票
Thread-2出票成功,余票:1
Thread-1出票成功,余票:0
Thread-0出票成功,余票:-1

解决方法:

1、同步代码块

使用synchronized 关键字 (隐式锁)

格式:

synchronized (锁对象){ //这里的锁对象必须的同一个锁对象
    // 同步执行的代码块
}
public class ThreadDemo1 {
    public static void main(String[] args) throws InterruptedException {
        // 线程安全问题
        Runnable r = new MyRunnable();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        Thread t3 = new Thread(r);
        t1.start();
        t2.start();
        t3.start();
    }
}
class MyRunnable implements Runnable{
    // 票数
    private int ticket = 10;
    private Object o = new Object();
    @Override
    public void run() {
        // 卖票
        while(true){
            synchronized(o) { // 锁对象 - 任意对象都行,但必须是同一个对象
                // 里边是同步代码块
                if (ticket > 0){
                    System.out.println(Thread.currentThread().getName()+"正在准备卖票");
                    try {
                        Thread.sleep(1000); //让发生问题的错误几率加大
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket--;
                    System.out.println(Thread.currentThread().getName()+"出票成功,余票:" + ticket);

                } else {
                    break;
                }
            }
        }
    }
}

结果:

Thread-0正在准备卖票
Thread-0出票成功,余票:9
Thread-0正在准备卖票
Thread-0出票成功,余票:8
Thread-0正在准备卖票
Thread-0出票成功,余票:7
Thread-2正在准备卖票
Thread-2出票成功,余票:6
Thread-2正在准备卖票
Thread-2出票成功,余票:5
Thread-1正在准备卖票
Thread-1出票成功,余票:4
Thread-1正在准备卖票
Thread-1出票成功,余票:3
Thread-1正在准备卖票
Thread-1出票成功,余票:2
Thread-1正在准备卖票
Thread-1出票成功,余票:1
Thread-1正在准备卖票
Thread-1出票成功,余票:0

2、同步方法

就是在方法加上synchronized

锁对象:方法的this,如果是静态的方法,就是类名.class 如:MyRunnable.class

public class ThreadDemo1 {
    public static void main(String[] args) throws InterruptedException {
        // 线程安全问题
        Runnable r = new MyRunnable();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        Thread t3 = new Thread(r);
        t1.start();
        t2.start();
        t3.start();
    }
}
class MyRunnable implements Runnable{
    // 票数
    private int ticket = 10;
    private Object o = new Object();
    @Override
    public void run() {
        // 卖票
        while(true){
            if (!sale()) break;
        }
    }
    //卖票方法
    public synchronized boolean sale(){
        if (ticket > 0){
            System.out.println(Thread.currentThread().getName()+"正在准备卖票");
            try {
                Thread.sleep(1000); //让发生问题的错误几率加大
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticket--;
            System.out.println(Thread.currentThread().getName()+"出票成功,余票:" + ticket);
            return true;
        } else {
            return false;
        }
    }
}

结果:

Thread-0正在准备卖票
Thread-0出票成功,余票:9
Thread-0正在准备卖票
Thread-0出票成功,余票:8
Thread-0正在准备卖票
Thread-0出票成功,余票:7
Thread-0正在准备卖票
Thread-0出票成功,余票:6
Thread-0正在准备卖票
Thread-0出票成功,余票:5
Thread-0正在准备卖票
Thread-0出票成功,余票:4
Thread-2正在准备卖票
Thread-2出票成功,余票:3
Thread-1正在准备卖票
Thread-1出票成功,余票:2
Thread-1正在准备卖票
Thread-1出票成功,余票:1
Thread-1正在准备卖票
Thread-1出票成功,余票:0

Process finished with exit code 0

3、显示锁

public class ThreadDemo1 {
    public static void main(String[] args) throws InterruptedException {
        // 线程安全问题
        Runnable r = new MyRunnable();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        Thread t3 = new Thread(r);
        t1.start();
        t2.start();
        t3.start();
    }
}
class MyRunnable implements Runnable{
    // 票数
    private int ticket = 10;
    // 显示锁 lock
    private Lock lock = new ReentrantLock();
    @Override
    public void run() {
        // 卖票
        while(true){
            // 上锁
            lock.lock();
            if (ticket > 0){
                System.out.println(Thread.currentThread().getName()+"正在准备卖票");
                try {
                    Thread.sleep(1000); //让发生问题的错误几率加大
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticket--;
                System.out.println(Thread.currentThread().getName()+"出票成功,余票:" + ticket);
            } else {
                break;
            }
            // 解锁
            lock.unlock();
        }
    }
}

显示锁与隐式锁的区别

所谓的显示和隐式就是在使用的时候,使用者要不要手动写代码去获取锁和释放锁的操作。

我们大家都知道,在使用sync关键字的时候,我们使用者根本不用写其他的代码,然后程序就能够获取锁和释放锁了。那是因为当sync代码块执行完成之后,系统会自动的让程序释放占用的锁。Sync是由系统维护的,如果非逻辑问题的话话,是不会出现死锁的。

在使用lock的时候,我们使用者需要手动的获取和释放锁。如果没有释放锁,就有可能导致出现死锁的现象。手动获取锁方法:lock.lock()。释放锁:unlock方法。需要配合tyr/finaly语句块来完成

4、公平锁与非公平锁

公平锁:先来先用

非公平锁:大家一块抢

以上三种都是非公平锁~

公平锁定义:

private Lock lock = new ReentrantLock(true);

指定 true 就是公平锁,

public class ThreadDemo1 {
    public static void main(String[] args) throws InterruptedException {
        // 线程安全问题
        Runnable r = new MyRunnable();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        Thread t3 = new Thread(r);
        t1.start();
        t2.start();
        t3.start();
    }
}
class MyRunnable implements Runnable{
    // 票数
    private int ticket = 10;
    // 显示锁 lock --公平锁
    private Lock lock = new ReentrantLock(true);
    @Override
    public void run() {
        // 卖票
        while(true){
            // 上锁
            lock.lock();
            if (ticket > 0){
                System.out.println(Thread.currentThread().getName()+"正在准备卖票");
                try {
                    Thread.sleep(1000); //让发生问题的错误几率加大
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticket--;
                System.out.println(Thread.currentThread().getName()+"出票成功,余票:" + ticket);
            } else {
                break;
            }
            // 解锁
            lock.unlock();
        }
    }
}

结果:(线程0,线程1,线程2有序执行)

Thread-0正在准备卖票
Thread-0出票成功,余票:9
Thread-1正在准备卖票
Thread-1出票成功,余票:8
Thread-2正在准备卖票
Thread-2出票成功,余票:7
Thread-0正在准备卖票
Thread-0出票成功,余票:6
Thread-1正在准备卖票
Thread-1出票成功,余票:5
Thread-2正在准备卖票
Thread-2出票成功,余票:4
Thread-0正在准备卖票
Thread-0出票成功,余票:3
Thread-1正在准备卖票
Thread-1出票成功,余票:2
Thread-2正在准备卖票
Thread-2出票成功,余票:1
Thread-0正在准备卖票
Thread-0出票成功,余票:0
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值