java多线程:线程同步synchronized(不同步的问题、队列与锁),死锁的产生和解决

0、不同步的问题

并发的线程不安全问题:

多个线程同时操作同一个对象,如果控制不好,就会产生问题,叫做线程不安全。

我们来看三个比较经典的案例来说明线程不安全的问题

0.1 订票问题

例如前面说过的黄牛订票问题,可能出现负数或相同。

线程创建方式&&黄牛订票模拟

0.2 银行取钱

再来看一个取钱的例子:

/*
    模拟一个账户
*/
class Account{
   
    int money;
    String name;
    public Account(int money, String name) {
   
        this.money = money;
        this.name = name;
    }
}
/*
    模拟取款机,方便设置名字,继承Thread而不是实现Runnable
*/
class Drawing extends Thread{
   
    Account account;
    int outMoney;//取出去了多少钱
    int outTotal;//总共取到了多少钱

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

    @Override
    public void run() {
   
        account.money -= outMoney;
        outTotal += outMoney;
        System.out.println(this.getName() + "---账户余额为:" + account.money);
        System.out.println(this.getName() + "---总共取到了:" + outTotal);
    }
}

然后我们写个客户端调用一下,假设两个人同时取钱,操作同一个账户

public class Checkout {
   
    public static void main(String[] args) {
   
        Account account = new Account(200000,"礼金");
        Drawing you = new Drawing(account,8000,"你");
        Drawing wife = new Drawing(account,300000,"你老婆");
        you.start();
        wife.start();
    }
}

运行起来,问题就会出现。

每次的结果都不一样,而且,这样肯定会把钱取成负数,显然这是非法的(嘻嘻),首先逻辑上需要修改,当钱少于 0 了就应该退出,并且不能继续取钱的动作了。按照这个思路,加上一个判断呢?

if (account.money < outMoney){
   
    System.out.println("余额不足");
    return;
}
try {
   
    Thread.sleep(1000);
} catch (InterruptedException e) {
   
    e.printStackTrace();
}

可是即便是这样,发现还是会出现结果为负的情况,无法保证线程安全

0.3 数字递增

还有一个经典的例子,那就是对于直接计算迭代过慢,而转为多线程。

一个数字 num ,开辟一万个线程对他做 ++ 操作,看结果会是多少。

public class AddSum {
   
    private static int num = 0;
    public static void main(String[] args) {
   
        for (int i=0; i<=10000; i++){
   
            new Thread(()->{
   
                num++;
            }).start();
        }
        System.out.println(num);
    }
}

每次运算的结果都不一样,一样的是,结果永远 < 10000 。

或者用给 list 里添加数字来测试:

List<String> list = new ArrayList<>();
for (int i=0; i<10000; i++){
   
    new Thread(()->{
   
        list.add(Thread.currentThread().getName());
    }).start();
}
System.out.println(list.size());

一样的结果。

线程不安全的问题如何解决呢?

一、同步(synchronized)

1.1 问题出现的原因

从前面的介绍里,我们总结出会出现同步问题的情况,也就是并发三要素:多个线程、同时操作、操作同一个对象。另外,操作的特点是:操作类型为修改这个时候会产生并发的问题,线程安全问题。

1.2 解决方案
  1. 确保线程安全,第一就是排队。只要排队,那么不管多少线程,始终一个时间点只会有一个线程在执行,就保证了安全。
    不过排队会有一个问题:怎么直到轮到我了呢,也就是怎么知道排在前面的线程执行完了呢?
  2. 现实生活中,可能会用类似房卡的形式,前一个人把卡交还了,才会有后面的人有机会入住。这就是

利用 队列 + 锁 的方式保证线程安全的方式叫线程同步,就是一种等待机制,多个同时访问此对象的线程进入这个对象的等待池 形成队列,前面的线程使用完毕后,下一个线程再使用。

锁机制最开始在 java 里就是一个关键字 synchronized(同步),属于排他锁,当一个线程获得对象的排他锁,独占资源,其他线程必须等待,使用后释放锁即可。

按照这种思路,可以想象到这种保证安全方式的弊端,也就是早期的 synchronized 存在的问题:

  1. 一个线程持有锁会导致其他所有需要这个锁的线程挂起;
  2. 多线程竞争下,加锁、释放锁导致耗时严重,性能问题
  3. 一个优先级高的线程等待一个优先级低的线程的锁释放,会使得本应该的优先级倒置&#
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值