当前位置 : 主页 > 编程语言 > java >

彻底搞懂Java多线程(二)

来源:互联网 收集:自由互联 发布时间:2021-08-21
目录 Java中的锁 1.synchronized锁(jvm层的解决方案,也叫监视器锁) 2.手动锁Lock synchronized锁 synchronized使用场景 1.使用synchronized来修饰代码块(可以给任意的对象进行加锁操作) 2.使用s
目录
  • Java中的锁
    • 1.synchronized锁(jvm层的解决方案,也叫监视器锁)
    • 2.手动锁Lock
    • synchronized锁
    • synchronized使用场景
      • 1.使用synchronized来修饰代码块(可以给任意的对象进行加锁操作)
      • 2.使用synchronized来修饰静态方法(对当前的类进行加锁的操作)
      • 3.使用synchronized来修饰普通的方法(对当前类的实例来进行加锁)
    • synchronized注意事项
      • 1.加锁的时候一定要使用同一把锁对象
    • Lock锁使用的注意事项
      • 公平锁、非公平锁
        • synchronzied 和 Lock 的区别
        • 死锁
        • 造成死锁的四个条件
        • 死锁的解决方案
    • 线程间通信
      • wait/notify机制的原理
        • notifyAll
          • wait()和sleep()的区别
            • 线程使用wait()的时候它就会释放掉锁。
              • sleep(0) 和wait(0)的区别:
                • LockSupport park()/unpark()
                • 总结

                  Java中的锁

                  Java中的加锁操作有两种:

                  1.synchronized锁(jvm层的解决方案,也叫监视器锁)

                  在操作系统的层面使用的是互斥锁(mutex lock)

                  在Java中放在了对象头中。

                  2.手动锁Lock

                  操作锁的流程

                  • 1.尝试获取锁
                  • 2.使用锁
                  • 3.释放锁

                  synchronized锁

                  package ThreadDeom;
                  /**
                   * user:ypc;
                   * date:2021-06-12;
                   * time: 14:12;
                   */
                  class Counter2 {
                      private static volatile int count = 0;
                      public void increase() {
                          for (int i = 0; i < 10000; i++) {
                              count++;
                          }
                      }
                      public void decrease() {
                          for (int i = 0; i < 10000; i++) {
                              count--;
                          }
                      }
                      public int getCount() {
                          return count;
                      }
                  }
                  
                  public class ThreadDemo19 {
                      public static void main(String[] args) throws InterruptedException {
                          //声明锁对象,任何的对象都可以作为锁
                          Object lock = new Object();
                          Counter2 counter2 = new Counter2();
                          Thread thread1 = new Thread(new Runnable() {
                              @Override
                              public void run() {
                                  //使用锁
                                  synchronized (lock) {
                                      counter2.decrease();
                                  }
                              }
                          });
                          Thread thread2 = new Thread(() -> {
                              synchronized (lock) {
                                  counter2.increase();
                              }
                          });
                          thread1.start();
                          thread2.start();
                          thread1.join();
                          thread2.join();
                          System.out.println(counter2.getCount());
                      }
                  }
                  

                  结果是:

                  在这里插入图片描述

                  synchronized使用场景

                  1.使用synchronized来修饰代码块(可以给任意的对象进行加锁操作)

                  public class ThreadDemo19 {
                      public static void main(String[] args) throws InterruptedException {
                          //声明锁对象,任何的对象都可以作为锁
                          Object lock = new Object();
                          Counter2 counter2 = new Counter2();
                          Thread thread1 = new Thread(new Runnable() {
                              @Override
                              public void run() {
                                  //使用锁
                                  synchronized (lock) {
                                      counter2.decrease();
                                  }
                              }
                          });
                          Thread thread2 = new Thread(() -> {
                              synchronized (lock) {
                                  counter2.increase();
                              }
                          });
                          thread1.start();
                          thread2.start();
                          thread1.join();
                          thread2.join();
                          System.out.println(counter2.getCount());
                      }
                  }
                  

                  在这里插入图片描述

                  2.使用synchronized来修饰静态方法(对当前的类进行加锁的操作)

                  package ThreadDeom;
                  /**
                   * user:ypc;
                   * date:2021-06-12;
                   * time: 14:02;
                   */
                  class Counter1 {
                      private static volatile int count = 0;
                      public void increase() {
                          for (int i = 0; i < 10000; i++) {
                              count++;
                          }
                      }
                      public void decrease() {
                          for (int i = 0; i < 10000; i++) {
                              count--;
                          }
                      }
                      public int getCount() {
                          return count;
                      }
                  }
                  
                  public class ThreadDemo18 {
                      public static void main(String[] args) throws InterruptedException {
                          Counter1 counter1 = new Counter1();
                          Thread thread1 = new Thread(new Runnable() {
                              @Override
                              public void run() {
                                  counter1.decrease();
                              }
                          });
                          Thread thread2 = new Thread(() -> {
                              counter1.increase();
                          });
                          thread1.start();
                          thread2.start();
                          thread1.join();
                          thread2.join();
                          System.out.println(counter1.getCount());
                      }
                  }
                  

                  在这里插入图片描述

                  3.使用synchronized来修饰普通的方法(对当前类的实例来进行加锁)

                  package ThreadDeom;
                  /**
                   * user:ypc;
                   * date:2021-06-12;
                   * time: 14:12;
                   */
                  public class ThreadDemo20 {
                      private static int num = 0;
                      private static final int maxSize = 100000;
                      public static void main(String[] args) throws InterruptedException {
                          ThreadDemo20 threadDemo20 = new ThreadDemo20();
                          Thread thread1 = new Thread(new Runnable() {
                              @Override
                              public void run() {
                                  threadDemo20.increase();
                              }
                          });
                          Thread thread2 = new Thread(new Runnable() {
                              @Override
                              public void run() {
                                 threadDemo20. decrease();
                              }
                          });
                          thread1.start();
                          thread2.start();
                          thread1.join();
                          thread2.join();
                          System.out.println(num);
                      }
                      //给静态的方法进行加锁,被加的锁是当前的对象。
                  //    public synchronized static void increase(){
                      //给普通的方法进行加锁的操作
                      public synchronized void increase() {
                          for (int i = 0; i < maxSize; i++) {
                              num++;
                          }
                      }
                      //    public synchronized static void decrease(){
                      public synchronized void decrease() {
                          for (int i = 0; i < maxSize; i++) {
                              num--;
                          }
                      }
                  }
                  

                  在这里插入图片描述

                  synchronized注意事项

                  1.加锁的时候一定要使用同一把锁对象

                  Lock类的使用

                  也叫手动锁

                  package ThreadDeom;
                  import java.util.concurrent.locks.Lock;
                  import java.util.concurrent.locks.ReentrantLock;
                  /**
                   * user:ypc;
                   * date:2021-06-12;
                   * time: 18:32;
                   */
                  public class ThreadDemo22 {
                      private static int number = 0;
                      private static final int maxSize = 100000;
                      public static void main(String[] args) {
                          //创建lock锁对象,lock是接口,不能实列化
                          Lock lock = new ReentrantLock();
                  
                          Thread thread1 = new Thread(() -> {
                              for (int i = 0; i < maxSize; i++) {
                                  lock.lock();
                                  try {
                                      number++;
                                  } finally {
                                      lock.unlock();
                                  }
                              }
                          });
                  
                          Thread thread2 = new Thread(() -> {
                              for (int i = 0; i < maxSize; i++) {
                                  lock.lock();
                                  try {
                                      number--;
                                  } finally {
                                      lock.unlock();
                                  }
                              }
                          });
                          System.out.println(number);
                      }
                  }
                  

                  在这里插入图片描述

                  Lock锁使用的注意事项

                  lock()操作一定要放在try外面

                  如果放在try的里面:

                  1.try中抛出了异常,还没有加锁就释放了finally中的锁的操作了

                  2.如果放在了try,没加锁就释放了锁,就会抛出异常,就会将业务代码中的异常吞噬掉👇如果一定要放的话,将lock()放在try的第一行。

                  package ThreadDeom;
                  import java.util.concurrent.locks.Lock;
                  import java.util.concurrent.locks.ReentrantLock;
                  /**
                   * user:ypc;
                   * date:2021-06-12;
                   * time: 18:49;
                   */
                  public class ThreadDemo23 {
                      public static void main(String[] args) {
                          Lock lock = new ReentrantLock();
                          try{
                              System.out.println(1/0);
                              lock.lock();
                          } finally {
                              lock.unlock();
                          }
                      }
                  }
                  

                  在这里插入图片描述

                  公平锁、非公平锁

                  公平锁的调度:

                  一个线程释放锁。

                  主动唤醒“需要得到锁”的队列来得到锁。

                  非公平锁

                  当一个线程释放锁之后,另一个线程刚好执行到获取锁的代码就可以直接获取锁。

                  Java中的所有锁默认都是非公平锁。

                  非公平锁的性能更高。

                  ReentrantLock可以设置非公平锁。

                  公平锁

                  package ThreadDeom;
                  import java.util.concurrent.locks.ReentrantLock;
                  /**
                   * user:ypc;
                   * date:2021-06-12;
                   * time: 19:22;
                   */
                  public class ThreadDemo24 {
                      public static void main(String[] args) throws InterruptedException {
                          ReentrantLock reentrantLock = new ReentrantLock();
                          Thread thread1 = new Thread(() -> {
                              for (int i = 0; i < 100; i++) {
                                  reentrantLock.lock();
                                  try {
                                      System.out.println("thread1");
                                  } finally {
                                      reentrantLock.unlock();
                                  }
                              }
                          });
                          Thread thread2 = new Thread(() -> {
                              for (int i = 0; i < 100; i++) {
                                  reentrantLock.lock();
                                  try {
                                      System.out.println("thread2");
                                  } finally {
                                      reentrantLock.unlock();
                                  }
                              }
                          });
                          Thread.sleep(100);
                          thread1.start();
                          thread2.start();
                      }
                  }
                  

                  打印的结果是无序的

                  在这里插入图片描述

                  如果设置为公平锁:👇

                  在这里插入图片描述

                  在这里插入图片描述

                  thread1和thread2 交替输出

                  synchronzied 和 Lock 的区别

                  1.synchronzied可以自动的进行加锁和释放锁,而Lock需要手动的加锁、释放锁。

                  2.Lock是Java层面的锁实现,而synchronzied 是JVM层面锁的实现

                  3.synchronzed 即可以修饰代码块,又可以修饰普通方法和静态的方法,而Lock 只能修饰代码块

                  4.synchronized 实现的是 非公平的锁,而Lock 可以实现公平锁。

                  5.lock的灵活性更高

                  死锁

                  在两个或两个以上的线程运行中,因为资源的抢占而造成线程一直等待的问题。看👇:

                  package ThreadDeom;
                  /**
                   * user:ypc;
                   * date:2021-06-12;
                   * time: 19:48;
                   */
                  public class ThreadDemo25 {
                      public static void main(String[] args) throws InterruptedException {
                          Object lockA = new Object();
                          Object lockB = new Object();
                  
                          Thread thread1 = new Thread(() -> {
                              synchronized (lockA) {
                                  System.out.println(Thread.currentThread().getName() + "获取到lockA");
                                  //让线程2获取lockB
                                  try {
                                      Thread.sleep(1000);
                                  } catch (InterruptedException e) {
                                      e.printStackTrace();
                                  }
                                  synchronized (lockB) {
                                      System.out.println(Thread.currentThread().getName() + "获取到lockB");
                                  }
                              }
                          });
                          Thread thread2 = new Thread(new Runnable() {
                              @Override
                              public void run() {
                                  //线程2获取资源B
                                  synchronized (lockB) {
                                      System.out.println(Thread.currentThread().getName() + "获取到lockB");
                                      //让线程1先获取到锁lockA
                                      try {
                                          Thread.sleep(1000);
                                      } catch (InterruptedException e) {
                                          e.printStackTrace();
                                      }
                                      synchronized (lockA) {
                                          System.out.println(Thread.currentThread().getName() + "获取到lockA");
                                      }
                                  }
                              }
                          });
                          thread1.start();
                          thread2.start();
                      }
                  }
                  

                  这就造成了死锁

                  在这里插入图片描述

                  造成死锁的四个条件

                  1.互斥条件:

                  当资源被一个线程拥有之后,就不能被其它的线程拥有了

                  2.拥有请求条件:

                  当一个线程拥有了一个资源之后,又试图请求另一个资源。

                  3.不可剥夺条件:

                  当一个线程拥有了一个资源之后,如果不是这个线程主动的释放资源,其他线程就不能拥有这个线程。

                  4.环路等待条件:

                  两个或两个以上的线程拥有了资源之后,试图获取对方的资源的时候形成了一个环路。

                  死锁的解决方案

                  解决请求拥有和环路等待。

                  最有效的解决方案就是控制加锁的顺序。

                  package ThreadDeom;
                  /**
                   * user:ypc;
                   * date:2021-06-12;
                   * time: 20:25;
                   */
                  public class ThreadDemo26 {
                      public static void main(String[] args) throws InterruptedException {
                          Object lockA = new Object();
                          Object lockB = new Object();
                  
                          Thread thread1 = new Thread(() -> {
                              synchronized (lockA) {
                                  System.out.println(Thread.currentThread().getName() + "获取到lockA");
                                  //让线程2获取lockB
                                  try {
                                      Thread.sleep(1000);
                                  } catch (InterruptedException e) {
                                      e.printStackTrace();
                                  }
                                  synchronized (lockB) {
                                      System.out.println(Thread.currentThread().getName() + "获取到lockB");
                                  }
                              }
                          });
                          Thread thread2 = new Thread(new Runnable() {
                              @Override
                              public void run() {
                                  synchronized (lockA) {
                                      System.out.println(Thread.currentThread().getName() + "获取到lockA");
                                      try {
                                          Thread.sleep(1000);
                                      } catch (InterruptedException e) {
                                          e.printStackTrace();
                                      }
                                      synchronized (lockB) {
                                          System.out.println(Thread.currentThread().getName() + "获取到lockB");
                                      }
                                  }
                              }
                          });
                          thread1.start();
                          thread2.start();
                      }
                  }
                  

                  在这里插入图片描述

                  线程间通信

                  线程之间的通讯是指在一个线程中的操作可以影响另一个线程。

                  wait/notify机制的原理

                  拥有相同锁的线程之间才能使用wait/notify机制。

                  wait()是Object()的方法,它的作用是是当前执行wait()方法的线程等待,在wati()所在的代码出停止执行,并释放锁,直到接到通知或者被中断为止。即在调用wait()的方法之前,线程必需先获取到对象级别的锁,也就是只能在同步方法或者同步块中使用wait()方法。

                  如果在使用wait()方法之前线程没有获得相应的锁,那么程序在执行时就会抛出异常。

                  notify()方法要在同步方法或者同步块中执行,即在调用notify()方法之前,线程必需要先获取到锁对象。如果线程没有持有锁对象的话,那么也会抛出异常。该方法用来通知可能在等待该锁的其它线程,如果有多个线程,那么则按照执行wait()方法的顺序来对处于wait()方法的线程发出通知,并使该线程重新获取锁。执行notify()方法之后,当前线程不会马上释放锁,处于wait()状态的线程也不会立马得到这个对象锁。而是要等notify的synchronized同步区域执行完成之后才会释放锁,处于wait()状态的线程才会得到锁对象。

                  总结:wait()方法用于让线程停止运行,而notify()方法用于通知暂停的线程继续运行。

                  在使用wait()或者notify()方法之前没有对象锁,就会报异常👇:

                          lock.notify();
                  

                  在这里插入图片描述

                  正确的使用之后

                  package ThreadDeom;
                  /**
                   * user:ypc;
                   * date:2021-06-12;
                   * time: 21:11;
                   */
                  public class ThreadDemo27 {
                      //设置锁对象
                      private static Object lock = new Object();
                      public static void main(String[] args) throws InterruptedException {
                          Thread thread = new Thread(new Runnable() {
                              @Override
                              public void run() {
                                  synchronized (lock) {
                                      System.out.println("在wait()");
                                      try {
                                          lock.wait();
                                      } catch (InterruptedException e) {
                                          e.printStackTrace();
                                      }
                                      System.out.println("被notify()唤醒之后");
                                  }
                              }
                          });
                          thread.start();
                          Thread.sleep(1000);
                          synchronized (lock) {
                              lock.notify();
                          }
                      }
                  }
                  

                  在这里插入图片描述

                  注意:使用wait()方法的时候一定要和线程的锁对象是一个锁。

                  notifyAll

                  在多线程的情况下使用notify()方法只可以唤醒一个线程👇

                  在这里插入图片描述

                  package ThreadDeom;
                  /**
                   * user:ypc;
                   * date:2021-06-13;
                   * time: 8:06;
                   */
                  public class ThreadDemo28 {
                      //设置锁对象
                      private static Object lock = new Object();
                      public static void main(String[] args) throws InterruptedException {
                          Thread thread1 = new Thread(new Runnable() {
                              @Override
                              public void run() {
                                  synchronized (lock) {
                                      System.out.println("thread1在wait()");
                                      try {
                                          lock.wait();
                                      } catch (InterruptedException e) {
                                          e.printStackTrace();
                                      }
                                      System.out.println("thread1被notify()唤醒之后");
                                  }
                              }
                          });
                  
                          Thread thread2 = new Thread(() -> {
                              synchronized (lock) {
                                  System.out.println("thread2在wait()");
                                  try {
                                      lock.wait();
                                  } catch (InterruptedException e) {
                                      e.printStackTrace();
                                  }
                                  System.out.println("thread2被notify()唤醒之后");
                              }
                          });
                          Thread thread3 = new Thread(new Runnable() {
                              @Override
                              public void run() {
                                  synchronized (lock) {
                                      System.out.println("thread3在wait()");
                                      try {
                                          lock.wait();
                                      } catch (InterruptedException e) {
                                          e.printStackTrace();
                                      }
                                      System.out.println("thread3被notify()唤醒之后");
                                  }
                              }
                          });
                  
                          thread1.start();
                          thread2.start();
                          thread3.start();
                          Thread.sleep(1000);
                          synchronized (lock) {
                              System.out.println("主线程调用notify()之后");
                              lock.notify();
                          }
                      }
                  }
                  

                  那么如果使用notifyAll()方法呢?

                  在这里插入图片描述

                  可以看到所有的线程都被唤醒了

                  在这里插入图片描述

                  那么使用notify()唤醒的线程有没有什么顺序呢?

                  使用notify()唤醒线程的顺序是正序、倒序、还是随机的,这取决与JVM的具体实现,并不是所有的JVM在执行notify()时都是按照wait()的执行顺序进行唤醒的,也不是所有的notidyAll()都是按照wait()方法的倒序进行唤醒的,这取决于JVM的具体实现。

                  wait()和notify()不能唤醒指定的线程。

                  wait()和sleep()的区别

                  也可以让wait()等待指定的时间,如果超过给定的时间,wait()不会无限期的等待下去.

                  在这里插入图片描述

                  没有被notify()唤醒,过了1000毫秒之后会自动停止。

                  在这里插入图片描述

                  wait()在不传入任何参数的时候,线程会进入waiting 的状态,而在wait()中加入一个大于0的参数的时候,线程会进入time_wating的状态。

                  sleep()和wait()的区别 : 线程在sleep()的时候是不会释放锁的,而执行wait()的时候它就会释放锁。👇:

                  package ThreadDeom;
                  import jdk.nashorn.internal.ir.Block;
                  /**
                   * user:ypc;
                   * date:2021-06-13;
                   * time: 8:45;
                   */
                  public class ThreadDemo29 {
                      private static Object lock = new Object();
                      public static void main(String[] args) throws InterruptedException {
                          Thread thread = new Thread(new Runnable() {
                              @Override
                              public void run() {
                                  synchronized (lock) {
                                      try {
                                          System.out.println("thread获取到了锁");
                                          //如果sleep释放锁的话,会在thread获取到了锁和thread释放了锁之间打印
                                          Thread.sleep(3000);
                                      } catch (InterruptedException e) {
                                          e.printStackTrace();
                                      }
                  
                                  }
                                  System.out.println("thread释放了锁");
                              }
                          });
                          thread.start();
                          //让thread 先获取到锁
                          Thread.sleep(1000);
                          synchronized (lock) {
                              System.out.println("主线程获取到了锁");
                          }
                      }
                  }
                  

                  在这里插入图片描述

                  可以看到线程在sleep()的时候,线程是不会释放锁的。再来看看wait()方法👇:

                  在这里插入图片描述

                  在这里插入图片描述

                  线程使用wait()的时候它就会释放掉锁。

                  1.wait()和sleep()都是让线程进行休眠的

                  2.wait()和sleep()方法都有可能在执行的过程接收到线程终止的通知

                  3.wait()必须和synchronzied一起使用,而sleep()不用。

                  4.wait()会释放锁,而sleep()不会释放锁。

                  5.wait()时Object的方法,而sleep()时Thread的方法。

                  6.默认情况下,wait()不传任何的参数的情况下,wait()会进入waiting的状态,如果传递了参数,wait()会进入time_waiting的状态。而sleep()进入的是time_waiting的状态。

                  sleep(0) 和wait(0)的区别:

                  1.sleep(0)表示0毫秒之后继续执行,而wait(0)表示线程会一直休眠下去wait(0)和wait()是一样的,wait()的源码就是调用了wait(0)方法。

                  2.sleep(0)表示重新出发一次CPU的竞争。

                  为什么wait()会释放锁,而sleep()不会释放锁?

                  sleep()需要传递一个最大的等待时间,也就是说sleep()是可控的,而wait()是不可以传递参数的,从设计的层面来说,如果让wait()一直持有所得话,那么线程就可能一直阻塞。

                  为什么wait()是Object的方法,而sleep()是线程的方法?

                  wait()需要操作锁,而锁是属于对象级别的,所有的锁都是放在对象头中的,它不是线程级别的,一个线程可以有多把的锁,为了灵活,就将wait()放在Object中了。

                  LockSupport park()/unpark()

                  使用LockSupport可以解决wait()/notify()随机唤醒的问题。

                  package ThreadDeom;
                  import java.util.concurrent.locks.LockSupport;
                  /**
                   * user:ypc;
                   * date:2021-06-13;
                   * time: 9:36;
                   */
                  public class ThreadDemo30 {
                      public static void main(String[] args) {
                          Thread thread1 = new Thread(new Runnable() {
                              @Override
                              public void run() {
                                  //让线程休眠
                                  LockSupport.park();
                                  System.out.println("unPark()了thread1");
                              }
                          });
                          Thread thread2 = new Thread(() -> {
                              LockSupport.park();
                              System.out.println("unPark()了thread2");
                          });
                  
                          Thread thread3 = new Thread() {
                              @Override
                              public void run() {
                                  LockSupport.park();
                                  System.out.println("unPark()了thread3");
                              }
                          };
                  
                          thread1.start();
                          thread2.start();
                          thread3.start();
                  
                          LockSupport.unpark(thread1);
                          LockSupport.unpark(thread2);
                  
                      }
                  }
                  

                  在这里插入图片描述

                  总结

                  本篇文章就到这里了,希望可以帮助到你,也希望您能够多多关注自由互联的更多内容!

                  网友评论