欢迎您访问365答案网,请分享给你的朋友!
生活常识 学习资料

第六天第七天

时间:2023-08-01

Author:老九
计算机专业
可控之事 沉重冷静 不可控之事 乐观面对
85180586@qq.com
         ☺️  

      ❓  ❤️ ☕️       ❗️ 
————————————————
版权声明:本文为CSDN博主「浦上青天」的原创文章

文章目录

第五天 第六天复习

线程安全问题阻塞队列定时器

定时器构成

构造方法快捷键 线程池

核心操作 线程总结
知识点 第五天 第六天复习

单例模式:某个类,不应该有多个实例,此时就可以使用单例模式
实现方式:1.懒汉模式2.饿汉模式
饿汉模式:在类加载的时候立刻实例化对象(实例化时机早)
懒汉模式:首次调用getInstance的时候再实例化对象(效率更高)

线程安全问题

1.饿汉模式,是线程安全的,多线程调用getInstance只涉及读操作
2.懒汉模式,在实例化对象之前是线程不安全的(涉及到多线程修改),一旦实例化之后,又是线程安全的
懒汉模式改进方式:
1.加锁(读取判断和new实例操作是原子的)
2.双重if(避免在实例化之后,再去调用getInstance频繁触发不必要的加锁操作)
3.volatile(在instance实例之前加)

阻塞队列

可以用来实现生产者消费者模型
阻塞队列是一个先进先出的队列
出队列的时候,如果发现队列空了,也会阻塞,直到有其他线程调用入队列操作,让队列中有元素,才能继续出队列
入队列的时候如果发现队列满了,就会阻塞,直到有其他线程调用出队列操作让队列中有空位的时候,才能继续入队列

public class ThreadDemo24 { static class BlockingQueue { private int[] array = new int[1000]; private int head = 0; private int tail = 0; //head 和 tail 构成一个前闭后开区间 //区分空还是满 private int size = 0; //阻塞版本入队列 public void put(int value) throws InterruptedException { synchronized (this) { if (size == array.length) { wait(); } array[tail] = value; tail++; if (tail == array.length) { tail = 0; } size++; notify(); } } public int take() throws InterruptedException { int ret = -1; synchronized (this) { if (size == 0) { wait(); } ret = array[head]; head++; if (head == array.length) { head = 0; } size--; notify(); } return ret; } }}

这两个wait不可能同时被调用

定时器

多线程编程中一个重要/常用组件
好比一个闹钟,有些逻辑,并不想立刻执行,而是等一定的时间之后,再来执行
例如,用户服务器,浏览器内部,淘宝服务器都有定时器,就跟闹钟是一样的,如果定时器时间到了,就不等了

定时器构成

1.使用一个类Task来描述“一段逻辑”(一个要执行的任务),同时也要记录这个任务在啥时候执行
阻塞优先队列:既支持阻塞的特性,又支持按优先级“先进先出”
本质上是一个“堆”
2.使用一个阻塞优先队列来组织若干个Task
3.还需要一个扫描线程,扫描线程要循环的检测队首元素需要执行,如果需要执行的话,就执行这个任务
java.util.concurrent 简称juc,包含很多并发编程的包
wait()死等,一直等到notify的通知过来
wait(time),等待是有上限,如果有notify就被提前唤醒,或者跟sleep一样

import javafx.concurrent.Worker;import java.sql.Time;import java.util.concurrent.PriorityBlockingQueue;public class ThreadDemo25 { //优先队列中的元素必须是可比较的 //1.让Task 实现Comparable接口 //2.让优先队列构造的时候,传入一个比较器对象(Comparator) static class Task implements Comparable { //Runnable 中有一个run方法,就可以借助这个run方法,来描述要执行的具体的任务 private Runnable command; //time表示啥时候来执行command,是一个绝对时间 private long time; //构造方法的参数表示,多少毫秒之后执行,(相对时间) public Task(Runnable command, long after) { this.command = command; this.time = System.currentTimeMillis() + after; } //执行任务的具体逻辑 public void run() { command.run(); } @Override public int compareTo(Task o) { return (int) (this.time - o.time); } } static class Worker extends Thread { private PriorityBlockingQueue queue = null; private Object mailBox = null; public Worker(PriorityBlockingQueue queue, Object mailBox) { this.queue = queue; this.mailBox = mailBox; } @Override public void run() { while (true) { //1.取出队首元素,检查时间是否到了 try { Task task = queue.take(); long curTime = System.currentTimeMillis(); if (task.time > curTime) { //时间还没到 queue.put(task); synchronized (mailBox) { mailBox.wait(task.time-curTime); } } else { task.run(); } } catch (InterruptedException e) { e.printStackTrace(); break; } } } } static class Timer { //为了避免忙等,需要使用wait方法 //使用一个单独的对象来辅助进行wait //用this也可以 private Object mailBox = new Object(); //定时器的基本构成,有三个部分 //1.用一个类来描述 “任务” //2.使用阻塞优先队列来组织若干个任务,让队首元素就是时间最早的任务, //如果队首元素时间未到,那么其他元素也肯定不能执行 private PriorityBlockingQueue queue = new PriorityBlockingQueue<>(); //3.用一个线程循环扫描当前的阻塞队列的队首元素,如果时间到 //就执行指定的任务 public Timer() { //创建线程 Worker worker = new Worker(queue,mailBox); worker.start(); } //4.还需要提供一个方法,让调用者能把任务给“安排”进来 public void schedule(Runnable command, long after) { Task task = new Task(command, after); queue.put(task); synchronized (mailBox) { mailBox.notify(); } } public static void main(String[] args) { Timer timer = new Timer(); timer.schedule(new Runnable() { @Override public void run() { System.out.println("hehe"); timer.schedule(this, 2000); } }, 2000); } }}

构造方法快捷键

ALT+INSERT

线程池

已经包含了一些线程,让我们直接去使用
避免了频繁创建/销毁线程的开销

核心操作

execute:把一个任务加到线程池中
shutdown:销毁线程池中的所有线程

线程池的组成部分
1.先有一个类,表示 工作线程
2.还得有一个类,来描述具体线程要做的工作是啥(借助runnable就可以表示
3.还需要有一个数据结构来组织若干个任务,BlockingQueue)
4.还需要一个数据结构,来组织若干个线程List

import java.util.ArrayList;import java.util.List;import java.util.concurrent.BlockingQueue;import java.util.concurrent.linkedBlockingQueue;public class ThreadDemo26 { //使用这个类来描述当前的工作线程是啥样的 static class Worker extends Thread { private int id = 0; private BlockingQueue queue = null; public Worker(BlockingQueue queue,int id) { this.queue = queue; this.id = id; } @Override public void run() { try{ while(!Thread.currentThread().isInterrupted()) { Runnable command = queue.take(); System.out.println("thread"+id+"running..."); command.run(); } } catch (InterruptedException e) { System.out.println("线程被终止"); } } } static class MyThreadPool { //这个阻塞队列用于组织若干个任务 private BlockingQueue queue = new linkedBlockingQueue<>(); //这个List用来组织若干个工作线程 private List workers = new ArrayList<>(); //一个线程池内部应该有多少个线程,需要根据实际情况确定 private static final int maxWorkerCount = 10; //实现execute方法 和 shutdown方法 public void execute(Runnable command) throws InterruptedException { //也是使用延时加载的方式创建线程 //当线程池中线程数目比较少,就新创建线程来作为工作线程 //如果线程数目已经比较多了,就不用新建线程了 if(workers.size()

线程总结

1.进程和线程的基本概念和区别
2.线程控制
a线程创建
b线程终止
c线程等待
d获取线程实例
e线程休眠
3.线程状态
4.线程安全【重中之重】
基本概念:多线程执行某个逻辑出现了逻辑错误
出现的原因:
1.抢占式执行(万恶之源)
2.修改操作不是原子的
3.多线程修改同一个变量
4.内存可见性(volatile,一个线程读,一个线程写,其中读操作被优化成直接取CPU寄存器数据,当写线程进行修改的时候,读线程无法获取到最新的值)
5.指令重排序
解决方案:
最主要的方案:
加锁(原子性)
对象等待集(避免出现线程饿死问题)
wait本质上就是当前线程对应的PCB移动到阻塞队列中,直到notify唤醒的时候再把PCB移动回来
wait内部做了三件事:
1.释放锁
2.等待通知
3.收到通知后尝试重新获取锁

先赞后看,养成习惯!!!^ _ ^♥♥♥
每天都更新知识点哦!!!
码字不易,大家的支持就是我坚持下去的动力。点赞后不要忘记关注我哦!

Copyright © 2016-2020 www.365daan.com All Rights Reserved. 365答案网 版权所有 备案号:

部分内容来自互联网,版权归原作者所有,如有冒犯请联系我们,我们将在三个工作时内妥善处理。