线程安全问题的三种解决方案
发布日期:2022-02-01 14:28:20 浏览次数:34 分类:技术文章

本文共 4440 字,大约阅读时间需要 14 分钟。

关于线程安全问题:

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();//释放锁            }        }    }

转载地址:https://blog.csdn.net/shunshizhen120412/article/details/99185893 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:关于File类中常用方法和知识点
下一篇:IDEA创建配置Tomcat和创建web项目及其打war包

发表评论

最新留言

第一次来,支持一个
[***.219.124.196]2024年03月27日 20时17分52秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章