四、线程同步

本文探讨了在Java中如何处理并发问题,通过线程同步避免资源竞争。介绍了线程同步的概念,展示了不安全的买票、取钱和集合操作案例,并通过synchronized关键字实现线程安全的方法和同步块,确保了并发环境下的数据一致性。
摘要由CSDN通过智能技术生成

一、并发

多个线程操作同一个资源

现实生活中,会遇到“同一个资源,多个人都想使用”的问题,比如:食堂排队打饭,每个人都想吃饭,最天然 的解决办法就是,排队,一个个来。

处理多线程问题时,多个线程访问同一个对象,并且某些对象还想修改这个对象,这时候就需要线程同步。--------> 线程同步其实是一种等待机制,多个需要同事访问的对象的线程进入这个对象的等待池,形成队列,等待前面线程使用完毕,下一个线程再使用。

二、队列 和 锁

三、线程同步

Q:由于同一进程的多个线程共享同一块存储空间,在带来便利的同时,也 带来了访问冲突问题,为了保证数据在方法中操作的正确性,在访问时加入 锁机制 synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,但也会存在以下问题:

一个线程持有锁会导致其他需要此锁的线程被挂起;

在多线程的竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引发性能问题;

如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题。

四、三大不安全案例

1. 不安全买票

//线程不安全 一次有三个人抢票,对ticketNums进行自减操作,所以线程不安全,出问题。
//不安全的买票
public class UnsafeBuyTicket {

    public static void main(String[] args) {
        BuyTicket station = new BuyTicket();

        new Thread(station,"小明").start();
        new Thread(station,"李华").start();
        new Thread(station,"狗蛋").start();
    }


}


class  BuyTicket implements Runnable{

    //票
    private int ticketNums = 10;
    boolean flag = true;//外部停止方式
    @Override
    public void run() {
        //买票
        while (flag){
            try {
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    
    private  void buy() throws InterruptedException {
        //判断是否有票
        if (ticketNums <=0){
            flag = false;
            return;
        }

        //模拟延迟
        Thread.sleep(1000);
        //买票
        System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
    }
}

运行结果:

小明拿到10
狗蛋拿到10
李华拿到9
小明拿到8
李华拿到8
狗蛋拿到8
小明拿到7
狗蛋拿到7
李华拿到7
狗蛋拿到6
李华拿到6
小明拿到6
李华拿到5
小明拿到5
狗蛋拿到4
李华拿到3
狗蛋拿到3
小明拿到3
小明拿到1
李华拿到2
狗蛋拿到2

2. 不安全的取钱

相当于两个人从同一个Account账户中去取钱,线程不安全。

//不安全的取钱
//两个人去银行取钱,账户
public class UnsafeBank {

    public static void main(String[] args) {

        Account account = new Account(100,"结婚基金");

        Drawing you = new Drawing(account, 50, "你");
        Drawing girlfriend = new Drawing(account, 100, "girlfriend");

        you.start();
        girlfriend.start();

    }


}

//账户
class Account{
    int money;//余额
    String name;//卡名

    public Account(int money,String name) {
        this.money = money;
        this.name = name;
    }
}


//银行:模拟取款
class Drawing extends Thread{

    Account account;//账户
    int drawingMoney;//取了多少钱
    int nowMoney;//现在手里有多少钱

    public Drawing(Account account, int drawingMoney, String name) {
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }


    //取钱
    //synchronized 默认锁的是this
    @Override
    public void run() {

        //判断有没有钱
        if (account.money - drawingMoney < 0){
            System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
            return;
        }

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //卡内余额  = 余额 - 你取走的钱
        account.money = account.money - drawingMoney;
        //你手里的钱
        nowMoney = nowMoney + drawingMoney;

        System.out.println(account.name+"余额为:"+account.money);
        //Thread.currentThread().getName() = this.getName()
        System.out.println(this.getName()+"手里的钱"+nowMoney);

//        //锁的量就是变化的量
//        synchronized (account){
//
//        }


        /**
         * 运行结果:
         *
         * 结婚基金余额为:50
         * 结婚基金余额为:-50
         * 分析:一开始Account里有100,
         * //卡内余额  = 余额 - 你取走的钱   50 = 100 - 50
         * account.money = account.money - drawingMoney;
         * //你手里的钱  50 = 0 + 50
         * nowMoney = nowMoney + drawingMoney;
         *
         * 你手里的钱50
         * girlfriend手里的钱100
         *
         * 分析: -50 = 50 - 100
         * //卡内余额  = 余额 - 你取走的钱
         * account.money = account.money - drawingMoney;
         * //你手里的钱
         * nowMoney = nowMoney + drawingMoney;
         */
    }
}

运行结果:

结婚基金余额为:-50
结婚基金余额为:-50
你手里的钱50
girlfriend手里的钱100

3. 线程不安全的集合

1000的不同的i线程调用同一个对象的run()方法

public class UnsafeList {

    public static void main(String[] args) throws InterruptedException {

        ArrayList<String> list = new ArrayList<>();

        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                
               list.add(Thread.currentThread().getName());
                
            }).start();
        }
        Thread.sleep(3000);//休眠主线程,确保主线程执行完毕
        System.out.println(list.size());
    }
}

运行结果:

9999

五、同步方法以及方法块

1. 同步方法

synchronized方法控制“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得该调用方法的锁才能执行,否则线程会阻塞

方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行

 public synchronized void method(int args){}

缺陷:若将一个大的方法声明为synchronized,将会影响效率,通常只需要把对象进行修改的方法纳入就可以,否则锁的太多,浪费资源。

例如:不安全买票(修改buy()方法)

//synchronized 同步方法,锁的是this,也就是BuyTicket对象
    private synchronized   void buy() throws InterruptedException {
        //判断是否有票
        if (ticketNums <=0){
            flag = false;
            return;
        }

        //模拟延迟
        Thread.sleep(1000);
        //买票
        System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
    }

运行结果:

实现了线程安全,不会再三个人操作同一个对象,拿到同一张票了!

//狗蛋、小明、李华都1次1人的拿到

狗蛋拿到10
狗蛋拿到9
狗蛋拿到8
狗蛋拿到7
狗蛋拿到6
李华拿到5
李华拿到4
李华拿到3
小明拿到2
李华拿到1

2. 同步块

同步块:synchronized(Obj){}

Obj:同步监视器:通俗来讲,就是多个线程操作,需要保证线程安全的那个对象。

Obj可以是任何对象,但推荐使用共享资源作为同步监视器

同步监视器的执行过程:

1.第一个线程访问,锁定同步监视器,执行其中那个代码;

2.第二个线程访问,发现同步监视器被锁定,无法访问;

3.第一个线程访问完毕,解锁同步监视器;

4.第二个线程访问,如果发现同步监视器没有锁,然后锁定并访问。

例如:不安全的取钱:修改Drawng(取钱)的run()方法

 //银行:模拟取款
class Drawing extends Thread{

    Account account;//账户
    int drawingMoney;//取了多少钱
    int nowMoney;//现在手里有多少钱

    public Drawing(Account account, int drawingMoney, String name) {
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }


    //取钱
    //synchronized 默认锁的是this
    @Override
    public void run() {

        //锁的量就是变化的量
        synchronized (account){
            //判断有没有钱
            if (account.money - drawingMoney < 0){
                System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
                return;
            }

            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //卡内余额  = 余额 - 你取走的钱
            account.money = account.money - drawingMoney;
            //你手里的钱
            nowMoney = nowMoney + drawingMoney;

            System.out.println(account.name+"余额为:"+account.money);
            //Thread.currentThread().getName() = this.getName()
            System.out.println(this.getName()+"手里的钱"+nowMoney);

        }


 

运行结果:实现了线程安全!You先取50,girlfriend想取100的时候,发现Account的余额为50,不够!

结婚基金余额为:50
你手里的钱50
girlfriend钱不够,取不了

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值