多线程学习Day05

本文详细介绍了JavaReentrantLock的特点,如可中断、超时、公平锁、可重入以及条件变量的使用。同时探讨了与synchronized的区别,并通过示例展示了如何在实际编程中应用这些概念,包括处理线程安全、互斥、同步和死锁等问题。
摘要由CSDN通过智能技术生成

ReentrantLock

相对于 synchronized 它具备如下特点
可中断、可以设置超时时间、可以设置为公平锁(防止线程饥饿)、支持多个条件变量
与 synchronized 一样,都支持可重入

基本语法
// 获取锁
      reentrantLock.lock();
      try {
           // 临界区
        } finally {
       // 释放锁
       reentrantLock.unlock();
}
可重入

可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁,如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住

@Slf4j(topic = "c.TestReentrantLock")
public class TestReentrantLock {
    /*// 获取锁
      reentrantLock.lock();
      try {
           // 临界区
        } finally {
       // 释放锁
       reentrantLock.unlock();
}*/
    private static ReentrantLock lock=new ReentrantLock();
    public static void main(String[] args) {
          lock.lock();
          try {
              log.debug("进入main");
              m1();
          }finally {
              lock.unlock();
          }
    }
    public static void m1(){
        lock.lock();
        try {
            log.debug("进入m1");
            m2();
        }finally {
            lock.unlock();
        }
    }
    public static void m2(){
        lock.lock();
        try {
            log.debug("进入m2");
        }finally {
            lock.unlock();
        }
    }
}
15:12:05 [main] c.TestReentrantLock - 进入main
15:12:05 [main] c.TestReentrantLock - 进入m1
15:12:05 [main] c.TestReentrantLock - 进入m2
可打断
@Slf4j(topic = "c.Test20")
public class Test20 {
    private static ReentrantLock lock=new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
       Thread t1= new Thread(()->{
            try {
                //如果有竞争会进入锁队列,可以用其他线程用interrupt方法打断
                log.debug("尝试获取锁");
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("没有获得锁,返回");
                return;
            }
            try {
                log.debug("获取到锁");
            }finally {
                lock.unlock();
            }
        },"t1");
       lock.lock();
       t1.start();
       Thread.sleep(1000);
       log.debug("打断t1");
       t1.interrupt();

    }
}
15:20:07 [t1] c.Test20 - 尝试获取锁
15:20:08 [main] c.Test20 - 打断t1
15:20:08 [t1] c.Test20 - 没有获得锁,返回
锁超时

lock.trylock()里面可以带参数,来指示它等待锁的时间,没有参数的话就立即失败

@Slf4j(topic = "c.Test20")
public class Test20 {
    private static ReentrantLock lock=new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
       Thread t1= new Thread(()->{
                log.debug("尝试获取锁");
           try {
               if(!lock.tryLock(2, TimeUnit.SECONDS)){
                   log.debug("获取不到锁");
                   return;
               }
           } catch (InterruptedException e) {
               e.printStackTrace();
               log.debug("获取不到锁");
           }
           try {
                log.debug("获取到锁");
            }finally {
                lock.unlock();
            }
        },"t1");
       lock.lock();
       log.debug("获取到锁");
       t1.start();
       Thread.sleep(1000);
       lock.unlock();
       log.debug("释放了锁");
    }
}
15:26:41 [main] c.Test20 - 获取到锁
15:26:41 [t1] c.Test20 - 尝试获取锁
15:26:42 [main] c.Test20 - 释放了锁
15:26:42 [t1] c.Test20 - 获取到锁
锁超时解决哲学家就餐(奈斯)
@Slf4j(topic = "c.PhilosophersEat")
public class PhilosophersEat {
    public static void main(String[] args) {
        Chopstick c1 = new Chopstick("1");
        Chopstick c2 = new Chopstick("2");
        Chopstick c3 = new Chopstick("3");
        Chopstick c4 = new Chopstick("4");
        Chopstick c5 = new Chopstick("5");
        new Philosopher("苏格拉底",c1, c2).start();
        new Philosopher("柏拉图", c2, c3).start();
        new Philosopher("亚里士多德", c3, c4).start();
        new Philosopher("赫拉克利特", c4, c5).start();
        new Philosopher("阿基米德", c5, c1).start();
    }
}
class Chopstick extends ReentrantLock {
    String name;
    public Chopstick(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "筷子{" + name + '}';
    }
}
@Slf4j(topic = "c.Philosopher")
class Philosopher extends Thread {
    Chopstick left;
    Chopstick right;
    public Philosopher(String name, Chopstick left, Chopstick right) {
        super(name);
        this.left = left;
        this.right = right;
    }
    private void eat() throws InterruptedException {
        log.debug("eating...");
        Thread.sleep(1000);
    }
    @Override
    public void run() {
        while (true) {
            // 获得左手筷子
           if(left.tryLock()) {
               try {
                   // 获得右手筷子
                   if (right.tryLock()) {
                       try {
                           eat();
                       } catch (InterruptedException e) {
                           throw new RuntimeException(e);
                       } finally {
                           right.unlock();
                       }
                   }
               } finally {
                   //如果获取右手筷子的时候失败了,会放下左手筷子
                   left.unlock();
               }
           }
        }
    }
}
公平锁(先入先得)

ReentrantLock默认时不公平的,但是可以修改构造方法变成公平锁

公平锁一般没有必要,会降低并发度

条件变量

synchronized 中也有条件变量,就是我们讲原理时那个 waitSet 休息室,当条件不满足时进入 waitSet 等待
ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比
synchronized 是那些不满足条件的线程都在一间休息室等消息而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒

使用要点:
await 前需要获得锁
await 执行后,会释放锁,进入 conditionObject 等待
await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁
竞争 lock 锁成功后,从 await 后继续执行

之前那个送烟送外卖得进一步例子

@Slf4j(topic = "c.Test21")
public class Test21 {
    static final Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;
    static ReentrantLock ROOM=new ReentrantLock();
    static Condition waitCigaretteSet=ROOM.newCondition();
    static Condition waitTakeoutSet=ROOM.newCondition();
        public static void main(String[] args) throws InterruptedException {
            new Thread(() -> {
                ROOM.lock();
                try {
                    log.debug("有烟没?[{}]",hasCigarette);
                    while (!hasCigarette){
                        log.debug("没有烟,先歇会儿");
                        waitCigaretteSet.await();
                    }
                    log.debug("有烟吗?[{}]",hasCigarette);
                    if(hasCigarette){
                        log.debug("可以开始干活了");
                    }else{
                        log.debug("没干成活");
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                } finally {
                    ROOM.unlock();
                }
            }, "小南").start();
            new Thread(() -> {
                ROOM.lock();
                try {

                    log.debug("外卖送到没?[{}]", hasTakeout);
                    while (!hasTakeout) {
                        log.debug("没外卖,先歇会!");
                        try {
                            waitTakeoutSet.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    log.debug("外卖送到没?[{}]", hasTakeout);
                    if (hasTakeout) {
                        log.debug("可以开始干活了");
                    } else {
                        log.debug("没干成活...");
                    }
                }finally {
                    ROOM.unlock();
                }

            }, "小女").start();
            sleep(1000);
            new Thread(() -> {
                ROOM.lock();
                try {
                    log.debug("烟送到了");
                    hasCigarette=true;
                    waitCigaretteSet.signal();
                }finally {
                    ROOM.unlock();
                }
            }, "送烟的").start();
            new Thread(() -> {
                ROOM.lock();
                try {
                    log.debug("外卖送到了");
                    hasTakeout=true;
                    waitTakeoutSet.signal();
                }finally {
                    ROOM.unlock();
                }
            }, "送外卖的").start();
        }
}
15:58:29 [小南] c.Test21 - 有烟没?[false]
15:58:29 [小南] c.Test21 - 没有烟,先歇会儿
15:58:29 [小女] c.Test21 - 外卖送到没?[false]
15:58:29 [小女] c.Test21 - 没外卖,先歇会!
15:58:30 [送烟的] c.Test21 - 烟送到了
15:58:30 [送外卖的] c.Test21 - 外卖送到了
15:58:30 [小南] c.Test21 - 有烟吗?[true]
15:58:30 [小南] c.Test21 - 可以开始干活了
15:58:30 [小女] c.Test21 - 外卖送到没?[true]
15:58:30 [小女] c.Test21 - 可以开始干活了

同步模式之顺序控制

1.固定顺序

比如保证t2先运行再运行t1

wait-notify法
@Slf4j(topic = "c.Test22")
public class Test22 {
    static final Object lock=new Object();
    //表示t2是否运行过
    static boolean t2runned=false;
    public static void main(String[] args) {
       Thread t1= new Thread(()->{
           synchronized (lock){
               while(!t2runned){
                   try {
                       lock.wait();
                   } catch (InterruptedException e) {
                       throw new RuntimeException(e);
                   }
               }
               log.debug("1");
           }
        },"t1");
       Thread t2= new Thread(()->{
           synchronized (lock){
                log.debug("2");
                t2runned=true;
                lock.notify();
           }
        },"t2");
       t1.start();
       t2.start();
    }
}
ReentrantLock中的await-signal法,大同小异纯当练手
@Slf4j(topic = "c.Test22")
public class Test22 {
    static ReentrantLock lock=new ReentrantLock();
    //表示t2是否运行过
    static boolean t2runned=false;
    static Condition condition=lock.newCondition();
    public static void main(String[] args) {
       Thread t1= new Thread(()->{
           lock.lock();
           try {
               while(!t2runned){
                   condition.await();
               }
           } catch (InterruptedException e) {
               throw new RuntimeException(e);
           } finally {
               lock.unlock();
           }
           log.debug("1");
        },"t1");
       Thread t2= new Thread(()->{
           lock.lock();
           try {
               log.debug("2");
               t2runned=true;
               condition.signal();
           }finally {
               lock.unlock();
           }
        },"t2");
       t1.start();
       t2.start();
    }
}
park-unpark法,这个写起来要简单一些
@Slf4j(topic = "c.Test23")
public class Test23 {
    public static void main(String[] args) {
       Thread t1= new Thread(()->{
            LockSupport.park();
            log.debug("1");
        },"t1");
        t1.start();
        new Thread(()->{
            log.debug("2");
            LockSupport.unpark(t1);
        },"t2").start();
    }
}
2.交替输出

线程 1 输出 a 5 次,线程 2 输出 b 5 次,线程 3 输出 c 5 次。现在要求输出 abcabcabcabcabc 怎么实现

wait-notify实现,通过整数标记来控制
@Slf4j(topic = "c.Test24")
public class Test24 {
    public static void main(String[] args) {
        WaitNotify wn=new WaitNotify(1,5);
        new Thread(()->{
            try {
                wn.print("a",1,2);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }).start();
        new Thread(()->{
            try {
                wn.print("b",2,3);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }).start();
        new Thread(()->{
            try {
                wn.print("c",3,1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }).start();
    }
}
class WaitNotify{
    public void print(String str,int waitFlag,int nextFlag) throws InterruptedException {
          for (int i=0;i<loopNumber;i++){
              synchronized (this){
                  while(flag!=waitFlag){
                      this.wait();
                  }
                  System.out.print(str);
                  flag=nextFlag;
                  this.notifyAll();
              }
          }
    }
    //等待标记
    private int flag;
    //循环次数
    private int loopNumber;
    public WaitNotify(int flag,int loopNumber){
        this.flag=flag;
        this.loopNumber=loopNumber;
    }
}
Lock 条件变量实现
@Slf4j(topic ="c.Test25")
public class Test25 {
    public static void main(String[] args) throws InterruptedException {
        AwaitSignal awaitSignal=new AwaitSignal(5);
        Condition a=awaitSignal.newCondition();
        Condition b=awaitSignal.newCondition();
        Condition c=awaitSignal.newCondition();
        new Thread(()->{
            try {
                awaitSignal.print("a",a,b);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        },"1").start();
        new Thread(()->{
            try {
                awaitSignal.print("b",b,c);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        },"2").start();
        new Thread(()->{
            try {
                awaitSignal.print("c",c,a);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        },"3").start();
        Thread.sleep(1000);
        awaitSignal.lock();
        try {
            System.out.println("开始:");
            //先唤醒线程1来打印a
            a.signal();
        }finally {
            awaitSignal.unlock();
        }
    }
}
class AwaitSignal extends ReentrantLock {
    private int loopNumber;
    public AwaitSignal(int loopNumber){
        this.loopNumber=loopNumber;
    }
    public void print(String str,Condition current,Condition next) throws InterruptedException {
        for(int i=0;i<loopNumber;i++){
            lock();
            try {
                current.await();
                System.out.print(str);
                next.signal();
            }finally {
                unlock();
            }
        }
    }
}
Park Unpark 实现
@Slf4j(topic = "c.Test26")
public class Test26 {
    static Thread t1;
    static Thread t2;
    static Thread t3;
    public static void main(String[] args) {
        parkUnpark pu=new parkUnpark(5);
        t1=new Thread(()->{
            pu.print("a",t2);
        },"t1");
        t2=new Thread(()->{
            pu.print("b",t3);
        },"t2");
        t3=new Thread(()->{
            pu.print("c",t1);
        },"t3");
        t1.start();
        t2.start();
        t3.start();
        LockSupport.unpark(t1);
    }
}
class parkUnpark{
    public void print(String str,Thread next){
        for(int i=0;i<loopNumber;i++){
            LockSupport.park();
            System.out.print(str);
            LockSupport.unpark(next);
        }
    }
    private int loopNumber;
    public parkUnpark(int loopNumber){
        this.loopNumber=loopNumber;
    }
}

小结

本章我们需要重点掌握的是
       分析多线程访问共享资源时,哪些代码片段属于临界区
       使用 synchronized 互斥解决临界区的线程安全问题
          掌握 synchronized 锁对象语法
          掌握 synchronzied 加载成员方法和静态方法语法
          掌握 wait/notify 同步方法
   使用 lock 互斥解决临界区的线程安全问题
        掌握 lock 的使用细节:可打断、锁超时、公平锁、条件变量
学会分析变量的线程安全性、掌握常见线程安全类的使用
了解线程活跃性问题:死锁、活锁、饥饿
应用方面
     互斥:使用 synchronized 或 Lock 达到共享资源互斥效果
     同步:使用 wait/notify 或 Lock 的条件变量来达到线程间通信效果
原理方面
      monitor、synchronized 、wait/notify 原理
      synchronized 进阶原理
      park & unpark 原理
模式方面
    同步模式之保护性暂停
    异步模式之生产者消费者
    同步模式之顺序控制

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值