全网最详细并发编程(4)—入门篇

本文主要讲解ReentrantLock、

一、 ReentrantLock

相对于 synchronized 它具备如下特点
 ● 可中断
 ● 可以设置超时时间
 ● 可以设置为公平锁
 ● 支持多个条件变量
 ● 与 synchronized 一样,都支持可重入

基本语法

// 获取锁
reentrantLock.lock(); //此行放在try块内和外效果都一样
	try {
	 // 临界区
	} finally {
	 // 释放锁
	 reentrantLock.unlock();
}

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

static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
 	method1();
}
public static void method1() {
 	lock.lock();
	 try {
	 log.debug("execute method1");
	 	method2();
	 } finally {
	 	lock.unlock();
	 }
}
public static void method2() {
 	lock.lock();
	 try {
	 log.debug("execute method2");
	 	method3();
	 } finally {
	 	lock.unlock();
	 }
}
public static void method3() {
	 lock.lock();
	 try {
	 	log.debug("execute method3");
	 } finally {
	 	lock.unlock();
	 }
}

输出

	17:59:11.862 [main] c.TestReentrant - execute method1 
	17:59:11.865 [main] c.TestReentrant - execute method2 
	17:59:11.865 [main] c.TestReentrant - execute method3

可打断

ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
	 log.debug("启动...");
 try {
 	lock.lockInterruptibly();  //可被打断的锁
 } catch (InterruptedException e) {
	e.printStackTrace();
 	log.debug("等锁的过程中被打断"); //没有获得锁就被打断跑出的异常
	return;						  //异常返回
 }
 try {
 	log.debug("获得了锁");
 } finally {
 	lock.unlock();
 }
}, "t1");

	lock.lock();                //主线程先获得锁
	log.debug("获得了锁");
	t1.start();
try {
 	sleep(1);
	 t1.interrupt();			//打断t1线程
 	log.debug("执行打断");
} finally {
 	lock.unlock();	
}

输出

	18:02:40.520 [main] c.TestInterrupt - 获得了锁
	18:02:40.524 [t1] c.TestInterrupt - 启动... 
	18:02:41.530 [main] c.TestInterrupt - 执行打断
	java.lang.InterruptedException 
	 at 
	java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchr
	onizer.java:898) 
	 at 
	java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchron
	izer.java:1222) 
	 at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335) 
	 at cn.itcast.n4.reentrant.TestInterrupt.lambda$main$0(TestInterrupt.java:17) 
	 at java.lang.Thread.run(Thread.java:748) 
	18:02:41.532 [t1] c.TestInterrupt - 等锁的过程中被打断

注意如果是不可中断模式,那么即使使用了 interrupt 也不会让等待中断

ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
	 log.debug("启动...");
 try {
 	lock.lock();  //可被打断的锁
 } catch (InterruptedException e) {
	e.printStackTrace();
 	log.debug("等锁的过程中被打断"); //没有获得锁就被打断跑出的异常
	return;						  //异常返回
 }
 try {
 	log.debug("获得了锁");
 } finally {
 	lock.unlock();
 }
}, "t1");

	lock.lock();                //主线程先获得锁
	log.debug("获得了锁");
	t1.start();
try {
 	sleep(1);
	 t1.interrupt();			//打断t1线程
 	log.debug("执行打断");
} finally {
	log.debug("释放了锁")
 	lock.unlock();	
}

输出

	18:06:56.261 [main] c.TestInterrupt - 获得了锁
	18:06:56.265 [t1] c.TestInterrupt - 启动... 
	18:06:57.266 [main] c.TestInterrupt - 执行打断 // 这时 t1 并没有被真正打断, 而是仍继续等待锁
	18:06:58.267 [main] c.TestInterrupt - 释放了锁
	18:06:58.267 [t1] c.TestInterrupt - 获得了锁

锁超时
(1) 立刻失败

import lombok.extern.slf4j.Slf4j;
import java.sql.Time;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import static cn.itcast.n2.util.Sleeper.sleep;

@Slf4j(topic = "c.Test22")
public class Test22 {
    private static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            log.debug("尝试获得锁");
            try {
                if (! lock.tryLock()) {
                    log.debug("获取立刻失败,返回");
                    return;
                }
            } 
            try {
                log.debug("获得到锁");
            } finally {
                lock.unlock();
            }
        }, "t1");

        lock.lock();
        log.debug("获得到锁");
        t1.start();
        sleep(2);
        log.debug("释放了锁");
        lock.unlock();
    }
}

输出

	18:15:02.918 [main] c.TestTimeout - 获得了锁
	18:15:02.921 [t1] c.TestTimeout - 启动... 
	18:15:02.921 [t1] c.TestTimeout - 获取立刻失败,返回

(2) 超时失败

import lombok.extern.slf4j.Slf4j;
import java.sql.Time;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import static cn.itcast.n2.util.Sleeper.sleep;

@Slf4j(topic = "c.Test22")
public class Test22 {
    private static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            log.debug("尝试获得锁");
            try {
                if (! lock.tryLock(1, TimeUnit.SECONDS)) {
                    log.debug("获取等待1s后失败,返回");
                    return;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("获取不到锁");
                return;
            }
            try {
                log.debug("获得到锁");
            } finally {
                lock.unlock();
            }
        }, "t1");

        lock.lock();
        log.debug("获得到锁");
        t1.start();
        log.debug("释放了锁");
        lock.unlock();
    }
}

输出

	18:19:40.537 [main] c.TestTimeout - 获得了锁
	18:19:40.544 [t1] c.TestTimeout - 启动... 
	18:19:41.547 [t1] c.TestTimeout - 获取等待 1s 后失败,返回

(3)锁超时解决哲学家就餐问题

import cn.itcast.n2.util.Sleeper;
import lombok.extern.slf4j.Slf4j;
import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j(topic = "c.Test23")
public class Test23 {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();
}
}

@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;
    }

    @Override
    public void run() {
        while (true) {
            // 尝试获得左手筷子
            if(left.tryLock()) {
                try {
                    // 尝试获得右手筷子
                    if(right.tryLock()) {
                        try {
                            eat();
                        } finally {
                            right.unlock();
                        }
                    }
                } finally {
                    left.unlock(); // 释放自己手里的筷子
                }
            }
        }
    }

    private void eat() {
        log.debug("eating...");
        Sleeper.sleep(0.5);
    }
}

class Chopstick extends ReentrantLock {  //继承ReentrantLock 
    String name;

    public Chopstick(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "筷子{" + name + '}';
    }
}

(4) ReentrantLock 条件变量Condition的使用

@Slf4j(topic = "c.Test24")
public class Test24 {
    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) {

        new Thread(() -> {
            ROOM.lock();
            try {
                log.debug("有烟没?[{}]", hasCigarette);
                while (!hasCigarette) {
                    log.debug("没烟,先歇会!");
                    try {
                        waitCigaretteSet.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("可以开始干活了");
            } 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("可以开始干活了");
            } finally {
                ROOM.unlock();
            }
        }, "小女").start();

        sleep(1);
        new Thread(() -> {
            ROOM.lock();
            try {
                hasTakeout = true;
                waitTakeoutSet.signal();
            } finally {
                ROOM.unlock();
            }
        }, "送外卖的").start();

        sleep(1);

        new Thread(() -> {
            ROOM.lock();
            try {
                hasCigarette = true;
                waitCigaretteSet.signal();
            } finally {
                ROOM.unlock();
            }
        }, "送烟的").start();
    }
}

二、设计模式
(1)固定顺序输出

① 使用wait/notify

@Slf4j(topic = "c.Test25")
public class Test25 {
    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) {
                        e.printStackTrace();
                    }
                }
                log.debug("1");
            }
        }, "t1");


        Thread t2 = new Thread(() -> {
            synchronized (lock) {
                log.debug("2");
                t2runned = true;
                lock.notify();
            }
        }, "t2");

        t1.start();
        t2.start();
    }
}

② 使用park/unpark(简洁,没有锁的概念,以线程为单位,唤醒线程)

@Slf4j(topic = "c.Test26")
public class Test26 {
    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();
    }
}

③ 使用ReentrantLock

@Slf4j(topic = "c.TestReentrantLock")
public class TestReentrantLock {

    static ReentrantLock lock = new ReentrantLock();
    static Condition condition = lock.newCondition();
    static boolean t2runned = false;

    public static void main(String[] args) {
    
        Thread t1 = new Thread(() ->{
            lock.lock();
            try {
                while (!t2runned){
                    try {
                        condition.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("我是t1");

            }finally {
                lock.unlock();
            }
        },"t1");

        Thread t2 = new Thread(() ->{
            lock.lock();
            try {
                log.debug("我是t2");
                t2runned = true;
                condition.signal();

            }finally {
                lock.unlock();
            }
        },"t2");

        t1.start();
        t2.start();
    }
}

(2)交替输出

① 使用wait/notify

public class TestWaitNotify {

    public static void main(String[] args) {
        WaitNotify waitNotify = new WaitNotify(1,5);

        new Thread(() -> {
            waitNotify.print("a",1,2);

        },"a线程").start();

        new Thread(() -> {
            waitNotify.print("b",2,3);

        },"b线程").start();

        new Thread(() -> {
            waitNotify.print("c",3,1);

        },"c线程").start();
    }
}

class WaitNotify {
    private int flag;
    private int loopNumber;

    /*打印*/
    public void print(String str ,int waitFlag , int nextFlag){
        for (int i = 0; i < loopNumber; i++) {
            synchronized (this){
                while (waitFlag != this.flag){
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.print(str);
                this.flag = nextFlag;
                this.notifyAll();
            }
        }
    }

    public WaitNotify(int flag, int loopNumber) {
        this.flag = flag;
        this.loopNumber = loopNumber;
    }

    public int getFlag() {
        return flag;
    }

    public void setFlag(int flag) {
        this.flag = flag;
    }

    public int getLoopNumber() {
        return loopNumber;
    }

    public void setLoopNumber(int loopNumber) {
        this.loopNumber = loopNumber;
    }
}

② 使用park/unpark(简洁,没有锁的概念,以线程为单位,唤醒线程)

public class TestParkUnpark {

    static Thread a ;
    static Thread b ;
    static Thread c ;

    public static void main(String[] args) {
        ParkUnpark parkUnpark = new ParkUnpark(5);

        a = new Thread(() -> {
            parkUnpark.print("a",b);
        },"a");

         b = new Thread(() -> {
            parkUnpark.print("b",c);
        },"b");

         c = new Thread(() -> {
            parkUnpark.print("c",a);
        },"c");

        a.start();
        b.start();
        c.start();

        LockSupport.unpark(a);

    }

}

class ParkUnpark {
    private int loopNumber;

    public ParkUnpark(int loopNumber) {
        this.loopNumber = loopNumber;
    }

    public void print(String str,  Thread nextThread){
        for (int i = 0; i < loopNumber; i++) {
            LockSupport.park();
            System.out.print(str);
            LockSupport.unpark(nextThread);
        }
    }
}

③ 使用ReentrantLock (休息室Condition)

public class TestAwaitSignal {

    public static void main(String[] args) throws InterruptedException {
        AwaitSignal awaitSignal = new AwaitSignal(5);
        Condition a_condition = awaitSignal.newCondition();
        Condition b_condition = awaitSignal.newCondition();
        Condition c_condition = awaitSignal.newCondition();

        new Thread(() -> {
            awaitSignal.print("a",a_condition,b_condition);
        },"a").start();

        new Thread(() -> {
            awaitSignal.print("b",b_condition,c_condition);
        },"b").start();

        new Thread(() -> {
            awaitSignal.print("c",c_condition,a_condition);
        },"c").start();

        Thread.sleep(1000);
        System.out.println("==========开始=========");
        awaitSignal.lock();
        try {
            a_condition.signal();  //首先唤醒a线程
        }finally {
            awaitSignal.unlock();
        }
    }

}

class AwaitSignal extends ReentrantLock {
    private int loopNumber;

    public AwaitSignal(int loopNumber) {
        this.loopNumber = loopNumber;
    }

    public void print(String str, Condition condition, Condition next){
        for (int i = 0; i < loopNumber; i++) {
            lock();

            try{
                try {
                    condition.await();
//                    System.out.print("i:==="+i);
                    System.out.print(str);
                    next.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }finally {
                unlock();
            }
        }
    }
}

三、入门篇小结
全网最详细并发编程(4)---入门篇_java
全网最详细并发编程(4)---入门篇_并发编程_02


欢迎关注公众号Java技术大本营,会不定期分享BAT面试资料等福利。

全网最详细并发编程(4)---入门篇_java_03