线程安全问题的解决方法

182 阅读3分钟

线程安全问题的解决方法

1. 问题描述

  • 当某个线程的操作还未结束的时候,其他线程就参与了操作,导致线程出现问题。

2. 解决方法:同步代码块

  • 方法格式:

    synchronized (同步监视器(锁)) {
        // 需要被同步的代码
        // 等于操作共享数据的代码
        
    }
    
    • 说明:需要同步的代码块 == 操作共享数据的代码。

    • 说明:同步监视器 == 锁。任何一个类的对象都可以充当锁。多线程共用同一把锁。

      ​ 如果是实现 Runnable 接口的方法来创造线程一般使用当前对象 this 作为同步监视器。

      ​ 如果是继承 Thread 类的方法创建线程一般使用当前类本身 类名.class 来作为同步监视器。

3. 解决方法:同步方法

  • 方法格式:

    1. Runnable 接口实现创造线程的同步方法

      private synchronized 方法类型 方法名() {
          // 需要被同步的代码
          // 等于操作共享数据的代码
      }
      
    2. Thread 类被继承创造线程的同步方法

      private static synchronized 方法类型 方法名() {
          // 需要被同步的代码
          // 等于操作共享数据的代码
      }
      
  • 说明:

    1. 通过将需要操作共享数据的代码方法另外一个方法中,并且用 synchronized 关键字修饰。
    2. 如果是 Thread 类的同步方法,需要用 static 关键字修饰
    3. 同步方法会自动使用同步监视器。Runnable 用的是当前对象 this; Thread 用的是当前类本身 类名.class

4. 解决方法:ReentrantLock 方法

  • 方法格式:

    1. Runnable 接口实现创造线程的lock() 方法

      class lockTest implements Runnable {
          // 1. 实例化ReentrantLock
          private ReentrantLock lock = new ReentrantLock(true);
      
          @Override
          public void run() {
              while (true) {
                  try {
                      // 2. 调用上锁方法 lock()
                      lock.lock();
                      
                      // 需要被同步的代码
                      // 操作共享数据的代码
                      
                  } finally {
                      // 3. 调用解锁方法 unlock()
                      lock.unlock();
                  }
      
              }
      
          }
      }
      
    2. Thread 类被继承创造线程的lock() 方法

      class lockTest implements Runnable {
          // 1. 实例化ReentrantLock
          private static ReentrantLock lock = new ReentrantLock(true);
      
          @Override
          public void run() {
              while (true) {
                  try {
                      // 2. 调用上锁方法 lock()
                      lock.lock();
      
                      // 需要被同步的代码
                      // 操作共享数据的代码
      
                  } finally {
                      // 3. 调用解锁方法 unlock()
                      lock.unlock();
                  }
              }
          }
      }
      
  • 说明:

    1. 使用过后必须释放锁,需要手动调用解锁方法。

    2. ReentrantLock 方法不会自动解锁。

    3. new ReentrantLock(true) :按照访问顺序执行线程。先进先出。不会出现刚释放CPU控制权就又获取CPU控制权的情况。

      new ReentrantLock(false) :随机获取。可能会出现同一线程不断获取CPU控制权

    4. 使用 try —— finally 结构调用解锁方法是为了防止忘记手动解锁造成死锁。

4. 相同与不同

  • 相同
    1. 都可以解决线程安全问题
  • 不同
    1. Lock 需要手动实现上锁和解锁;synchronized 可以自动实现上锁和解锁功能。
    2. Lock 只有代码块锁;synchronized 又代码块锁和方法锁。
    3. 使用 Lock 锁,JVM 将花费较少的时间来调度线程,性能更好。并且又更好的扩展性。

5.个人重点

  1. 同步代码不可多,同步代码不可少。多了会变慢,使资源不能更好利用。少了会出错,出现线程安全问题。
  2. 宁愿多也不要少。可以慢,不能错。
  3. 尽量使用锁可以是任何类的特点。
  4. 建议优先使用 lock。