zoukankan      html  css  js  c++  java
  • 线程的同步

    线程的同步

    问题的提出

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

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

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

    问题:

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

    解决问题:

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

    • 引入同步机制

      • 同步代码块

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

        • 通过 关键字 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);
              }
          }
      }
      
    追求吾之所爱
  • 相关阅读:
    keras使用AutoEncoder对mnist数据降维
    maven插件生成可执行jar包
    python基于opencv实现人脸定位
    使用Jieba提取文章的关键词
    汉语词性对照表
    SQL优化
    keras基于卷积网络手写数字识别
    统计学习
    log4j和slf4j的区别
    log4j配置详解(非常详细)
  • 原文地址:https://www.cnblogs.com/rstz/p/14390975.html
Copyright © 2011-2022 走看看