线程安全问题的三种解决方案

关于线程安全问题:
1.什么是线程安全问题?.为什么会出现线程安全问题?
首先我们需要了解一个概念,临界资源:即多个线程同时访问的资源(共享资源)。
当多个线程同时操作临界资源的时候,就容易出现线程安全问题,线程安全问题只会影响到线程对同一个共享的全局变量的写操作。

》接下来来演示一下出现线程安全问题的案例:窗口售票

public class Ticket implements Runnable{//创建对象继承Runnable接口  作为参数传入Thread对象构造中
    private  int ticket=100;//设定临界资源100张票
    @Override
    public void run() {
        while (true){//死循环
            if(ticket<=0){票数少于等于0结束循环  不再买票
                break;
            }
            try {
                Thread.sleep(10);//休眠只是为了更明显突出多线程安全问题
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"卖出了第"+ticket+"张票");//打印卖票
            ticket--;//每卖出一张票  票数减一
        }
    }
    }
public class TirketDemo {
    public static void main(String[] args) {
        Ticket ticket=new Ticket();//创建票对象
        new Thread(ticket,"窗口一").start();//作为参数传入
        new Thread(ticket,"窗口二").start();
        new Thread(ticket,"窗口三").start();
        new Thread(ticket,"窗口四").start();
    }
}

运行结果:在这里插入图片描述
可以看出来不仅出现重复,还有出现负数票的情况,这是因为在线程运行时候,刚判定结束后,cpu倍另外的线程抢夺而出现的问题,再次醒来之后,票数已经被其他线程修改,该线程仍然继续向下执行减一 就有可能出现负数或者重复。解决方法大致有三种:

一同步代码块

说到同步代码块就要说锁了,一个线程在访问临界资源的时候,如果给这个资源“上一把锁”,这个时候如果其他线程也要访问这个资源,就得在“锁”外面等待。
锁对象可以是任意对象,但是要求必须是同一把锁才行。
加进同步代码块的代码,执行时候其他线程必须等待,待当前进入的线程执行完,释放锁对象之后其他线程才能进去执行,以此类推。
同步中的线程没有执行完毕不会释放锁,同步外的线程没有锁对象,进不去同步

代码实现:加了同步代码块之后就不会出现以上问题

public class Ticket implements Runnable{
    private  int ticket=100;
    @Override
    public void run() {
        while (true){
            synchronized (this) {//在这里加上同步代码块并传入所对象
                if(ticket<=0){
                    break;
                }
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"卖出了第"+ticket+"张票");
                ticket--;
            }
        }
    }

二.同步方法

创建一个方法,该方法必须用synchronized关键字修饰,将出现安全问题的代码放入到同步方法中,再在Runnable实现类重写的run方法中调用该方法即可。
同步方法默认的锁对象是调用该方法的类 也就是this

public class Ticket implements Runnable{
    private  int ticket=100;
    @Override
    public void run() {
        while (true) {
            sellTicket();
        }
    }
    public synchronized  boolean sellTicket(){
//注意:while循环不能加到方法里面  因为这样的话  调用一次方法  一个线程就会把票卖完了  这里我们返回boolean来判定
            if(ticket<=0){//票数少于等于0结束循环  不再买票
               return false;
            }
            try {
                Thread.sleep(10);//休眠只是为了更明显突出多线程安全问题
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"卖出了第"+ticket+"张票");//打印卖票
            ticket--;//每卖出一张票  票数减一
        return true;

    }

静态同步方法:即在方法加静态修饰符,静态方法的锁不能是this,因为this只有在类加载之后创建对象才会有,而静态方法在创建对象之前就已经加载,所以同步静态方法的锁是本类的class

public class Ticket implements Runnable{
    private   static int ticket=100;//静态方法只能访问静态变量
    @Override
    public void run() {
        while (true) {
            sellTicket();
        }
    }
    public synchronized  static boolean sellTicket(){
//注意:while循环不能加到方法里面  因为这样的话  调用一次方法  一个线程就会把票卖完了
            if(ticket<=0){//票数少于等于0结束循环  不再买票
               return false;
            }
            try {
                Thread.sleep(10);//休眠只是为了更明显突出多线程安全问题
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"卖出了第"+ticket+"张票");//打印卖票
            ticket--;//每卖出一张票  票数减一
        return true;

    }

三.Lock锁

从jdk1.5之后加入新的接口 Lock,ReentrantLock是Lock接口的实现类。
通过显式定义同步锁对象来实现同步,同步锁提供了比synchronized代码块更广泛的锁定操
注意:最好将 unlock的操作放到finally块中
通过使用ReentrantLock这个类来进行锁的操作,它实现了Lock接口,使用ReentrantLock可以显式地加锁、释放锁。

lock锁使用步骤:1.在成员位置创建一个Reentrantlock对象,因为lock是一个接口需要实现类才能创建对象。
2.在可能会出现安全问题的代码前调用接口方法中的lock()方法获取锁
3.在可能会出现安全问题的代码后面调用接口方法中的Unlock()方法释放锁
注意:最好将有安全问题的代码放到try…finally{ … }中,finally中用于释放锁。

代码:

public class Ticket implements Runnable{
    private   int ticket=100;

    Lock lock=new ReentrantLock();//创建锁对象
    @Override
    public void run() {
        while (true){
            lock.lock();//获取锁
            try {
                if(ticket<=0){
                    break;
                }
                System.out.println(Thread.currentThread().getName()+"卖出了第"+ticket+"张票");
                ticket--;
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                lock.unlock();//释放锁
            }
        }

    }
  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值