多线程——线程同步与死锁

多线程的安全性问题

多线程的安全性问题其实就是数据不一致的问题。类似取钱过程。银行卡中有100块,A正在用卡取出50块(取钱过程比较慢,还没有取完,账户上的余额显示还没有修改)。此时B也正好在用存折想取出100块。A,B如果都成功了,银行不就亏本了?这种数据不一致的问题银行不允许出现
怎么解决呢?

  1. 可以规定在取钱过程中只允许一个人操作(加锁)。但是会存在问题:如果这个人待了1h,其他人都不能操作,效率低。
    (见上机练习

  2. 无锁机制–比较交换 CAS(Compare and Swap)。里面设置了三个值,VEN。

    V表示准备要被更新的变量 实际值
    E表示我们提供的 期望的值 期望值
    N表示新值 ,准备更新V的值 改变值

    每次当E == V时,才能发生改变操作,将N写入V中。
    如果 E != V,我们要准备一个新的E去跟V比较,成功后才能更新V。

使用同步解决多线程的安全问题

同步的前提

  1. 必须是两个及以上的线程使用同一资源
  2. 必须保证同步中只能有一个线程在运行

卖票案例中,无论是,都存在数据不一致的问题。解决方法都是线程同步(加锁)

例子在GitHub-Day07-threadticket包中。多线程——线程与进程,线程的实现方式文中也有相关描述。

实现方式
  1. 同步代码块
    Synchronized(共享资源,共享对象,需要是object的子类) {具体执行的代码块}
    ()中的obj称为同步监视器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public void run() {
    for (int i = 0; i < 100; i++) {
    try {
    Thread.sleep(200);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    synchronized (this) { // this 表示当前对象
    if (ticket > 0) {
    System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "张票");
    }
    }
    }
    }
  2. 同步方法
    将核心代码逻辑定义成一个方法,使用synchronized关键字修饰
    不需要自己指定锁对象(同步监视器),因为同步方法的监视器是this,也就是该对象本身

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    private int ticket = 5; // 一共5张票
    @Override
    public void run() {
    for (int i = 0; i < 100; i++) {
    try {
    Thread.sleep(200);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    /** this的意思是sale()已经属于TicketRunnable3这个对象了,而不归于整个类了
    * this代表的就是synchronized的锁的对象
    */
    this.sale();
    }
    }
    public synchronized void sale() {
    if (ticket > 0) {
    System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "张票");
    }
    }

    在这里插入图片描述

死锁

  • 同步可以保证资源共享操作的正确性,但过多同步可能产生死锁
  • 死锁一般情况下表示互相等待