JUC并发编程(上)

一、JUC学习准备

核心知识点:进程、线程、并发(共享模型、非共享模型)、并行

预备知识:
基于JDK8,对函数式编程、lambda有一定了解
采用了slf4j打印日志
采用了lombok简化java bean编写

二、进程与线程

进程和线程概念

两者对比

并行和并发

操作系统cpu任务调度器

同一时间能够应对多件事件的能力称为并发

应用

三、java线程

创建线程

方法一:使用Thread

创建线程对象

给线程命名t1

启动线程

@Slf4j(topic = "c.Test1")
public class Test1 {
    public static void main(String[] args) {
        Thread t = new Thread(){
            @Override
            public void run() {
                log.debug("running");
            }
        };
        t.setName("t1");
        t.start();
        log.debug("running");
    }
}

方法二:使用Runnable

把线程和任务分开

@Slf4j(topic = "c.Test2")
public class Test2 {
    public static void main(String[] args) {
      Runnable  r = new Runnable() {

           @Override
           public void run() {
               log.debug("running");
           }
       };
       Thread t = new Thread(r,"t2");
       t.start();
    }
}

使用lambda简化

idea快捷键alt+enter

@Slf4j(topic = "c.Test2")
public class Test2 {
    public static void main(String[] args) {
      Runnable  r = () -> log.debug("running");
       Thread t = new Thread(r,"t2");
       t.start();
    }
}

Thread与Runnable的关系
 

方法三:使用FutureTask

间接使用了Runnable

能够接收Callable

可以把任务的结果传给其他线程

@Slf4j(topic = "c.Test3")
public class Test3 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                log.debug("running");
                Thread.sleep(1000);
                return 0;
            }
        });
        Thread t1 = new Thread(task, "t1");
        t1.start();

        log.debug("{}", task.get());
    }
}

线程运行

现象-两个线程交替运行

@Slf4j(topic = "c.TestMultiThread")
public class Test4 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        new Thread(() -> {
            while (true) {
                log.debug("running");
            }
        }, "t1").start();

        new Thread(() -> {
            while (true) {
                log.debug("running");
            }
        }, "t2").start();
        
    }
}

查看进程线程

线程运行原理

原理

图解

多线程

多线程的栈桢之间互不干扰,相互独立

debug时采用Thread查看

线程上下文切换


线程API

start和run比较

start主要作用启动另一个线程调用run方法,如果直接用创建的线程对象调用run方法,占用的还是主线程。

创建线程后,调用start前处于new状态,未被cpu运行

创建线程后,调用start后处于runnable状态,可以被cpu调度运行

sleep与yield

sleep会让当前调用的线程进入睡眠

睡眠结束后要等待cpu时间片(任务调度器)分配才能得到执行

线程优先级

public class Test6 {
    public static void main(String[] args) {
        Runnable task1 = () -> {
            int count = 0;
            while (true) {
                System.out.println("t1---------->" + count++);
            }
        };
        Runnable task2 = () -> {
            int count = 0;
            while (true) {
                //Thread.yield();
                System.out.println("                t2---------->" + count++);
            }
        };
        Thread t1 = new Thread(task1,"t1");
        Thread t2 = new Thread(task2,"t2");
        t1.setPriority(Thread.MAX_PRIORITY);
        t2.setPriority(Thread.MIN_PRIORITY);
        t1.start();
        t2.start();
    }
    
}

sleep应用

join方法

@Slf4j(topic = "c.Test7")
public class Test7 {
    static int r = 0;
    public static void main(String[] args) {
        test();
    }
    private static void test() {
        log.debug("main start...");
        Thread t1 = new Thread(() -> {
            log.debug("t1 start...");
            try {
                sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            log.debug("t1 end...");
            r = 10;
        }, "t1");
        t1.start();
        log.debug("结果:{}", r);
        log.debug("main end...");
    }
}

join方法作用:等待调用join方法的线程运行结束

在start后加join即可

t1.start();
t1.join();
应用同步

@Slf4j(topic = "c.Test8")
public class Test8 {
    static int r1 = 0;
    static int r2 = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            r1 = 10;
        }, "t1");

        Thread t2 = new Thread(() -> {
            try {
                sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            r2 = 20;
        }, "t2");
        t1.start();
        t2.start();
        long start = System.currentTimeMillis();
        log.debug("join begin");
        t1.join();
        log.debug("t1 join end");
        t2.join();
        log.debug("t2 join end");
        long end = System.currentTimeMillis();
        log.debug("r1:{},r2:{},cost:{}", r1, r2, end - start);
    }
}

多线程start时已经开始运行,当join调用时,如果线程已经运行结束,则不会等待。

限时同步

超过join的等待时间,则不会等待线程的结果。

如果设置的join等待时间内,线程提前结束,也会提前结束等待。

interrupt方法

打断sleep,wait,join的阻塞线程

打断特殊的阻塞线程会清空打断状态,打断状态重置为false,正常为true

打断正常的运行线程

主线程调用interrupt方法只是通知打断t1线程,但不会真的打断,此时t1线程的打断标记为true,t1线程可以通过打断标记决定是否中断自己。

@Slf4j(topic = "c.Test9")
public class Test9 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while (true) {
               boolean interrupted = Thread.currentThread().isInterrupted();
               if (interrupted) {
                   log.debug("线程被中断");
                   break;
               }
            }
        }, "t1");
        t1.start();

        sleep(1000);
        log.debug("打断t1线程");
        t1.interrupt();
    }
}
设计模式:两阶段终止模式

1.启动监控线程

2.定时循环执行监控记录

3.在循环过程中判断监控线程是否被打断

4.打断标记为true时执行打断操作

@Slf4j(topic = "c.Test10")
public class Test10 {
    public static void main(String[] args) throws InterruptedException {
        TwoPhaseTermination tpt = new TwoPhaseTermination();
        tpt.start();
        sleep(3500);
        tpt.stop();
    }
}
@Slf4j(topic = "c.TwoPhaseTermination")
class TwoPhaseTermination {
    private Thread monitor;
    //启动监控线程
    public void start() {
        monitor = new Thread(() -> {
            while (true) {
                Thread current = Thread.currentThread();
                if(current.isInterrupted()) {
                    log.debug("料理后事");
                    break;
                }
                try {
                    sleep(2000);//情况一:阻塞被打断
                    log.debug("执行监控记录");//情况二:正常运行被打断
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    monitor.interrupt();
                }
            }
        });
        monitor.start();
    }
    //停止监控线程
    public void stop() {
        monitor.interrupt();
    }
}

打断park

park被打断后,此时打断状态为true,如果继续执行park会失效,可以将状态设为false后恢复。

过时方法

守护线程

线程状态

五种状态

六种状态

 

四、共享模型之管程

悲观锁思想解决并发问题

共享问题

由线程上下文切换引起指令交错导致的多线程访问共享资源的线程安全问题

静态变量 i 存储在主内存中,多个线程都可以读取 i 值,计算是在线程中完成,完成后需写回主内存。如果计算完成还未写入主存时出现上下文切换则会导致线程安全问题。

临界区

竞态条件


synchronized

需要一个多线程共享的对象

语法

@Slf4j(topic = "c.Test11")
public class Test11 {
    static int count = 0;
    static final Object lock = new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (lock) {
                    count++;
                }
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (lock) {
                    count--;
                }
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.debug("count:{}", count);
    }
}

理解

线程1获取锁对象后,如果临界区还未执行完,发生上下文切换到其他线程获取不到会被blocked,继续上下文切换,直到临界区代码执行结束释放锁,唤醒阻塞的线程,其他线程才能获取到锁。

synchronized放在for循环外面,会保证整个循环的原子性

如果多线程获取的不是同一个锁对象,不能保证线程安全;要给同一对象加锁保护共享资源

如果t1加锁,t2未加锁,上下文切换到t2时,此时不需要获取对象锁,则不会被阻塞,t2线程会运行

锁对象面向对象改进

@Slf4j(topic = "c.Test12")
public class Test12 {
    public static void main(String[] args) throws InterruptedException {
        Room room = new Room();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                room.add();
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                room.reduce();
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.debug("count:{}", room.getCount());
    }
}

@Slf4j(topic = "c.room")
class Room {
    private static int count;

    public void add() {
        synchronized (this) {
            count++;
        }
    }

    public void reduce() {
        synchronized (this) {
            count--;
        }
    }

    public int getCount() {
        synchronized (this) {
            return count;
        }
    }
}

方法上的synchronized

成员方法:锁this对象

静态方法:锁类对象


线程安全分析

变量线程安全分析

局部变量的自增和成员变量自增不同。局部变量只用了一步

局部变量在栈中互相独立

子类继承父类并重写方法,可能会再加一个线程,此时有多个线程访问共享资源list,造成局部变量引用暴露给了其它线程,有线程安全问题

private修饰父类方法会限制子类不能覆盖

final修饰父类方法防止子类重写

常见线程安全类

多方法组合不能保证原子性

不可变线程安全性

通过如下源码可知,这些方法重新创建了一个新的字符串对象,并把新值赋给新字符串的value,没有改变原有的String对象

无状态(成员变量)的类一般是线程安全的

如果有成员变量,最好变成局部变量,否则要看多线程访问是否是同一个

如果是成员变量,看是否向外暴露引用。如下所示局部变量向外泄露导致并发不安全

Monitor概念

java对象头

对象头占8个字节,对象的类型通过Klass Word指针找到

Monitor工作原理

图文角度

字节码角度

synchronized进阶原理

轻量级锁

加锁、锁重入、解锁

锁膨胀

自旋优化

锁竞争发生阻塞会进行上下文切换比较耗时,通过自旋(循环重试获取锁)优化

自旋需要cpu,不适合单核。

偏向锁

加偏向锁前、中、后测试代码

测试hashCode()

调用hashCode会禁用偏向锁,因为对象头没有空间存储31位hashcode,需要将偏向状态变成正常状态,再将hashcode放进去。

在轻量级锁中,hashcode存储在线程栈桢的锁记录中

在重量级锁中,hashcode存储在monitor对象中

让两个线程错开访问锁对象,如果发生交替会产生锁竞争

批量重偏向

批量撤销

锁消除

java代码在编译时会对代码进行锁优化,如果发现代码中加的锁没有任何意义,JIT会把锁优化掉。

如图局部变量o不会逃离方法作用范围,表明对象不会被共享,代码真正执行时没有sychronized。

锁消除禁用:-XX:-EliminateLocks

wait/notify

当一个线程占着锁后,由于需要一些条件暂时无法工作,导致后续想获取锁的线程阻塞,此时用wait方法该线程进入waitset并释放锁,让其他线程运行。等待条件满足通知该线程离开waitset,进入竞争锁队列entrylist

工作原理

API

线程必须先获取到锁对象后才能调用wait方法,进入该锁对象的休息室

wait(1000)代表等待一秒后,退出等待,如果等待时间内被唤醒,则提前退出等待

wait和sleep区别

wait/notify正确用法

1.如果只有一个线程wait,等待另一个线程将条件满足后notify即可

2.如果有多个线程wait,还用notify可能会出现虚假唤醒,可以改用notifyAll唤醒全部;此时肯定唤醒了其他不必要的线程,可以使用循环条件判断 while 让他们再次进入wait等待下次唤醒。

同步模式之保护性暂停

定义

代码实现

1.使用join后线程不能执行其他操作,而保护性暂停可以在等待过程中执行其他操作

2.使用join获得的结果的变量是全局的,而保护性暂停的变量是局部的

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

    public static void main(String[] args) throws InterruptedException {
        GuardedObject guardedObject = new GuardedObject();
        Thread t1, t2;
        t1 = new Thread(() -> {
            log.debug("等待结果..");
            String result = (String) guardedObject.get();
            log.debug("结果是:{}", result);
        }, "t1");

        t2 = new Thread(() -> {
            log.debug("进行下载..");
            //  等待下载完成结果
            try {
                sleep(1000);
                String result = "下载完成";
                guardedObject.complete(result);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "t2");
        t1.start();
        t2.start();
    }
}

class GuardedObject {
    private Object response;

    public Object get() {
        synchronized (this) {
            while (response == null) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return response;
        }
    }

    public void complete(Object response) {
        synchronized (this) {
            this.response = response;
            this.notifyAll();
        }
    }

}

扩展--增加超时等待

1.记录等待时间和经历时间:超时时间 - 经历时间 = 等待时间

2.防止虚假唤醒对等待时间的影响

join原理

join源码使用的就是保护性暂停模式

扩展--解耦中间类

产生结果线程和消费结果线程一一对应

收信人通知信箱要收信,并准备接收;

信箱创建邮件并附上唯一的id,每创建一份就把邮件放到集合里;

快递员拿到邮件id和邮件内容开始送信,信件送达后通知收信人。

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

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 3; i++) {
            new People().start();
        }

        sleep(1000);

        for (Integer id : Mailboxes.getIds()) {
            new Postman(id, "mail" + id).start();
        }
    }
}
@Slf4j(topic = "c.People")
class People extends Thread {
    @Override
    public void run() {
       //收信
        GauardedObject gauardObject = Mailboxes.createGauardedObject();
        log.debug("开始收信 id:{}", gauardObject.getId());
        Object mail = gauardObject.get(5000);
        log.debug("收到信 id:{} mail:{}", gauardObject.getId(), mail);
    }
}

@Slf4j(topic = "c.Postman")
class Postman extends Thread {
    private int id;
    private String mail;

    public Postman(int id, String mail) {
        this.id = id;
        this.mail = mail;
    }

    @Override
    public void run() {
        GauardedObject gauardedObject = Mailboxes.getGauardedObject(id);
        log.debug("开始送信 id:{} mail:{}", id, mail);
        gauardedObject.complete(mail);
    }
}

class Mailboxes {
    private static Map<Integer, GauardedObject> boxes = new Hashtable<>();

    private static int id = 1;

    private static synchronized int generateId() {
        return id++;
    }

    public static GauardedObject getGauardedObject(int id) {
        return boxes.remove(id);
    }
    public static GauardedObject createGauardedObject() {
        GauardedObject go = new GauardedObject(generateId());
        boxes.put(go.getId(), go);
        return go;
    }

    public static Set<Integer> getIds() {
        return boxes.keySet();
    }

}

class GauardedObject {

    private int id;

    public GauardedObject(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    private Object response;

    public Object get(long timeout) {
        synchronized (this) {
            long passedtime = 0;
            long start = System.currentTimeMillis();
            while (response == null) {
                long waitTime = timeout - passedtime;
                if (waitTime < 0){
                    break;
                }
                try {
                    this.wait(waitTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                passedtime  = System.currentTimeMillis() - start;
            }
            return response;
        }
    }

    public void complete(Object response) {
        synchronized (this) {
            this.response = response;
            this.notifyAll();
        }
    }
}

异步模式之生产者/消费者

定义

存放在消息队列里的消息不会立刻被消费,所以归类为异步。

注:这个消息队列是线程间通信的,而RabbitMQ里消息队列是进程间通信的

消息队列里要有容量限制

填充队列时要检查是否容量已满

拿去消息时要检查队列消息是否为空

代码实现

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

    public static void main(String[] args) throws InterruptedException {
        MessageQueue queue = new MessageQueue(2);
        for (int i = 0; i < 3; i++) {
            int id = i;
            new Thread(() -> {
                queue.put(new Message(id, "msg" + id));
                log.debug("id:{},生产消息:{}", id, "msg" + id);
            }, "生产者" + i).start();
        }
        sleep(1000);
        new Thread(() -> {
            while (true) {
                Message message = queue.take();
                log.debug("id:{},消费消息:{}", message.getId(), message.getValue());
            }
        }).start();
    }
}

@Slf4j(topic = "c.MessageQueue")
class MessageQueue {
    private int capacity;
    private LinkedList<Message> queue = new LinkedList<>();

    public MessageQueue(int capacity) {
        this.capacity = capacity;
    }

    public Message take() {
        synchronized (this) {
            while (queue.isEmpty()) {
                log.debug("队列为空,等待消息填充!");
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            this.notifyAll();
            return queue.removeFirst();
        }
    }

    public void put(Message message) {
        synchronized (this) {
            while (queue.size() == capacity) {
                log.debug("队列已满,等待消费!");
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            queue.addLast(message);
            this.notifyAll();
        }
    }
}

final class Message {
    private int id;
    private Object value;

    public Message(int id, Object value) {
        this.id = id;
        this.value = value;
    }

    public int getId() {
        return id;
    }

    public Object getValue() {
        return value;
    }
}

park&unpark

与wait&notify区别

原理

线程状态转换

单向箭头表示只能一种状态向另一种状态转换

双向箭头表示可以互相转换

多把锁

如图所示,房间可以睡觉和学习,此时如果有人要睡觉,会把屋子锁上,而要进屋学习的人就必须等待睡觉结束,导致并发度很低。显然两者不冲突,需要优化。

用多把锁优化(保证业务不关联)

将锁的粒度细分
好处,是可以增强并发度
坏处,如果一个线程需要同时获得多把锁,就容易发生死锁

 

线程活跃性

死锁

死锁定位
方法一:jstack

利用  jps 定位线程id

使用  "jstack 线程id" 命令查看

方法二:jconsole

命令行输入 jconsole 打开

连接到所在线程

切换线程窗口,检测死锁

哲学家就餐

问题原因:syschronized获取不到锁会一直等待。

public class Test21 {
    public static void main(String[] args) throws InterruptedException {
        Chopstick c1 = new Chopstick("C1");
        Chopstick c2 = new Chopstick("C2");
        Chopstick c3 = new Chopstick("C3");
        Chopstick c4 = new Chopstick("C4");
        Chopstick c5 = new Chopstick("C5");

        new philosopher("p1", c1, c2).start();
        new philosopher("p2", c2, c3).start();
        new philosopher("p3", c3, c4).start();
        new philosopher("p4", c4, c5).start();
        new philosopher("p5", c5, c1).start();
    }
}

@Slf4j
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) {
            synchronized (left) {
                synchronized (right) {
                    eat();
                }
            }
        }
    }

    private void eat() {
        log.debug("eating....");
        try {
            sleep(500);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

class Chopstick{
    String name;

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

活锁

解决活锁方法:让两个线程指令交错执行,下图的活锁问题可以通过增加一个线程的睡眠时间解决

饥饿

死锁问题是两个线程要获取对方拥有的锁

顺序加锁是让两个线程都先获取A锁,必然有一个线程获取不到锁导致阻塞,另一个线程就能再次获取到B锁

造成问题:有的锁可能会一直得不到锁,导致无法得到执行。

ReentrantLock

相对于synchronized它具备如下特点
可中断(syschronized锁加上后不可被其他方法中断破坏)
可以设置超时时间(syschronized的阻塞线程会进入EntryList里一直等待,而RL可以设置等待时间,超过时间会放弃锁竞争)
可以设置为公平锁(防止饥饿锁问题)
支持多个条件变量(多个细分的休息室)
与synchronized一样,都支持可重入(同一线程可以对同一对象反复加锁)

可重入锁

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

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

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

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

可打断

lock方法不支持打断

加入打断机制,防止无限制的运行,避免产生死锁

其他线程调用interrupt打断无限制的等待

public class Test20 {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                log.debug("尝试获取锁");
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                log.debug("没有获取到锁");
                e.printStackTrace();
                return;
            }
            try {
                log.debug("获取到锁");
            } finally {
                lock.unlock();
            }
        }, "t1");

        lock.lock();
        t1.start();
        log.debug("开启打断");
        t1.interrupt();
    }
}

锁超时

主动避免无限制等待的手段

超过等待时间,主动放弃获取锁,避免死锁

加入等待时间,且tryLock也支持打断

解决哲学家就餐问题

将Chopstick的对象做为一个ReentrantLock的锁对象

在获取不到锁时主动释放锁,从而避免了每个人都拿一个筷子的死锁问题

public class Test21 {
    public static void main(String[] args) throws InterruptedException {
        Chopstick c1 = new Chopstick("C1");
        Chopstick c2 = new Chopstick("C2");
        Chopstick c3 = new Chopstick("C3");
        Chopstick c4 = new Chopstick("C4");
        Chopstick c5 = new Chopstick("C5");

        new philosopher("p1", c1, c2).start();
        new philosopher("p2", c2, c3).start();
        new philosopher("p3", c3, c4).start();
        new philosopher("p4", c4, c5).start();
        new philosopher("p5", c5, c1).start();
    }
}

@Slf4j
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....");
        try {
            sleep(500);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

class Chopstick extends ReentrantLock{
    String name;

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

公平锁

不公平锁:在锁释放时,处于阻塞队列的线程抢锁,谁抢到谁执行,不按照在阻塞队列的顺序先入先得获得锁

ReentrantLock默认时不公平的

条件变量

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

        Thread t1 = new Thread(() -> {
            ROOM.lock();
            try {
                log.debug("有烟没?");
                while (!hasCigarette) {
                    log.debug("没有烟,先去休息!");
                    try {
                        waitCigaretteSet.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("可以开始干活了");
            } finally {
                ROOM.unlock();
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            ROOM.lock();
            try {
                log.debug("外卖到了没?");
                while (!hasTakeout) {
                    log.debug("没有外卖,先去休息!");
                    try {
                        waitTakeoutSet.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有外卖了,开始干活!");
            } finally {
                ROOM.unlock();
            }
        }, "t2");

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

        Thread.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();
    }
}

同步模式之顺序控制(先2后1)

固定运行顺序--wait notify

用一个变量记录t2是否执行,t1根据变量来决定是否要等待。

@Slf4j(topic = "c.Test23")
public class Test23 {
    static Object lock = new Object();
    static boolean t2Runned = false;
    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            synchronized (lock) {
                while (!t2Runned) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                log.debug("t1");
            }
        }, "t1").start();

        new Thread(() -> {
            synchronized (lock) {
                log.debug("t2");
                t2Runned = true;
                lock.notifyAll();
            }
        }, "t2").start();

    }
}

固定运行顺序--await signal

@Slf4j(topic = "c.Test23")
public class Test23 {
    static ReentrantLock lock = new ReentrantLock();
    static boolean t2Runned = false;
    static Condition t1Room = lock.newCondition();
    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            lock.lock();
            try {
                while (!t2Runned) {
                    try {
                        t1Room.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("t1");
            } finally {
                lock.unlock();
            }
        }, "t1").start();

        new Thread(() -> {
                lock.lock();
                try {
                    log.debug("t2");
                    t2Runned = true;
                    t1Room.signal();
                } finally {
                    lock.unlock();
                }
        }, "t2").start();
    }
}

固定运行顺序--park unpark

@Slf4j(topic = "c.Test24")
public class Test24 {
    public static void main(String[] args) throws InterruptedException {
     Thread t1 = new Thread(()->{
          LockSupport.park();
          log.debug("t1");
      },"t1");

      Thread t2 = new Thread(()->{
          log.debug("t2");
          LockSupport.unpark(t1);
      },"t2");
      
      t1.start();
      t2.start();
    }
}

交替输出(1输出a5次,2输出b5次,3输出c5次)

交替输出--wait notify

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

class WaitNotify {
    private int flag;
    private int loopNum;

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

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

交替输出--await signal

全部线程先进入各自的休息室,主线程唤醒t1。

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

    static ReentrantLock lock = new ReentrantLock();
    static Condition t1Room = lock.newCondition();
    static Condition t2Room = lock.newCondition();
    static Condition t3Room = lock.newCondition();

    public static void main(String[] args) throws InterruptedException {
        AwaitSignal awaitSignal = new AwaitSignal(5, lock);
        new Thread(() -> {
            awaitSignal.print("a", t1Room, t2Room);
        }, "t1").start();
        new Thread(() -> {
            awaitSignal.print("b", t2Room, t3Room);
        }, "t2").start();
        new Thread(() -> {
            awaitSignal.print("c", t3Room, t1Room);
        }, "t3").start();
        Thread.sleep(1000);
        lock.lock();
        try {
            t1Room.signal();
        } finally {
            lock.unlock();
        }
    }
}

class AwaitSignal {
    private int loopNum;
    private ReentrantLock lock;

    public AwaitSignal(int loopNum, ReentrantLock lock) {
        this.loopNum = loopNum;
        this.lock = lock;
    }
    public void print(String name, Condition currentRoom, Condition nextRoom) {
        for (int i = 0; i < loopNum; i++) {
            lock.lock();
            try {
                currentRoom.await();
                System.out.print(name);
                nextRoom.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
}

交替输出--park unpark

线程先全部park,在通过主线程唤醒t1。

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

    static Thread t1, t2, t3;
    public static void main(String[] args) throws InterruptedException {
        t1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                LockSupport.park();
                System.out.print("a");
                LockSupport.unpark(t2);
            }
        }, "t1");
        t2 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                LockSupport.park();
                System.out.print("b");
                LockSupport.unpark(t3);
            }
        }, "t2");
        t3 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                LockSupport.park();
                System.out.print("c");
                LockSupport.unpark(t1);
            }
        }, "t3");
        t1.start();
        t2.start();
        t3.start();
        LockSupport.unpark(t1);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值