java基础-多线程(6)

80 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天

同步块和锁

同步块

  • 同步块: synchronized (Obj)f)
  • Obj称之为同步监视器
  • Obj 可以是任何对象,但是推荐使用共享资源作为同步监视器
  • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class [反射中讲解]
  • 同步监视器的执行过程
  1. 第一个线程访问,锁定同步监视器,执行其中代码,
  2. 第二个线程访问,发现同步监视器被锁定,无法访问
  3. 第一个线程访问完毕,解锁同步监视器
  4. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
 //线程安全的集合 同步块
 public class Demo29_SafeList {
     public static void main(String[] args) {
         List<String> list = new ArrayList<String>();
         for (int i = 0; i < 1000; i++) {
             new Thread(() -> {
                 synchronized (list) {
                     list.add(Thread.currentThread().getName());
                 }
             }).start();
         }
         try {
             Thread.sleep(300);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println(list.size());
     }
 }
 ​

image-20221004234052479

Lock(锁)

◆从JDK 5.0开始, Java提供了更强大的线程同步机制--通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当

java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。

锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象

ReentrantLock类实现了 Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。

死锁避免方法

产生死锁的四个必要条件:

  1. 互斥条件:一个资源每次只能被一个进程使用。
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
  5. 上面列出了死锁的四个必要条件,我们只要想办法破其中的任意一个或多个条件就可以避免死锁发生

实现:

 //测试Lock锁
 public class Demo32_ThreadLock {
     public static void main(String[] args) {
         TestLock testLock = new TestLock();
         new Thread(testLock).start();
         new Thread(testLock).start();
         new Thread(testLock).start();
     }
 }
 ​
 class TestLock implements Runnable {
     int tickerNums = 10;
     //定义Lock锁
     private final ReentrantLock lock = new ReentrantLock();
 ​
     @Override
     public void run() {
         while (true) {
             //加锁
             try {
                 lock.lock();
                 if (tickerNums <= 0) {
                     break;
                 }
                 try {
                     Thread.sleep(1000);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
                 System.out.println(tickerNums--);
             } catch (Exception e) {
                 e.printStackTrace();
             } finally {
                 //解锁
                 lock.unlock();
             }
         }
     }
 }
 ​

synchroized和lock对比

synchronized 与 Lock 的对比

  • Lock是显式锁(手动开启和关闭锁,别忘记关闭锁) synchronized是隐式锁,出了作用域自动释放
  • Lock只有代码块锁, synchronized有代码块锁和方法锁
  • 使用Lock锁, JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
  • 优先使用顺序:
  • Lock > 同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方法体之外)

\