线程的同步

线程的同步

问题的提出

  • 多个线程的不确定性引起执行结果的不稳定
  • 多个线程对账本的共享,会造成操作的不完整性,会破坏数据。
  • 例如:当你和媳妇同时取同一张卡的钱,就会造成数据安全问题。

线程安全问题,就是对数据的保护问题。???

解决卖票过程中,出现重票和错票问题 !

问题:

  • 卖票过程中,出现重票和错票问题 ==> 出现了线程安全问题。
  • 问题出现原因: 当某个线程操作车票的过程中, 尚未操作完成时,其他线程参与进来,也操作车票(也就是前一个线程进入时,运算还没又结束,并未修改票的数据,所以出现了,后面线程也可以通过判断条件进入代码块,执行购票过程。

解决问题:

  • 如何让一个线程操作时,其他线程不能进入操作。直到当前正在操作线程完成任务,其他才能进入。(就相当于上厕所,你在上厕所,把门锁了,其他人不能上,除非你上完厕所。)

  • 引入同步机制

    • 同步代码块

      • 操作 共享数据(多个线程共同操作的变量)的代码,即为需要被同步的代码

      • 通过 关键字 synchronized

        • synchronized (/*同步监视器, 锁*/) {
          	// 同步代码块,需要同步的代码
          }
          
        • 同步监视器:俗称,。任何一个类的对象,都可以充当锁 (确保锁的唯一性)。

      • 通过同步代码块,解决卖票的线程安全问题

        • package 实现Runnable;
          
          public class WindowStore {
              public static void main(String[] args){
                  Window window = new Window();
          
                  new Thread(window).start();
                  new Thread(window).start();
                  new Thread(window).start();
          
              }
          }
          
          class Window implements Runnable{
              private int tickets = 100;
          
              @Override
              public void run() {
                  while (true) {
                      synchronized (this) {
                              if (tickets > 0) {
                                  try {
                                      Thread.sleep(100); // 模拟网络延时
                                  } catch (InterruptedException e) {
                                      e.printStackTrace();
                                  }
                                  System.out.println(Thread.currentThread().getName() + " 购买了第" + tickets-- + "票");
                              } else break;
                          }
                      }
                  }
          
          }
          
          
          
    • 同步方法

      • 如果操作共享数据的代码完整的声明在一个方法中,可以将该方法声明为 同步方法

        • 同步方法的声明

        • public synchronized void xxx() {
          	// 共享数据操作过程。
          }
          
      • 同步方法,解决卖票问题

        • package 线程同步;
          
          public class WindowTicket {
              public static void main(String[] args){
                  Window window = new Window();
          
                  new Thread(window, "窗口1").start();
                  new Thread(window, "窗口2").start();
                  new Thread(window, "窗口3").start();
          
              }
          }
          
          class Window implements Runnable{
              private int tickets = 100;
          
              @Override
              public void run() {
                  while (tickets > 0) {
                      show();
                  }
              }
          
              public synchronized void show() { // 同步方法中,默认的同步监视器 就是 this
                  if (tickets > 0) {
                      try {
                          Thread.sleep(100);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                      System.out.println(Thread.currentThread().getName() + " 购买了第" + tickets-- + "票");
                  }
              }
          }
          

线程死锁问题

死锁

  • 不同的线程分别占用对方需要的同步资源部放弃,都在等对方放弃自己需要的同步资源,就形成了线程的死锁。(也就像支付密码一人设置一般,谁也不高数谁,都在等对方说出口,者就是一个死锁问题)。
  • 出现死锁问题后,不会出现异常,不会出现提示,只是所有线程都处于阻塞状态,无法继续

解决方法

  • 专门的算法、原则
  • 尽量减少同步资源的定义
  • 尽量避免嵌套同步

Lock

  • JDK5.0 开始, java提供了更强大的同步机制 —— 通过显示定义同步锁对象来实现同步。同步锁使用Lock对象充当。
  • java.util.concurrent.locks.Lock 接口是控制多个线程对共享资源进行访问的工具。 锁提供了对共享资源的独占,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应该先获得Lock对象。
  • ReentrantLock 类实现了 Lock, 他们拥有于 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentranLock, 可以显示的加锁,和解锁。

使用方式

  1. 先声明一个锁

    private ReetrantLock lock = new ReentrantLcok();
    
  2. 用try 将同步代码块,包裹起来。

    // 加锁
    try {
        lock.lock(); // 加锁
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "票号为" + ticket --);
        } else break;
    }
    
  3. 在 finally 中解锁

    finally {
    	lock.unlock();
    }
    

使用实例,卖票问题

import java.util.concurrent.locks.ReentrantLock;

public class LockTest {
    public static void main(String[] args) {
        Window window = new Window();

        Thread t1 = new Thread(window);
        Thread t2 = new Thread(window);
        Thread t3 = new Thread(window);

        t1.start();
        t2.start();
        t3.start();
    }

}

class Window implements Runnable {
    private int ticket = 100;
    private ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        while(true) {

          try {
              lock.lock(); // 加锁
              if (ticket > 0) {
                  try {
                      Thread.sleep(100);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  System.out.println(Thread.currentThread().getName() + "票号为" + ticket --);
              } else {
                  break;
              }
          } finally {
              lock.unlock(); // 解锁
          }
        }
    }
}

面试题目

  1. synchronized 和 Lock 的异同?

    同:二者都可以解决线程安全问题

    异:synchronized 机制在执行完相应的同步代码以后,自动释放同步监视器

    ​ Lock需要手动启动同步(lock()), 同时结束同步也需要手动的实现 (unlock())

  2. 如何解决线程安全问题?有几种方式?

    答:线程安全问题,解决的重点,是如何让单个共享资源,一次只能被一个线程处理。

    ​ 解决方式有,两种:1. 同步代码块; 2. 同步方法。

练习题

  • 两个人同时向同一账户存1000, 存三次,解决线程安全问题?

  • public class StoreMoney {
        public static void main(String[] args) {
            Account acct = new Account(0);
            Customer c1 = new Customer(acct);
            Customer c2 = new Customer(acct);
    
    
            c1.setName("甲");
            c2.setName("乙");
    
            c1.start();
            c2.start();
    
        }
    }
    
    
    class Account {
        private double balance;
        public Account(double balance) {
            this.balance = balance;
        }
    
        public synchronized    void deposit(double amt) {
            if (amt > 0) {
                balance += amt;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "存钱成功。余额为:" + balance);
            }
        }
    }
    
    class Customer extends Thread {
        private Account acct;
        public Customer(Account acct) {
            this.acct = acct;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 3; ++ i) {
                acct.deposit(1000);
            }
        }
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值