【记录】并发编程 - 学习日志(四)

4.10 线程状态转换(Java API)


所附代码为转换方式部分(非全部)展示

4.10.1 NEW - - - > RUNNABLE

 线程可调用 start 方法使其状态实现从 NEW 到 RUNNABLE 的转换

package com.rui.state;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test1")
public class Test1 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {

        }, "t");
        log.debug("此时 t 线程的状态为:{}", t.getState());

        t.start();
        log.debug("此时 t 线程的状态为:{}", t.getState());

    }
}
// 运行结果

09:01:28 [main] c.Test1 - 此时 t 线程的状态为:NEW
09:01:28 [main] c.Test1 - 此时 t 线程的状态为:RUNNABLE

进程已结束,退出代码 0

4.10.2 RUNNABLE < - - - > WAITING

线程状态实现 RUNNABLE 和 WAITING 间的转换有三种方式


方式一

t 线程通过 synchronized (o) 获取对象锁后

通过 o.wait (); 语句使其状态实现从 RUNNABLE 到 WAITING 的转换

执行 o.notify(); / o.notifyAll(); / t.interrupt(); 语句,该线程会从 WaitSet 进入 EntryList,使其状态实现从 WAITING 到 BLOCKED 的转换

若该线程获取锁(竞争锁)成功,该线程的状态会从 BLOCKED 转换为 RUNNABLE;

若该线程获取锁失败,该线程会保持 BLOCKED 状态。

package com.rui.state;

import lombok.extern.slf4j.Slf4j;

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

    public static void main(String[] args) throws InterruptedException {
        final Object o = new Object();

        Thread t1 = new Thread(() -> {
            synchronized (o) {
                try {
                    log.debug("此时 t1 线程的状态:{}", Thread.currentThread().getState());
                    o.wait();
                    log.debug("此时 t1 线程的状态:{}", Thread.currentThread().getState());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            synchronized (o) {
                try {
                    log.debug("此时 t2 线程的状态:{}", Thread.currentThread().getState());
                    o.wait();
                    log.debug("此时 t2 线程的状态:{}", Thread.currentThread().getState());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t2");

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

        Thread.sleep(500);
        log.debug("此时 t1 线程的状态:{}", t1.getState());
        log.debug("此时 t2 线程的状态:{}", t2.getState());
        synchronized (o) {
            o.notifyAll();
            log.debug("此时 t1 线程的状态:{}", t1.getState());
            log.debug("此时 t2 线程的状态:{}", t2.getState());
        }
    }
}

// 某次运行结果

15:47:26 [t1] c.Test5 - 此时 t1 线程的状态:RUNNABLE
15:47:26 [t2] c.Test5 - 此时 t2 线程的状态:RUNNABLE
15:47:27 [main] c.Test5 - 此时 t1 线程的状态:WAITING
15:47:27 [main] c.Test5 - 此时 t2 线程的状态:WAITING
15:47:27 [main] c.Test5 - 此时 t1 线程的状态:BLOCKED
15:47:27 [main] c.Test5 - 此时 t2 线程的状态:BLOCKED
15:47:27 [t2] c.Test5 - 此时 t2 线程的状态:RUNNABLE
15:47:27 [t1] c.Test5 - 此时 t1 线程的状态:RUNNABLE

进程已结束,退出代码 0

方式二 

在 t2 线程中执行 t1.join (); 语句可使 t2 线程的状态实现从 RUNNABLE 到 WAITING 的转换

待 t1 线程运行结束或执行 t2.interrupt(); 语句,t2 线程的状态会从 WAITING 转换为 RUNNABLE

package com.rui.state;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test3")
public class Test3 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                log.debug("t1 线程开始运行...");
                Thread.sleep(2000);
                log.debug("t1 线程结束运行...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            try {
                log.debug("此时 t2 线程的状态:{}", Thread.currentThread().getState());
                log.debug("join...");
                t1.join();
                log.debug("t1 线程运行结束,t2 线程开始运行");
                log.debug("此时 t2 线程的状态:{}", Thread.currentThread().getState());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t2");

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

        Thread.sleep(1000);
        log.debug("此时 t2 线程的状态:{}", t2.getState());

    }

}
// 某次运行结果

15:17:14 [t1] c.Test3 - t1 线程开始运行...
15:17:14 [main] c.Test3 - 此时 t2 线程的状态:RUNNABLE
15:17:15 [t2] c.Test3 - join...
15:17:15 [main] c.Test3 - 此时 t2 线程的状态:WAITING
15:17:16 [t1] c.Test3 - t1 线程结束运行...
15:17:16 [t2] c.Test3 - t1 线程运行结束,t2 线程开始运行
15:17:16 [t2] c.Test3 - 此时 t2 线程的状态:RUNNABLE

进程已结束,退出代码 0

方式三 

在 t 线程中执行 LockSupport.park(); 语句可使该线程的状态实现从 RUNNABLE 到 WAITING 的转换

再执行 LockSupport.unpark(Thread t); / t.interrupt(); 语句可使 t 线程的状态实现从 WAITING 到 RUNNABLE 的转换

package com.rui.state;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.LockSupport;

@Slf4j(topic = "c.Test4")
public class Test4 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            log.debug("此时 t 线程的状态:{}", Thread.currentThread().getState());
            LockSupport.park();
            log.debug("此时 t 线程的状态:{}", Thread.currentThread().getState());
        });

        t.start();

        Thread.sleep(500);
        log.debug("此时 t 线程的状态:{}", t.getState());

        LockSupport.unpark(t);
    }
}
// 某次运行结构

15:33:20 [Thread-0] c.Test4 - 此时 t 线程的状态:RUNNABLE
15:33:21 [main] c.Test4 - 此时 t 线程的状态:WAITING
15:33:21 [Thread-0] c.Test4 - 此时 t 线程的状态:RUNNABLE

进程已结束,退出代码 0

4.10.3  RUNNABLE < - - - > TIMED_WAITING

线程状态实现 RUNNABLE 和 TIMED_WAITING 间的转换有四种方式


方式一

t 线程通过 synchronized (o) 获取对象锁后

通过 o.wait (long timeout); 语句使其状态实现从 RUNNABLE 到 TIMED_WAITING 的转换

执行 o.notify(); / o.notifyAll(); / t.interrupt(); 语句或 t 线程等待 timeout 毫秒后,该线程会从 WaitSet 进入 EntryList,使其状态实现从 TIMED_WAITING 到 BLOCKED 的转换

若该线程获取锁(竞争锁)成功,该线程的状态会从 BLOCKED 转换为 RUNNABLE;

若该线程获取锁失败,该线程会保持 BLOCKED 状态。

package com.rui.state;

import lombok.extern.slf4j.Slf4j;

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

    public static void main(String[] args) throws InterruptedException {
        final Object o = new Object();

        Thread t1 = new Thread(() -> {
            synchronized (o) {
                try {
                    log.debug("此时 t1 线程的状态:{}", Thread.currentThread().getState());
                    o.wait(1000);
                    log.debug("此时 t1 线程的状态:{}", Thread.currentThread().getState());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            synchronized (o) {
                try {
                    log.debug("此时 t2 线程的状态:{}", Thread.currentThread().getState());
                    o.wait(1000);
                    log.debug("此时 t2 线程的状态:{}", Thread.currentThread().getState());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t2");

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

        Thread.sleep(500);
        log.debug("此时 t1 线程的状态:{}", t1.getState());
        log.debug("此时 t2 线程的状态:{}", t2.getState());
        synchronized (o) {
            o.notifyAll();
            log.debug("此时 t1 线程的状态:{}", t1.getState());
            log.debug("此时 t2 线程的状态:{}", t2.getState());
        }
    }
}

// 某次运行结果

15:50:53 [t1] c.Test5 - 此时 t1 线程的状态:RUNNABLE
15:50:53 [t2] c.Test5 - 此时 t2 线程的状态:RUNNABLE
15:50:54 [main] c.Test5 - 此时 t1 线程的状态:TIMED_WAITING
15:50:54 [main] c.Test5 - 此时 t2 线程的状态:TIMED_WAITING
15:50:54 [main] c.Test5 - 此时 t1 线程的状态:BLOCKED
15:50:54 [main] c.Test5 - 此时 t2 线程的状态:BLOCKED
15:50:54 [t2] c.Test5 - 此时 t2 线程的状态:RUNNABLE
15:50:54 [t1] c.Test5 - 此时 t1 线程的状态:RUNNABLE

进程已结束,退出代码 0

方式二 

在 t2 线程中执行 t1.join (long millis); 语句可使 t2 线程的状态实现从 RUNNABLE 到 TIMED_WAITING 的转换

待 t1 线程运行结束或 t2 线程等待 millis 毫秒后或执行 t2.interrupt(); 语句,t2 线程的状态会从 TIMED_WAITING 转换为 RUNNABLE

package com.rui.state;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test6")
public class Test6 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                log.debug("t1 线程开始运行...");
                Thread.sleep(2000);
                log.debug("t1 线程结束运行...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            try {
                log.debug("此时 t2 线程的状态:{}", Thread.currentThread().getState());
                log.debug("join...");
                t1.join(3000);
                log.debug("t1 线程运行结束,t2 线程开始运行");
                log.debug("此时 t2 线程的状态:{}", Thread.currentThread().getState());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t2");

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

        Thread.sleep(1000);
        log.debug("此时 t2 线程的状态:{}", t2.getState());

    }

}

// 某次运行结果

16:00:04 [t1] c.Test6 - t1 线程开始运行...
16:00:04 [t2] c.Test6 - 此时 t2 线程的状态:RUNNABLE
16:00:04 [t2] c.Test6 - join...
16:00:05 [main] c.Test6 - 此时 t2 线程的状态:TIMED_WAITING
16:00:06 [t1] c.Test6 - t1 线程结束运行...
16:00:06 [t2] c.Test6 - t1 线程运行结束,t2 线程开始运行
16:00:06 [t2] c.Test6 - 此时 t2 线程的状态:RUNNABLE

进程已结束,退出代码 0

方式三

在线程中执行 Thread.sleep(long millis); 语句可使该线程的状态实现从 RUNNABLE 到 TIMED_WAITING 的转换

待该线程等待 millis 毫秒后或执行 t2.interrupt(); 语句,t2 线程的状态会从 TIMED_WAITING 转换为 RUNNABLE

package com.rui.state;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test")
public class Test7 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            try {
                log.debug("此时 t 线程的状态:{}", Thread.currentThread().getState());
                Thread.sleep(1000);
                log.debug("此时 t 线程的状态:{}", Thread.currentThread().getState());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t");

        t.start();

        Thread.sleep(500);
        log.debug("此时 t 线程的状态:{}", t.getState());
    }
}
// 某次运行结果

16:12:00 [t] c.Test - 此时 t 线程的状态:RUNNABLE
16:12:01 [main] c.Test - 此时 t 线程的状态:TIMED_WAITING
16:12:01 [t] c.Test - 此时 t 线程的状态:RUNNABLE

进程已结束,退出代码 0

方式四

对该方式有个印象即可

在 t 线程中执行 LockSupport.parkNanos(long nanos); / LockSupport.parkUntil(long deadline); 语句可使该线程的状态实现从 RUNNABLE 到 TIMED_WAITING 的转换

再执行 LockSupport.unpark(Thread t); / t.interrupt(); 语句或者等待超时后可使 t 线程的状态实现从 TIMED_WAITING 到 RUNNABLE 的转换

4.10.4 RUNNABLE < - - - > BLOCKED

多个线程同时获取同一把对象锁时(竞争锁):未拥有锁的线程进入 EntryList(竞争失败),其状态由 RUNNABLE 转换为 BLOCKED;待该对象锁被释放,EntryList 中的线程获取锁(竞争锁),若某个线程拥有锁(竞争成功),则其状态由 BLOCKED 转换为 RUNNABLE。

4.10.5 RUNNABLE - - - >  TERMINATED

当线程运行结束时,其状态由 RUNNABLE 转换为 TERMINATED

4.11 多把锁

package com.rui.blocked;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test12")
public class Test12 {
    public static void main(String[] args) {
        Room room = new Room();

        new Thread(() -> {
            try {
                room.sleep();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "A").start();

        new Thread(() -> {
            try {
                room.study();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "B").start();
    }
}

@Slf4j(topic = "c.Room")
class Room {
    public synchronized void study() throws InterruptedException {
        log.debug("学习 1 小时...");
        Thread.sleep(1000);
    }

    public synchronized void sleep() throws InterruptedException {
        log.debug("睡眠 2 小时...");
        Thread.sleep(2000);
    }
}
// 某次运行结果

09:50:00 [A] c.Room - 睡眠 2 小时...
09:50:02 [B] c.Room - 学习 1 小时...

进程已结束,退出代码 0

若把 A、B 两个线程看做是两个同学,根据常识可知 A 同学睡眠与 B 同学学习并不冲突。

根据上述代码的某次运行结果可以看出,B 同学需要等待 A 同学睡眠结束才能开始学习,影响效率。

导致上述现象产生的原因是由于 A 同学睡眠与B 同学学习共用了同一把锁

因此,由上述比喻分析可以得出:当多个线程间执行的指令互不干扰时,若这些指令共用同一把锁会影响对应进程的运行效率


package com.rui.blocked;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test12")
public class Test12 {
    public static void main(String[] args) {
        Room room = new Room();
        new Thread(() -> {
            try {
                room.sleep();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "A").start();

        new Thread(() -> {
            try {
                room.study();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "B").start();
    }
}

@Slf4j(topic = "c.Room")
class Room {
    private final Object studyRoom = new Object();
    private final Object sleepRoom = new Object();

    public void study() throws InterruptedException {
        synchronized (studyRoom) {
            log.debug("学习 1 小时...");
            Thread.sleep(1000);
        }
    }

    public void sleep() throws InterruptedException {
        synchronized (sleepRoom) {
            log.debug("睡眠 2 小时...");
            Thread.sleep(2000);
        }
    }
}
// 某次运行结果

10:10:27 [A] c.Room - 睡眠 2 小时...
10:10:27 [B] c.Room - 学习 1 小时...

进程已结束,退出代码 0

根据上述代码的某次运行结果可以看出,A 线程与 B 线程同时运行。


package com.rui.blocked;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test12")
public class Test12 {
    public static void main(String[] args) {
        new Thread(() -> {
            try {
                new Room().sleep();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "A").start();

        new Thread(() -> {
            try {
                new Room().study();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "B").start();
    }
}

@Slf4j(topic = "c.Room")
class Room {
    public synchronized void study() throws InterruptedException {
        log.debug("学习 1 小时...");
        Thread.sleep(1000);
    }

    public synchronized void sleep() throws InterruptedException {
        log.debug("睡眠 2 小时...");
        Thread.sleep(2000);
    }
}
// 某次运行结果

10:10:27 [A] c.Room - 睡眠 2 小时...
10:10:27 [B] c.Room - 学习 1 小时...

进程已结束,退出代码 0

 该思路用在此处也对,但不建议。因为若当多个线程间执行的指令一部分互不干扰,另一部分互相干扰时,阁下又该如何应对呢?


运用多把锁可能会带来死锁问题

4.12 活跃性

多线程的活跃性有三种:死锁、活锁、饥饿锁


4.12.1 死锁

t1 线程已拥有 A 对象锁,想要获取 B 对象锁;t2 线程已拥有 B 对象锁,想要获取 A 对象锁。

package com.rui.blocked;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test13")
public class Test13 {
    public static void main(String[] args) {
        Object A = new Object();
        Object B = new Object();

        Thread t1 = new Thread(() -> {
            synchronized (A) {
                log.debug("拥有 A 对象锁");
                synchronized (B) {
                    log.debug("拥有 B 对象锁");
                }
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            synchronized (B) {
                log.debug("拥有 B 对象锁");
                synchronized (A) {
                    log.debug("拥有 A 对象锁");
                }
            }
        }, "t2");

        t1.start();
        t2.start();
    }
}
// 某次运行结果

16:44:50 [Thread-0] c.Test13 - 拥有 A 对象锁
16:44:50 [Thread-1] c.Test13 - 拥有 B 对象锁


哲学家就餐问题

有五位哲学家,围坐在圆桌旁。

他们只做两件事,思考和吃饭,思考一会吃口饭,吃完饭后接着思考。

吃饭时要用两根筷子吃,桌上共有 5 根筷子,每位哲学家左右手边各有一根筷子。

如果筷子被身边的人拿着,自己就得等待

package com.rui.blocked;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test14")
public class Test14 {
    public static void main(String[] args) {
        Chopsticks c1 = new Chopsticks("1");
        Chopsticks c2 = new Chopsticks("2");
        Chopsticks c3 = new Chopsticks("3");
        Chopsticks c4 = new Chopsticks("4");
        Chopsticks c5 = new Chopsticks("5");
        new Thinker("A", c1, c2).start();
        new Thinker("B", c2, c3).start();
        new Thinker("C", c3, c4).start();
        new Thinker("D", c4, c5).start();
        new Thinker("E", c5, c1).start();
    }
}

@Slf4j(topic = "c.Thinker")
class Thinker extends Thread {
    private Chopsticks left;
    private Chopsticks right;

    public Thinker(String name, Chopsticks left, Chopsticks right) {
        super(name);
        this.left = left;
        this.right = right;
    }

    public void action() throws InterruptedException {
        log.debug("吃饭...");
        Thread.sleep(1000);  // 思考...
    }

    @Override
    public void run() {
        while (true) {
            synchronized (left) {
                synchronized (right) {
                    try {
                        action();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

class Chopsticks {

    private String name;

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

    @Override
    public String toString() {
        return "Chopsticks{" +
                "name='" + name + '\'' +
                '}';
    }
}
// 某次运行结果

19:37:10 [A] c.Thinker - 吃饭...
19:37:10 [C] c.Thinker - 吃饭...
19:37:11 [C] c.Thinker - 吃饭...
19:37:12 [B] c.Thinker - 吃饭...

4.12.2 活锁

两个线程互相改变对方的结束条件,导致线程无法运行结束。

package com.rui.blocked;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test18")
public class Test18 {
    static volatile int count = 10;

    public static void main(String[] args) {
        new Thread(() -> {
            while (count > 0) {
                try {
                    Thread.sleep(200);
                    count--;
                    log.debug("t1 线程:count = {}", count);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t1").start();
        new Thread(() -> {
            while (count < 20) {
                try {
                    Thread.sleep(200);
                    count++;
                    log.debug("t2 线程:count = {}", count);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t2").start();

    }
}
// 某次运行结果

22:17:19 [t1] c.Test18 - t1 线程:count = 11
22:17:19 [t2] c.Test18 - t2 线程:count = 11
22:17:20 [t1] c.Test18 - t1 线程:count = 10
22:17:20 [t2] c.Test18 - t2 线程:count = 11
22:17:20 [t1] c.Test18 - t1 线程:count = 10
22:17:20 [t2] c.Test18 - t2 线程:count = 11
22:17:20 [t1] c.Test18 - t1 线程:count = 10
22:17:20 [t2] c.Test18 - t2 线程:count = 11
22:17:20 [t1] c.Test18 - t1 线程:count = 10
22:17:20 [t2] c.Test18 - t2 线程:count = 11
22:17:20 [t1] c.Test18 - t1 线程:count = 10
22:17:20 [t2] c.Test18 - t2 线程:count = 11
22:17:21 [t1] c.Test18 - t1 线程:count = 10
22:17:21 [t2] c.Test18 - t2 线程:count = 11
22:17:21 [t1] c.Test18 - t1 线程:count = 10
22:17:21 [t2] c.Test18 - t2 线程:count = 11
22:17:21 [t1] c.Test18 - t1 线程:count = 10
22:17:21 [t2] c.Test18 - t2 线程:count = 11
22:17:21 [t1] c.Test18 - t1 线程:count = 10
22:17:21 [t2] c.Test18 - t2 线程:count = 11
22:17:21 [t1] c.Test18 - t1 线程:count = 10
22:17:21 [t2] c.Test18 - t2 线程:count = 11
22:17:22 [t1] c.Test18 - t1 线程:count = 10
22:17:22 [t2] c.Test18 - t2 线程:count = 11
22:17:22 [t1] c.Test18 - t1 线程:count = 10
22:17:22 [t2] c.Test18 - t2 线程:count = 11
22:17:22 [t1] c.Test18 - t1 线程:count = 10
22:17:22 [t2] c.Test18 - t2 线程:count = 11
22:17:22 [t1] c.Test18 - t1 线程:count = 10
22:17:22 [t2] c.Test18 - t2 线程:count = 11
22:17:22 [t1] c.Test18 - t1 线程:count = 10
22:17:22 [t2] c.Test18 - t2 线程:count = 11
22:17:23 [t1] c.Test18 - t1 线程:count = 10
22:17:23 [t2] c.Test18 - t2 线程:count = 11
22:17:23 [t1] c.Test18 - t1 线程:count = 10
22:17:23 [t2] c.Test18 - t2 线程:count = 11
22:17:23 [t1] c.Test18 - t1 线程:count = 10
22:17:23 [t2] c.Test18 - t2 线程:count = 11
22:17:23 [t1] c.Test18 - t1 线程:count = 10
22:17:23 [t2] c.Test18 - t2 线程:count = 11
22:17:23 [t1] c.Test18 - t1 线程:count = 10
22:17:23 [t2] c.Test18 - t2 线程:count = 11
22:17:24 [t1] c.Test18 - t1 线程:count = 10
22:17:24 [t2] c.Test18 - t2 线程:count = 11
22:17:24 [t1] c.Test18 - t1 线程:count = 10
22:17:24 [t2] c.Test18 - t2 线程:count = 11
22:17:24 [t1] c.Test18 - t1 线程:count = 10
22:17:24 [t2] c.Test18 - t2 线程:count = 11
22:17:24 [t1] c.Test18 - t1 线程:count = 10
22:17:24 [t2] c.Test18 - t2 线程:count = 11
22:17:24 [t1] c.Test18 - t1 线程:count = 10
22:17:25 [t2] c.Test18 - t2 线程:count = 11
22:17:25 [t1] c.Test18 - t1 线程:count = 10
// ...

4.12.3 饥饿锁

线程一直未拥有锁。


修改【哲学家就餐问题】中 main 函数的代码

package com.rui.blocked;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test19")
public class Test19 {
    public static void main(String[] args) {
        Chopsticks c1 = new Chopsticks("1");
        Chopsticks c2 = new Chopsticks("2");
        Chopsticks c3 = new Chopsticks("3");
        Chopsticks c4 = new Chopsticks("4");
        Chopsticks c5 = new Chopsticks("5");
        new Thinker("A", c1, c2).start();
        new Thinker("B", c2, c3).start();
        new Thinker("C", c3, c4).start();
        new Thinker("D", c4, c5).start();
        new Thinker("E", c1, c5).start();
    }
}

// 某次运行结果

22:27:48 [C] c.Thinker - 吃饭...
22:27:48 [A] c.Thinker - 吃饭...
22:27:49 [C] c.Thinker - 吃饭...
22:27:49 [A] c.Thinker - 吃饭...
22:27:50 [C] c.Thinker - 吃饭...
22:27:51 [C] c.Thinker - 吃饭...
22:27:52 [B] c.Thinker - 吃饭...
22:27:52 [D] c.Thinker - 吃饭...
22:27:53 [D] c.Thinker - 吃饭...
// ...

通过某次运行结果可以发现,E 线程一直未拥有锁。

E 线程的活跃性:饥饿锁

4.13 ReentrantLock 

ReentrantLock 具有可打断、可设置超时时间、可设置为公平锁、支持多个条件变量和支持可重入的特点

// 语法

ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
    // ...
} finally {
    lock.unlock();
}

4.13.1 可打断

lockInterruptibly() - 可打断线程的阻塞状态

package com.rui.blocked;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.ReentrantLock;

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

    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            try {
                log.debug("t 线程获取锁");
                lock.lockInterruptibly();
                try {
                    log.debug("t 线程拥有锁");
                } finally {
                    log.debug("t 线程释放锁");
                    lock.unlock();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("t 线程获取锁失败");
            }
        }, "t");

        lock.lock();
        t.start();
        try {
            log.debug("打断 t 线程");
            t.interrupt();
        } finally {
            lock.unlock();
        }
    }
}
// 某次运行结果

09:29:17 [t] c.Test15 - t 线程获取锁
09:29:17 [main] c.Test15 - 打断 t 线程
09:29:17 [t] c.Test15 - t 线程获取锁失败
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
	at com.rui.blocked.Test15.lambda$main$0(Test15.java:14)
	at java.lang.Thread.run(Thread.java:745)

进程已结束,退出代码 0

4.13.2 可设置超时时间

tryLock() - 若获取锁成功,拥有锁;若获取锁失败,放弃获取锁(即刻生效)

package com.rui.blocked;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.ReentrantLock;

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

    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            log.debug("t 线程获取锁");
            if (!lock.tryLock()) {
                log.debug("t 线程获取锁失败");
                return;
            }
            try {
                log.debug("t 线程拥有锁");
            } finally {
                log.debug("t 线程释放锁");
                lock.unlock();
            }
        }, "t1");

        lock.lock();
        t.start();
        try {
            log.debug("主线程运行状态:{}", Thread.currentThread().getState());
        } finally {
            lock.unlock();
        }
    }
}
// 某次运行结果

09:50:44 [t1] c.Test16 - t 线程获取锁
09:50:44 [main] c.Test16 - 主线程运行状态:RUNNABLE
09:50:44 [t1] c.Test16 - t 线程获取锁失败

进程已结束,退出代码 0

tryLock(long timeout, TimeUnit unit) - 在 timeout unit 时间内:若获取锁成功,拥有锁;若获取锁失败,放弃获取锁

package com.rui.blocked;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

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

    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            log.debug("t 线程获取锁");
            try {
                if (!lock.tryLock(1, TimeUnit.SECONDS)) {
                    log.debug("t 线程获取锁失败");
                    return;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                log.debug("t 线程拥有锁");
            } finally {
                log.debug("t 线程释放锁");
                lock.unlock();
            }
        }, "t1");

        lock.lock();
        t.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

// 某次运行结果

09:57:16 [t1] c.Test17 - t 线程获取锁
09:57:17 [t1] c.Test17 - t 线程获取锁失败

进程已结束,退出代码 0

解决【哲学家就餐问题】中的死锁现象

此处需注意:若获取锁失败则无需释放锁,故不要把判断获取锁是否成功的语句写在 try 代码块中

package com.rui.blocked;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.ReentrantLock;

@Slf4j(topic = "c.Test14")
public class Test14 {
    public static void main(String[] args) {
        Chopsticks c1 = new Chopsticks("1");
        Chopsticks c2 = new Chopsticks("2");
        Chopsticks c3 = new Chopsticks("3");
        Chopsticks c4 = new Chopsticks("4");
        Chopsticks c5 = new Chopsticks("5");
        new Thinker("A", c1, c2).start();
        new Thinker("B", c2, c3).start();
        new Thinker("C", c3, c4).start();
        new Thinker("D", c4, c5).start();
        new Thinker("E", c5, c1).start();
    }
}

@Slf4j(topic = "c.Thinker")
class Thinker extends Thread {
    private Chopsticks left;
    private Chopsticks right;

    public Thinker(String name, Chopsticks left, Chopsticks right) {
        super(name);
        this.left = left;
        this.right = right;
    }

    public void action() throws InterruptedException {
        log.debug("吃饭...");
        Thread.sleep(1000);  // 思考...
    }

    @Override
    public void run() {
        while (true) {
            if (left.tryLock()) {
                try {
                    if (right.tryLock()) {
                        try {
                            action();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        } finally {
                            right.unlock();
                        }
                    }
                } finally {
                    left.unlock();
                }
            }
        }
    }
}

class Chopsticks extends ReentrantLock {

    private String name;

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

    @Override
    public String toString() {
        return "Chopsticks{" +
                "name='" + name + '\'' +
                '}';
    }
}
// 某次运行结果

10:16:03 [A] c.Thinker - 吃饭...
10:16:03 [C] c.Thinker - 吃饭...
10:16:04 [B] c.Thinker - 吃饭...
10:16:04 [D] c.Thinker - 吃饭...
10:16:05 [B] c.Thinker - 吃饭...
10:16:05 [E] c.Thinker - 吃饭...
10:16:06 [C] c.Thinker - 吃饭...
10:16:06 [E] c.Thinker - 吃饭...
10:16:07 [B] c.Thinker - 吃饭...
10:16:07 [E] c.Thinker - 吃饭...
10:16:08 [E] c.Thinker - 吃饭...
10:16:08 [B] c.Thinker - 吃饭...
10:16:09 [D] c.Thinker - 吃饭...
10:16:09 [A] c.Thinker - 吃饭...
10:16:10 [D] c.Thinker - 吃饭...
10:16:10 [A] c.Thinker - 吃饭...
10:16:11 [E] c.Thinker - 吃饭...
10:16:11 [B] c.Thinker - 吃饭...
10:16:12 [E] c.Thinker - 吃饭...
// ...

4.13.3 可设置为公平锁

4.13.4 支持多个条件变量

await() / await(long time, TimeUnit unit) / signal() / singnalAll()

类似于 wait() / wait(long timeout) / notify() / notifyAll()


要点:

        1. 调用  await() / await(long time, TimeUnit unit) 前需要获取锁

        2. 调用  await() / await(long time, TimeUnit unit) 后线程会进入 conditionObject 等待,并释放锁

        3. conditionObject 中的线程被唤醒后需重新获取锁

        类似于 wait() / wait(long timeout)

package com.rui.blocked;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

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

    static final Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;
    static ReentrantLock lock = new ReentrantLock();
    static Condition waitCigaretteSet = lock.newCondition();
    static Condition waitTakeoutSet = lock.newCondition();

    public static void main(String[] args) throws InterruptedException {

        new Thread(() -> {
            lock.lock();
            try {
                while (!hasCigarette) {
                    waitCigaretteSet.await();
                }
                log.debug("begin...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "小南").start();

        new Thread(() -> {
            lock.lock();
            try {
                while (!hasTakeout) {
                    waitTakeoutSet.await();
                }
                log.debug("begin...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "小女").start();

        Thread.sleep(1000);

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

        new Thread(() -> {
            lock.lock();
            try {
                hasCigarette = true;
                waitCigaretteSet.signal();
            } finally {
                lock.unlock();
            }
        }, "送烟的").start();
    }
}
// 某次运行结果

10:35:15 [小女] c.Test20 - begin...
10:35:15 [小南] c.Test20 - begin...

进程已结束,退出代码 0

 4.13.5 支持可重入

package com.rui.blocked;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.ReentrantLock;

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

    public static void main(String[] args) {
        lock.lock();
        try {
            log.debug("main begin...");
            m1();
        } finally {
            lock.unlock();
        }
    }

    public static void m1() {
        lock.lock();
        try {
            log.debug("m1 begin...");
            m2();
        } finally {
            lock.unlock();
        }
    }

    public static void m2() {
        lock.lock();
        try {
            log.debug("m2 begin...");
        } finally {
            lock.unlock();
        }
    }
}
// 某次运行结果

10:47:40 [main] c.Test21 - main begin...
10:47:40 [main] c.Test21 - m1 begin...
10:47:40 [main] c.Test21 - m2 begin...

进程已结束,退出代码 0

4.14 顺序控制

4.14.1 固定运行顺序

new Thread(() -> {
    System.out.println(1);
}).start();

new Thread(() -> {
    System.out.println(2);
}).start();

// 需求:输出 21

wait / notify
package com.rui.blocked;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test22")
public class Test22 {
    static Object o = new Object();
    static Boolean flag = false;

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (o) {
                while (!flag) {
                    try {
                        o.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.print(1);
            }
        }).start();
        new Thread(() -> {
            synchronized (o) {
                System.out.print(2);
                flag = true;
                o.notify();
            }
        }).start();
    }
}
// 运行结果

21
进程已结束,退出代码 0
 park / unpark
package com.rui.blocked;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.LockSupport;

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

    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            LockSupport.park();
            System.out.print(1);
        });

        t.start();

        new Thread(() -> {
            System.out.print(2);
            LockSupport.unpark(t);
        }).start();
    }
}
// 运行结果

21
进程已结束,退出代码 0

4.14.2 交替输出 

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


wait / notify
package com.rui.blocked;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test24")
public class Test24 {
    public static void main(String[] args) {
        WaitNotify wn = new WaitNotify(1, 5);
        new Thread(() -> {
            try {
                wn.print(1, 2, "a");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1").start();
        new Thread(() -> {
            try {
                wn.print(2, 3, "b");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t2").start();
        new Thread(() -> {
            try {
                wn.print(3, 1, "c");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t3").start();
    }
}

class WaitNotify {

    private int flag;
    private int cycle;

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

    public void print(int currentFlag, int nextFlag, String str) throws InterruptedException {
        for (int i = 0; i < cycle; i++) {
            synchronized (this) {
                while (currentFlag != flag) {
                    wait();
                }
                System.out.print(str);
                flag = nextFlag;
                notifyAll();
            }
        }
    }
}
// 运行结果

abcabcabcabcabc
进程已结束,退出代码 0

await / signal
package com.rui.blocked;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j(topic = "c.Test25")
public class Test25 {
    public static void main(String[] args) throws InterruptedException {
        AwaitSignal as = new AwaitSignal(5);
        Condition t1 = as.newCondition();
        Condition t2 = as.newCondition();
        Condition t3 = as.newCondition();
        new Thread(() -> {
            try {
                as.print(t1, t2, "a");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1").start();
        new Thread(() -> {
            try {
                as.print(t2, t3, "b");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t2").start();
        new Thread(() -> {
            try {
                as.print(t3, t1, "c");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t3").start();

        Thread.sleep(1000);
        as.lock();
        try {
            t1.signal();
        } finally {
            as.unlock();
        }
    }
}

class AwaitSignal extends ReentrantLock {

    private int cycle;


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

    public void print(Condition current, Condition next, String str) throws InterruptedException {
        for (int i = 0; i < cycle; i++) {
            lock();
            try {
                current.await();
                System.out.print(str);
                next.signal();
            } finally {
                unlock();
            }
        }
    }
}
// 运行结果

abcabcabcabcabc
进程已结束,退出代码 0

park / unpark 
package com.rui.blocked;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.LockSupport;

@Slf4j(topic = "c.Test26")
public class Test26 {
    static Thread t1;
    static Thread t2;
    static Thread t3;

    public static void main(String[] args) throws InterruptedException {
        ParkUnpark pu = new ParkUnpark(5);
        t1 = new Thread(() -> {
            pu.print(t2, "a");
        }, "t1");
        t2 = new Thread(() -> {
            pu.print(t3, "b");
        }, "t2");
        t3 = new Thread(() -> {
            pu.print(t1, "c");
        }, "t3");

        t1.start();
        t2.start();
        t3.start();

        Thread.sleep(1000);
        LockSupport.unpark(t1);
    }
}

class ParkUnpark {

    private int cycle;

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

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

// 运行结果

abcabcabcabcabc
进程已结束,退出代码 0

说些废话

本篇文章为博主日常学习记录,故而会小概率存在各种错误,若您在浏览过程中发现一些,请在评论区指正,望我们共同进步,谢谢!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值