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);
              }
          }
      }
      
    追求吾之所爱
  • 相关阅读:
    Truck History(poj 1789)
    Highways poj 2485
    117. Populating Next Right Pointers in Each Node II
    116. Populating Next Right Pointers in Each Node
    115. Distinct Subsequences
    114. Flatten Binary Tree to Linked List
    113. Path Sum II
    109. Convert Sorted List to Binary Search Tree
    106. Construct Binary Tree from Inorder and Postorder Traversal
    105. Construct Binary Tree from Preorder and Inorder Traversal
  • 原文地址:https://www.cnblogs.com/rstz/p/14390975.html
Copyright © 2011-2022 走看看