线程安全问题及解决方法

在此之前,先让我们以一个简单的案例认识什么是线程安全问题
1.1线程安全问题
需求: 使用多线程模拟4个窗口共同卖100张电影票

public class MyRunnable implements Runnable {

    // 共享变量--被4条线程共享
    int tickets = 100;

    @Override
    public void run() {
        // 线程的任务代码----->卖票
        // 循环卖票,直到没有票为止
        while (true) {
            // 条件判断
            if (tickets < 1){
                break;
            }
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":线程正在出售第" + tickets + "张票");
            tickets--;
        }
    }
}
public class Test {
    public static void main(String[] args) {
        // 创建任务对象
        MyRunnable mr = new MyRunnable();
        // 创建并启动4条线程
        new Thread(mr,"窗口1").start();
        new Thread(mr,"窗口2").start();
        new Thread(mr,"窗口3").start();
        new Thread(mr,"窗口4").start();

        /*
            执行后出现的问题:
                1.卖了重复票     eg:多个窗口共同卖了第100张票
                2.卖了不存在的票 eg:多个窗口分别卖了第0,-1,-2张票
                3.遗漏了票      eg: 第99,98,97张票没有出售
         */
    }
}
  • 原因: 线程的调度是抢占式
    当某条线程在执行卖票的代码的时候,被其他线程干扰了,导致程序运行结果受影响

  • 解决:
    -当某条线程在执行卖票的代码的时候,不要被其他线程干扰了---->加锁
    -synchronized—>同步代码块,同步方法
    -Lock锁

1.2 synchronized

  • synchronized关键字:表示“同步”的。它可以对“多行代码”进行“同步”——将多行代码当成是一个完整的整体,一个线程如果进入到这个代码块中,会全部执行完毕,执行结束后,其它线程才会执行。这样可以保证这多行的代码作为完整的整体,被一个线程完整的执行完毕。

  • synchronized被称为“重量级的锁”方式,也是“悲观锁”——效率比较低。

  • synchronized有几种使用方式:
    a).同步代码块

    b).同步方法【常用】

当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。

要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制(synchronized)来解决。

1.3 同步代码块
概述:使用synchronized关键字修饰的代码块就是同步代码块,表示只对这个区块的资源实行互斥访问

格式:

synchronized(锁对象){
    // 代码块
}

锁对象:
语法的角度: 锁对象可以是任意类的对象
同步的角度: 多条线程想要实现同步,那么这多条线程使用的锁对象要一致(相同)

解决卖票案例问题:

public class MyRunnable implements Runnable {

    // 共享变量--被4条线程共享
    int tickets = 100;

    @Override
    public void run() {
        // 线程的任务代码----->卖票
        // 循环卖票,直到没有票为止
        while (true) {
            // 加锁
            synchronized (this){
                // 条件判断
                if (tickets < 1) {
                    break;
                }
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() +
                        ":线程正在出售第" + tickets + "张票");
                tickets--;
            }
            // 释放锁
        }
    }
}


public class Test {
    public static void main(String[] args) {
        // 创建任务对象
        MyRunnable mr = new MyRunnable();
        // 创建并启动4条线程
        new Thread(mr,"窗口1").start();
        new Thread(mr,"窗口2").start();
        new Thread(mr,"窗口3").start();
        new Thread(mr,"窗口4").start();

    }
}

1.4 同步方法
概述:使用synchronized关键字修饰方法就是同步方法,表示整个方法的资源实行互斥访问

格式:

修饰符 synchronized 返回值类型 方法名(形参列表){
    方法体
}

锁对象:
非静态同步方法锁对象是: this
静态同步方法锁对象是: 该方法所在类的字节码对象—>类名.class

解决卖票案例的问题:

public class MyRunnable implements Runnable {

    // 共享变量--被4条线程共享
    int tickets = 100;

    @Override
    public void run() {
        // 线程的任务代码----->卖票
        // 循环卖票,直到没有票为止
        while (true) {
            // 条件判断
            if (sellTickets()) break;
        }
    }

    // 非静态同步方法
    private synchronized boolean sellTickets() {
        if (tickets < 1){
            return true;
        }
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() +
                ":线程正在出售第" + tickets + "张票");
        tickets--;
        return false;
    }
}


public class Test {
    public static void main(String[] args) {
        // 创建任务对象
        MyRunnable mr = new MyRunnable();
        // 创建并启动4条线程
        new Thread(mr,"窗口1").start();
        new Thread(mr,"窗口2").start();
        new Thread(mr,"窗口3").start();
        new Thread(mr,"窗口4").start();

    }
}

1.5 Lock锁
概述: 也是一种锁,他比synchronized更加强大,更加面向对象
使用:
-Lock是一个接口,Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作
-使用Lock就需要使用Lock接口的实现类ReentrantLock
-常用方法:
-void lock();加锁
-void unlock();释放锁

解决卖票案例问题:

public class MyRunnable implements Runnable {

    // 共享变量--被4条线程共享
    int tickets = 100;

    // 创建Lock对象
    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        // 线程的任务代码----->卖票
        // 循环卖票,直到没有票为止
        while (true) {
            // 条件判断
            // 加锁
            lock.lock();
            if (tickets < 1){
                // 释放锁
                lock.unlock();
                break;
            }
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() +
                    ":线程正在出售第" + tickets + "张票");
            tickets--;
            // 释放锁
            lock.unlock();
        }
    }
}

public class Test {
    public static void main(String[] args) {
        // 创建任务对象
        MyRunnable mr = new MyRunnable();
        // 创建并启动4条线程
        new Thread(mr,"窗口1").start();
        new Thread(mr,"窗口2").start();
        new Thread(mr,"窗口3").start();
        new Thread(mr,"窗口4").start();

        /*
            注意:
                1.线程锁对象没有释放,线程就不会销毁
                2.子线程没有销毁,主线程就不能结束\销毁
         */

    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夏目不听话

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值