线程模式总结

1.同步模式之固定顺序输出

t1,t2都运行,谁先运行是不确定的,取决于处理器的调度
想要控制线程顺序:让t2先运行

(1)wait和notify控制

    static boolean t2IsRunned = false;//t2是否运行
    static final Object obj = new Object();//资源

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (obj) {
                while (!t2IsRunned) {
                    try {
                        log.debug("t1,wait");
                        obj.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                log.debug("t1运行");
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            synchronized (obj) {
                log.debug("t2运行");
                t2IsRunned = true;
                obj.notify();
            }
        }, "t2");
        t1.start();
        t2.start();
    }

(2)ReentrantLock的await和signal

    static boolean t2IsRunned = false;//t2是否运行
    static final ReentrantLock obj = new ReentrantLock();//资源
    static Condition condition = obj.newCondition();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {
                obj.lock();
                log.debug("t1上锁");
                while (!t2IsRunned) {
                    try {
                        log.debug("t1等待");
                        condition.await();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                log.debug("t1运行");
            }finally {
                obj.unlock();
                log.debug("t1解锁");
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            try {
                obj.lock();
                log.debug("t2上锁");
                log.debug("t2运行");
                t2IsRunned = true;

                log.debug("t2唤醒t1");
                condition.signal();
            }finally {
                obj.unlock();
                log.debug("t2解锁");
            }
        }, "t2");
        t1.start();
        t2.start();
    }

(3)park和unpark

public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            log.debug("t1-park停止");
            LockSupport.park();
            log.debug("t1运行");
        }, "t1");

        Thread t2 = new Thread(() -> {
            log.debug("运行t2");
            log.debug("t1补充");
            LockSupport.unpark(t1);

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

    }

2.同步模式之交替输出

需求:
线程t1打印a;t2打印b;t3打印c。需要打印n轮abc

(1)wait和notify

思想:
1.设计一个flag,当flag为1时,t1执行,然后把资源期待得到的flag变为2,依次类推。
2.当不足条件,则wait
3.条件满足,执行,改变期待的flag,然后唤醒其他线程。

@Slf4j(topic = "c.test1")
public class test1 {
    public static void main(String[] args) {
        Print print = new Print(1, 3);
        Thread t1 = new Thread(() -> {
            print.print("a", 1, 2);
        }, "t1");

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

        }, "t2");

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

        }, "t3");

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

/**
 * 打印的执行类(资源)
 */
class Print {
    private Integer flag;//标记.1-t1;2-t2;3-t3
    private Integer loop;//打印的轮数

    public Print(Integer flag, Integer loop) {
        this.flag = flag;
        this.loop = loop;
    }

    /**
     * 线程调用的打印方法
     *
     * @param content  打印的内容
     * @param waitFlag 等待执行的标记
     * @param nextFlag 执行后下一个执行线程的标记
     */
    public void print(String content, Integer waitFlag, Integer nextFlag) {
        for (int i = 0; i < loop; i++) {
            synchronized (this) {
                while (flag != waitFlag) {
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                //就是自己执行
                System.out.println(content);
                flag = nextFlag;
                this.notifyAll();
            }
        }
    }
}

(2)ReentrantLock的await和signal

ReentrantLock也可以用上面的方式(用flag控制是不是自己运行,不是则wait)的方式运行,即让Print类实现ReentrantLock即可。

但是根据其多个变量的(多个WaitSet)的特点,可以用3个变量来实现。
思想:先把三个线程分别放在自己的变量中await,然后main唤醒t1,t1执行完,唤醒t2,依次类推即可

这个例子中没有用while去防止虚假唤醒,是因为每个阻塞队列只有一个线程。在正常的情况下,一定要记得使用while防止虚假唤醒

@Slf4j(topic = "c.test1")
public class test1 {
    public static void main(String[] args) {
        Print print = new Print(3);
        Condition condition1 = print.newCondition();
        Condition condition2 = print.newCondition();
        Condition condition3 = print.newCondition();

        Thread t1 = new Thread(() -> {
            print.print("a", condition1, condition2);
        }, "t1");

        Thread t2 = new Thread(() -> {
            print.print("b", condition2, condition3);

        }, "t2");

        Thread t3 = new Thread(() -> {
            print.print("c", condition3, condition1);

        }, "t3");

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

        //等1s,为了让三个线程都在各自的变量里阻塞
        try {
            sleep(100);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        //主线程唤醒t1
        print.lock();//主线程必须先上锁,再区唤醒
        try {
            condition1.signal();
        }finally {
            print.unlock();
        }
    }
}

/**
 * 打印的执行类(资源)
 */
class Print extends ReentrantLock {

    private Integer loop;//打印的轮数

    public Print(Integer loop) {
        this.loop = loop;
    }


    /**
     * 线程调用的打印方法
     *
     * @param content      打印的内容
     * @param myCondition  当前线程await的waitSet
     * @param nextConditin 下一个线程await的waitSet
     */
    public void print(String content, Condition myCondition, Condition nextConditin) {
        for (int i = 0; i < loop; i++) {
            this.lock();
            try {
                try {
                    //全都先await
                    myCondition.await();
                    //被唤醒(达到执行条件)执行
                    System.out.println(content);
                    nextConditin.signal();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }finally {
                this.unlock();
            }
        }
    }
}

(3)park和unpark

park会阻塞当前线程,unpark会唤醒指定线程

@Slf4j(topic = "c.test1")
public class test1 {
    //声明成员变量:1。会初始化;2.t1的时候就要传入t2,提前声明
    static Thread t1;
    static Thread t2;
    static Thread t3;
    public static void main(String[] args) {
        Print print = new Print(3);

        t1 = new Thread(() -> {
            print.print("a",  t2);
        }, "t1");

        t2 = new Thread(() -> {
            print.print("b",  t3);

        }, "t2");

        t3 = new Thread(() -> {
            print.print("c", t1);

        }, "t3");

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

        //unpark t1
        LockSupport.unpark(t1);

    }
}

/**
 * 打印的执行类(资源)
 */
class Print{

    private Integer loop;//打印的轮数

    public Print(Integer loop) {
        this.loop = loop;
    }


    /**
     * 线程调用的打印方法
     * @param content 打印的内容
     * @param next 要唤醒的下一个线程
     */
    public void print(String content, Thread next) {
        for (int i = 0; i < loop; i++) {
            LockSupport.park();//如果该他执行了,就会有一个unpark的存储,这个park就不会阻塞住
            System.out.println(content);
            LockSupport.unpark(next);
        }
    }
}

3.同步模式之Balking模式

Balking模式是指线程发现前面已经有线程做了一件事,自己就不用做这件事了

举例
有一个监控线程执行监控任务,但是这个监控任务只需要执行一次
1.那么就可以设置一个volatile变量,在第一个线程执行监控任务之后,修改这个变量,让后面的线程不用再做了
2.另外为了保证这个变量修改过程的原子性,还需要对这个过程加锁

在这里插入图片描述

4.终止模式之两阶段终止模式

错误的两种终止线程的方式:

  • 使用线程对象的 stop() 方法停止线程:stop 方法会真正杀死线程,如果这时线程锁住了共享资源,当它被杀死后就再也没有机会释放锁,其它线程将永远无法获取锁
  • 使用 System.exit(int) 方法停止线程:目的仅是停止一个线程,但这种做法会让整个程序都停止

(1)通过打断的方式实现

两阶段终止模式:在一个线程 T1 中如何优雅终止线程 T2?优雅指的是给 T2 一个后置处理器

应用:2秒一次进行监控,若需要不监控,可以优雅的停止监控行为
1.当正常情况被打断,有一个料理后事的时间,然后终止
2.进入睡眠模式被打算(异常),设置标记,重新进入循环,有一个料理后事的时间,然后终止

这样的化,无论是正常情况下被打断还是睡眠情况下被打断,都会有一个料理后事的时间

在这里插入图片描述

public class Test {
    public static void main(String[] args) throws InterruptedException {
        TwoPhaseTermination tpt = new TwoPhaseTermination();
        tpt.start();
        Thread.sleep(3500);
        tpt.stop();
    }
}
class TwoPhaseTermination {
    private Thread monitor;
    // 启动监控线程
    public void start() {
        monitor = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    Thread thread = Thread.currentThread();
                    if (thread.isInterrupted()) {
                        System.out.println("后置处理");
                        break;
                    }
                    try {
                        Thread.sleep(1000);					// 睡眠
                        System.out.println("执行监控记录");	//1. 在此被打断不会异常,仅仅是改变了标记为true,会进入下一次循																环,在代码19行进行后置处理
                        
                    } catch (InterruptedException e) {		// 在睡眠期间被打断的化,进入异常处理的逻辑
                        e.printStackTrace();
                        // 2.重新设置打断标记,打断 sleep 会清除打断状态为true
                        //结束后继续进入下一次循环,然后在代码19行进行后置处理
                        thread.interrupt();
                    }
                }
            }
        });
        monitor.start();
    }
    // 停止监控线程
    public void stop() {
        monitor.interrupt();
    }
}

(2)通过volatile变量实现

上面实现两阶段终止模式
​ 可以看出,之前想要一个线程A打断线程B,是通过monitor.interrupt();进行打断
​ 1.在正常运行情况下,修改了打断标记位true,从而在下一次循环进入if语句进行后置处理
​ 2.在睡眠情况下,进入异常catch块修改打断标记,从而在下一次循环进入if语句进行后置处理

这样的问题就是需要通过打断标记来判断进步进入后置处理,就很容易出问题(为啥容易出问题?是因为打断标记容易被修改吗?)

所以考虑设置一个volatile标记变量,在打断的时候修改这个变量,则在下一次循环的时候,就可以不依靠打断标记,而依靠这个变量标记,看是否进入后置处理。
在这里插入图片描述

5.同步模式之保护性暂停

(1)基本实现

保护性暂停就是让一个线程等待另一个线程的执行结果,然后在执行

怎么做?
使用一个资源对象GuardedObject(操作等待资源和赋值资源两个操作),等待的线程A判断这个资源对象的资源是否为空,来判断被等待的线程B是否已经完成了。

class GuardedObject {
 	private Object response;
 	private final Object lock = new Object();
 	//等待资源的线程调用
 	public Object get() {
		synchronized (lock) {
			// 条件不满足则等待
			while (response == null) {
				try {
					lock.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			return response;
		}
	}
	//先执行的线程执行,填充结果
 	public void complete(Object response) {
		synchronized (lock) {
			// 条件满足,通知等待线程
			this.response = response;
			lock.notifyAll();
		}
	 }
}

//使用
public static void main(String[] args) {
//资源对象
 	GuardedObject guardedObject = new GuardedObject();
 	new Thread(() -> {
		try {
		// 子线程执行下载
		List<String> response = download();
		log.debug("download complete...");
		//子线程完成任务后,给资源对象的资源赋值
		guardedObject.complete(response);
		} catch (IOException e) {
			e.printStackTrace();
		}
 	}).start();
 	log.debug("waiting...");
 	// 主线程调用了get,阻塞等待。子线程结束后,获得了结果,阻塞结束
 	Object response = guardedObject.get();
 	log.debug("get response: [{}] lines", ((List<String>) 				response).size());
}

(2)给等待设置超时时间

为了让正在等待的线程有一个时间的限制,可以给设置一个超时时间
要注意的点:
1.wait方法也要传入一个时间,设置等待的时间。
2.但是这个给wait传入的时间不是真正的这个超时时间,因为等待之后,要重新进入while循环,如果一直都是一个时间,就是有问题的。这个传入的时间应该时还需要等待多久的时间。
在这里插入图片描述

join底层就是使用了设置超时时间的保护性暂停
在这里插入图片描述

(3)多任务处理

在这里插入图片描述
在这里插入图片描述

思想:
1.GuardedObject类用于进行等待信件和投递信件的造作
2.因为不同的邮递员给不同的邮箱,所以GuardedObject对象是使用id进行区分绑定的
3.需要个邮箱类,进行对GuardedObject对象进行操作

GuardedObject负责等待结果和产生结果

    class GuardedObject {
        // 标识 Guarded Object
        private int id;//id负责表示GuardedObject 

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

        public int getId() {
            return id;
        }

        // 结果
        private Object response;

        // 获取结果
        // timeout 表示要等待多久 2000
        public Object get(long timeout) {
            synchronized (this) {
                // 开始时间 15:00:00
                long begin = System.currentTimeMillis();
                // 经历的时间
                long passedTime = 0;
                while (response == null) {
                    // 这一轮循环应该等待的时间
                    long waitTime = timeout - passedTime;
                    // 经历的时间超过了最大等待时间时,退出循环
                    if (timeout - passedTime <= 0) {
                        break;
                    }
                    try {
                        this.wait(waitTime); // 虚假唤醒 15:00:01
                    } catch (InterruptedException e) {
                        e.printStackTrace();

                    }
                }
                // 求得经历时间
                passedTime = System.currentTimeMillis() - begin; // 15:00:02 1s
            }
            return response;
        }


        // 产生结果
        public void complete(Object response) {
            synchronized (this) {
                // 给结果成员变量赋值
                this.response = response;
                this.notifyAll();
            }
        }
    }

用于解耦的邮箱类(用邮箱类绑定操作类,进行解耦)

在这里插入图片描述
用户类和邮递员类
在这里插入图片描述
执行类
在这里插入图片描述

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

在这里插入图片描述

思想:
主要就是一个消息队列。当空的时候,取操作wait;满的时候放操作wait。
取和放的操作要加锁

消息类
在这里插入图片描述
消息队列类
在这里插入图片描述
应用
在这里插入图片描述

7.异步模式之工作线程

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到上面的代码是一个固定大小为2的线程池,当创建两个线程,两个线程都开始执行点菜的操作,此时就会产生饥饿

解决饥饿:不同任务类型使用不同线程池

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值