Java 多线程案例

文章目录

  • 1. 多线程案例
    • 1.1 单例模式
    • 1.2 阻塞式队列
  • 2. 定时器
  • 3. 线程池

1. 多线程案例

1.1 单例模式

单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目标是确保一个类只有一个实例,并提供一个全局访问点。在很多情况下,单例模式是非常有用的,例如日志记录、驱动对象、缓存等。

单例模式能保证某个类在程序中只存在唯一一份实例, 而不会创建出多个实例
这一点在很多场景上都需要. 比如 JDBC 中的 DataSource 实例就只需要一个

单例模式具体的实现方式, 分成 “饿汉” 和 “懒汉” 两种

饿汉模式
在这种方式下,实例在类加载时就已经创建,因此,没有任何多线程的问题。这是最简单的一种实现方式,但在类加载的时候就创建实例可能会增加系统的开销

// 恶汉模式的 单例模式实现
// 此处保证 Singleton 这个类只能创建出一个单例
class Singleton {// 先把这个实例创建出来private static Singleton instance  = new Singleton();// 如果需要创建这个实例, 统一使用 Singleton.getInstance() 来获取public static Singleton  getInstance() {return instance;}// 为了避免 Singleton 这个类被创建出多份// 此处把构造方法设置为 private , 此时在类外面就无法通过 new 的方式来创建 Singleton 这个实例了private Singleton() {};
}public class ThreadDemo1 {public static void main(String[] args) {Singleton s1 = Singleton.getInstance();Singleton s2 = Singleton.getInstance();System.out.println(s1 == s2);   // 同一个对象        }
}

懒汉模式 - 单线程版

类加载的时候不创建实例, 类加载的时候才创建实例.

class Singleton2 {private static Singleton2 instance = null;private Singleton2() {}public static Singleton2 getInstance() {if (instance == null) {instance = new Singleton2();}return instance;}
}

上述这个懒汉模式是线程不安全的, 在首次创建实例时, 如果在线程中同时调用getInstance() 方法, 就可能导致创建出多个实例.

加上synchronized可以解决这里的线程安全问题

class Singleton3 {private static Singleton3 instance = null;private Singleton3 () {}public synchronized static Singleton3 getInstance() {if (instance == null) {instance = new Singleton3();}return instance;}
}

在这里我们还可以在改进

  • 使用双重 if 判定, 降低锁竞争的频率
  • 给 instance 加上 volatile
class Singleton4 {private volatile static Singleton4 instance = null;private Singleton4 () {}public static Singleton4 getInstance() {if (instance == null) {synchronized (Singleton4.class) {if (instance == null) {instance = new Singleton4();}}}return instance;}
}

理解双重 if 判定 / volatile:
加锁 / 解锁是一件开销比较高的事情. 而懒汉模式的线程不安全只是发生在首次创建实例的时候.因此后续使用的时候, 不必再进行加锁了.
外层的 if 就是判定下看当前是否已经把 instance 实例创建出来了.
同时为了避免 “内存可见性” 导致读取的 instance 出现偏差, 于是补充上 volatile .
当多线程首次调用 getInstance, 大家可能都发现 instance 为 null, 于是又继续往下执行来竞争锁,
其中竞争成功的线程, 再完成创建实例的操作.当这个实例创建完了之后, 其他竞争到锁的线程就被里层 if 挡住了. 也就不会继续创建其他实例.

1.2 阻塞式队列

阻塞队列是一种特殊的队列. 也遵守 “先进先出” 的原则.
阻塞队列能是一种线程安全的数据结构, 并且具有以下特性:

  • 当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素.
  • 当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素.

阻塞队列的一个典型应用场景就是 “生产者消费者模型”. 这是一种非常典型的开发模型.

生产者消费者模型
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。
生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等
待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取.

  1. 阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力.
  2. 阻塞队列也能使生产者和消费者之间 解耦.

标准库中的阻塞队列

在Java标准库中内置了阻塞队列, 如果我们需要在程序中使用阻塞队列, 直接使用标准库中即可

  • BlockingQueue 是一个接口. 真正实现的类是 LinkedBlockingQueue.
  • put 方法用于阻塞式的入队列, take 方法用于阻塞式的出队列
  • BlockingQueue 也有offer , poll, peek 等方法, 但是这些方法不带有阻塞特性
BlockingQueue<String> queue = new LinkedBlockingQueue<>();
// 入队列
queue.put("abc");
// 出队列. 如果没有 put 直接 take, 就会阻塞.
String elem = queue.take();

生产者消费者模型
生产者消费者模型是一个非常经典的多线程并发协作的模型,在分布式系统里非常常见,它由两类线程和一个缓冲区组成,这两类线程分别是生产者线程和消费者线程,缓冲区存放生产者的数据,生产者往缓冲区中添加数据,消费者从缓冲区中取走数据,当缓冲区满时,生产者阻塞,当缓冲区空时,消费者阻塞。

import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;public class ThreadDemo5 {public static void main(String[] args) throws InterruptedException {//创建一个阻塞队列BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>();// 模拟消费者Thread customer = new Thread(() -> {while (true) {try {int value = blockingQueue.take();System.out.println("消费元素" + value);} catch (InterruptedException e) {throw new RuntimeException(e);}}},"消费者");// 启动线程customer.start();// 模拟生产者Thread product = new Thread(() -> {Random random = new Random();while (true) {try {int num = random.nextInt(1000);System.out.println("生产元素" + num);blockingQueue.put(num);} catch (InterruptedException e) {throw new RuntimeException(e);}}},"消费者");//启动线程product.start();customer.join();product.join();}
}

阻塞队列实现

  • 通过 “循环队列” 的方式来实现.
  • 使用 synchronized 进行加锁控制.
  • put 插入元素的时候, 判定如果队列满了, 就进行 wait. (注意, 要在循环中进行 wait. 被唤醒时不一定队列就不满了, 因为同时可能是唤醒了多个线程).
  • take 取出元素的时候, 判定如果队列为空, 就进行 wait. (也是循环 wait)
public class BlockingQueue {private int[] items =  new int[1000];private volatile int size = 0;private int head = 0;private int tail = 0;public void put(int value) throws InterruptedException {synchronized (this) {while (size == items.length) {wait();}items[tail] = value;tail = (tail + 1) / items.length;size++;notifyAll();}}public int take() throws InterruptedException {int ret = 0;synchronized (this) {while (size == 0) {wait();}ret = items[head];head = (head + 1) / items.length;size--;notifyAll();}return ret;}public synchronized int size() {return size;}public static void main(String[] args) throws InterruptedException {BlockingQueue blockingQueue = new BlockingQueue();Thread customer = new Thread(() -> {while (true) {try {int value = blockingQueue.take();System.out.println("消费元素" + value);} catch (InterruptedException e) {throw new RuntimeException(e);}}},"消费者");customer.start();Thread producer = new Thread(() -> {while (true) {try {Random random = new Random();int ret = random.nextInt(10000);blockingQueue.put(ret);System.out.println("生产元素" + ret);} catch (InterruptedException e) {throw new RuntimeException(e);}}},"生产者");producer.start();customer.join();producer.join();}
}

2. 定时器

定时器是编程语言中用于在指定的时间或时间间隔内执行特定任务的工具

定时器也是软件开发中的一个重要组件. 类似于一个 “闹钟”. 达到一个设定的时间之后, 就执行某个指定好的代码

标准库中的定时器

  • 标准库中提供了一个 Timer 类. Timer 类的核心方法为 schedule
  • schedule 包含两个参数. 第一个参数指定即将要执行的任务代码, 第二个参数指定多长时间之后
    执行 (单位为毫秒)

这里我们简单举个例子

import java.util.Timer;
import java.util.TimerTask;public class ThreadDemo6 {public static void main(String[] args) {Timer timer = new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello 1");}},3000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello 2");}},2000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello 3");}},1000);}
}

在这里插入图片描述

自己实现定时器

import java.util.TimerTask;
import java.util.concurrent.PriorityBlockingQueue;// 核心
// 1. 有一个扫描线程, 负责判定时间到, 执行任务
// 2. 还需要一个数据结构, 来保存所有的被注册的任务(优先级队列)// 使用这个类来表示一个定时器中的任务
class MyTask implements Comparable<MyTask> {// 要执行的任务private Runnable runnable;// 任务在啥时候执行(使用一个毫秒级时间戳来表示)private long time;public MyTask(Runnable runnable, long time) {this.runnable = runnable;this.time = time;}// 获取时间public long getTime() {return time;}// 执行任务public void run() {runnable.run();}@Overridepublic int compareTo(MyTask o) {// 返回小于 0 , 大于0 , 0// this 比 o 小, 返回 < 0// this 比 o 打,  返回 > 0// this 和 o 相同, 返回 0return (int)(this.time - o.time);}
}// 自己实现定时器
class MyTimer {// 扫描线程private Thread t = null;//用一个优先级阻塞队列来保存任务private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();public MyTimer() {t = new Thread(() -> {while (true) {// 取出队首元素, 检查队首元素是否任务时间到了// 如果时间没到, 就把任务塞回去// 如果时间到了, 就开始执行try {synchronized (this) {MyTask myTask = queue.take();long curTime = System.currentTimeMillis();if (curTime < myTask.getTime()) {// 还没到点, 不执行queue.put(myTask);// 在 put 之后, 进行waitthis.wait(myTask.getTime() - curTime);} else {// 时间到了, 开始执行myTask.run();}}} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();}// 指定两个参数// 第一个参数是 任务内容// 第二个参数是 任务在多少毫秒后开始执行public void schedule(Runnable runnable, long after) {MyTask myTask = new MyTask(runnable,System.currentTimeMillis() + after);queue.put(myTask);synchronized (this) {this.notify();}}}
public class ThreadDemo7 {public static void main(String[] args) {MyTimer myTimer = new MyTimer();Runnable runnable1 =new Runnable() {@Overridepublic void run() {System.out.println("hello 1");}};Runnable runnable2 =new Runnable() {@Overridepublic void run() {System.out.println("hello 2");}};Runnable runnable3 =new Runnable() {@Overridepublic void run() {System.out.println("hello 3");}};Runnable runnable4 =new Runnable() {@Overridepublic void run() {System.out.println("hello 4");}};myTimer.schedule(runnable1,4000);myTimer.schedule(runnable2,3000);myTimer.schedule(runnable3,2000);myTimer.schedule(runnable4,1000);}
}

在这里插入图片描述

3. 线程池

线程池是什么

  • 线程池是将多个线程预先存储在一个"池子"内,当有任务出现时可以避免重新创建和销毁线程所带来性能开销,只需要从"池子"内取出相应的线程执行对应的任务即可。
  • 线程池中的线程都为后台线程,每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲,则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程,但线程的数目永远不会超过最大值,超过最大值的线程可以排队,但他们要等到其他线程完成后才启动

线程池最大的好处就是减少每次启动、销毁线程的损耗

标准库中的线程池

  • 使用 Executors.newFixedThreadPool(10) 能创建出固定包含 10 个线程的线程池.
Executors.newFixedThreadPool(10)
  • 返回值类型为 ExecutorService
ExecutorService pool = Executors.newFixedThreadPool(10);
  • 通过 ExecutorService.submit 可以注册一个任务到线程池中.
pool.submit(new Runnable() {public void run() {System.out.println("hello");}
});

下面举一个简单的例子
创建一个 10 个线程的线程池, 打印 1000 次 “hello”

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;// 使用标准库的线程池
public class ThreadDemo8 {public static void main(String[] args) {// 创建了一个线程池, 池子里有10个线程ExecutorService pool = Executors.newFixedThreadPool(10);for (int i = 0; i < 1000; i++) {int n = i;pool.submit(new Runnable() {public void run() {System.out.println("hello" + n);}});}}
}

在这里插入图片描述

Executors 创建线程池的几种方式

  • newFixedThreadPool: 创建固定线程数的线程池
  • newCachedThreadPool: 创建线程数目动态增长的线程池.
  • newSingleThreadExecutor: 创建只包含单个线程的线程池.
  • newScheduledThreadPool: 设定 延迟时间后执行命令,或者定期执行命令. 是进阶版的 Timer

在这里插入图片描述

那么我们在开发中, 线程池的线程数设置为多少合适呢?
不同的程序特点不同, 此时要设置的线程数也是不同的,
我们考虑两个极端情况,:

  • CPU密集型 , 每个线程要执行的任务都是"狂转"CPU(进行一系列的算术运算)

此时线程池的线程数 最多也不应该超过CPU的核数
设置得再大了, 也没用, CPU密集型, 要一直占用CPU, 搞那么多线程, CPU的坑位就不够了.

  • IO密集型, 每个线程干的工作就是等待IO(读写硬盘, 读写网卡, 等待用户输入…),不吃CPU

此时这样的线程处于阻塞状态, 不参与CPU调度
这种情况多搞一些线程都没关系, 不在受制于CPU核数了

然而, 在我们实际开发中, 并没有程序符合这两种理想情况, 真实的程序, 往往是一部分要吃CPU, 一部分等待IO.
具体这个程序, 几成工作量吃CPU, 几成工作量等待IO, 不确定

实践中确定线程的数量, 通过测试/实验的方式来确定

自己实现一个线程池(固定数量线程的线程池)

// 一个线程池, 里面至少包含两个大的部分
// 1. 阻塞队列, 用来保存任务
// 2. 若干个工作线程import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;class MyThreadPool {// 此处不涉及"时间", 只有任务, 就直接使用Runnableprivate BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();// n 表示线程的数量public MyThreadPool(int n) {// 在这里创建出线程for (int i = 0; i < n; i++) {Thread t = new Thread(() -> {while (true) {try {Runnable runnable = queue.take();runnable.run();} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();}}// 注册任务给线程池public void submit(Runnable runnable) {try {queue.put(runnable);} catch (InterruptedException e) {throw new RuntimeException(e);}}
}public class ThreadDemo9 {public static void main(String[] args) {// 创建我自己的线程池MyThreadPool pool = new MyThreadPool(10);for (int i = 0; i < 1000; i++) {int n = i;pool.submit(new Runnable() {@Overridepublic void run() {System.out.println("hello" + n);}});}}
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/163287.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

关于网络协议的若干问题(五)

1、DH 算法会因为传输随机数被破解吗&#xff1f; 答&#xff1a;DH 算法的交换材料要分公钥部分和私钥部分&#xff0c;公钥部分和其他非对称加密一样&#xff0c;都是可以传输的&#xff0c;所以对于安全性是没有影响的&#xff0c;而且传输材料远比传输原始的公钥更加安全。…

Apache Jmeter测压工具快速入门

Jmeter测压工具快速入门 一、Jmeter介绍二、Jmeter On Mac2.1 下载2.2 安装2.2.1 环境配置2.2.2 初始化设置 2.3 测试2.3.1 创建JDBC Connection Configuration2.3.2 创建线程组2.3.3 创建JDBC Request2.3.4 创建结果监控2.3.5 运行结果 2.4 问题记录2.4.1 VM option UseG1GC异…

小程序技术在信创操作系统中的应用趋势:适配能力有哪些?

小程序技术在信创操作系统中的应用前景非常广阔&#xff0c;但也面临着一些挑战和问题。开发者需要积极应对这些挑战和问题&#xff0c;为信创操作系统的发展和推广做出贡献。同时&#xff0c;开发者也需要关注小程序技术在信创操作系统中的应用趋势&#xff0c;积极探索新的应…

视频剪辑SDK,实现高效的移动端视频编辑

为了满足企业对视频编辑的需求&#xff0c;美摄提供了iOS/Android端视频编辑SDK技术开发服务&#xff0c;帮助企业快速高效地制作高质量视频。本文将详细介绍美摄的视频编辑SDK的优势和特点&#xff0c;以及如何为企业提供技术解决方案。 随着智能手机的普及和移动互联网的发展…

Java实现业务异步的几种方案

背景&#xff1a; 在java中异步线程很重要&#xff0c;比如在业务流处理时&#xff0c;需要通知硬件设备&#xff0c;发短信通知用户&#xff0c;或者需要上传一些图片资源到其他服务器这种耗时的操作&#xff0c;在主线程里处理会阻塞整理流程&#xff0c;而且我们也不需要等…

Zookeeper集群 + Kafka集群的详细介绍与部署

文章目录 1. Zookeeper 概述1.1 简介1.2 Zookeeper的工作机制1.3 Zookeeper 主要特点1.4 Zookeeper 数据结构1.5 Zookeeper的相关应用场景1.5.1 统一命名服务1.5.2 统一配置管理1.5.3 统一集群管理1.5.4 服务器动态上下线1.5.5 软负载均衡 1.6 Zookeeper 选举机制1.6.1 第一次启…

Jmeter 之接口测试(http 接口测试)

基础知识储备 一、了解 jmeter 接口测试请求接口的原理 客户端 -- 发送一个请求动作 -- 服务器响应 -- 返回客户端 客户端 -- 发送一个请求动作 --jmeter 代理服务器 --- 服务器 --jmeter 代理服务器 -- 服务器 二、了解基础接口知识&#xff1a; 1、什么是接口&#xff1a…

uniapp 小程序优惠劵样式

先看效果图 上代码 <view class"coupon"><view class"tickets" v-for"(item,index) in 10" :key"item"><view class"l-tickets"><view class"name">10元优惠劵</view><view cl…

centos7 部署oracle完整教程(命令行)

centos7 部署oracle完整教程&#xff08;命令行&#xff09; 一. centos7安装oracle1.查看Swap分区空间&#xff08;不能小于2G&#xff09;2.修改CentOS系统标识 (由于Oracle默认不支持CentOS)2.1.删除CentOS Linux release 7.9.2009 (Core)&#xff08;快捷键dd&#xff09;&…

python爬取boss直聘数据(selenium+xpath)

文章目录 一、主要目标二、开发环境三、selenium安装和驱动下载四、主要思路五、代码展示和说明1、导入相关库2、启动浏览器3、搜索框定位创建csv文件招聘页面数据解析(XPATH)总代码效果展示 六、总结 一、主要目标 以boss直聘为目标网站&#xff0c;主要目的是爬取下图中的所…

EV SSL数字证书贵吗

EVSSL证书通常适用于具有高需求的网站和企业&#xff0c;特别是涉及在线交易、金融服务、电子商务平台等需要建立用户信任的场景。大型企业、金融机构、电子商务平台等可以受益于使用EV证书来提升品牌形象和安全性。 申请EVSSL证书&#xff08;Extended Validation SSL certifi…

Ubuntu:VS Code IDE安装ESP-IDF【保姆级】(草稿)

物联网开发学习笔记——目录索引 Visual Studio Code&#xff08;简称“VS Code”&#xff09;是Microsoft向开发者们提供的一款真正的跨平台编辑器。 参考&#xff1a; VS Code官网&#xff1a;Visual Studio Code - Code Editing. Redefined 乐鑫官网&#xff1a;ESP-IDF …

python:talib.BBANDS 画股价-布林线图

python 安装使用 TA_lib 安装主要在 http://www.lfd.uci.edu/~gohlke/pythonlibs/ 这个网站找到 TA_Lib-0.4.24-cp310-cp310-win_amd64.whl pip install /pypi/TA_Lib-0.4.24-cp310-cp310-win_amd64.whl 编写 talib_boll.py 如下 # -*- coding: utf-8 -*- import os impor…

c语言练习93:环形链表的约瑟夫问题

环形链表的约瑟夫问题 环形链表的约瑟夫问题_牛客题霸_牛客网 描述 编号为 1 到 n 的 n 个人围成一圈。从编号为 1 的人开始报数&#xff0c;报到 m 的人离开。 下一个人继续从 1 开始报数。 n-1 轮结束以后&#xff0c;只剩下一个人&#xff0c;问最后留下的这个人编号是…

使用IDEA2022.1创建Maven工程出现卡死问题

使用IDEA创建Maven工程出现卡死问题&#xff0c;这个是一个bug 这里是别人和官方提供这个bug,大家可以参考一下 话不多说&#xff0c;上教程 解决方案&#xff1a; 方案1&#xff1a;更新idea版本 方案2&#xff1a;关闭工程&#xff0c;再新建&#xff0c;看图

知识分享:如何制作一个电子名片二维码?

参加国际展会、寻找合作商、线下客户拜访、渠道开发、商务对接、行业交流大会……在这些场合中&#xff0c;商务名片都是必不可少的。随着二维码应用的流行&#xff0c;名片上使用二维码已经非常普遍了。你也可以在商务名片上使用一个自己设计的电子名片二维码&#xff0c;扫描…

微软Azure OpenAI支持数据微调啦!可打造专属ChatGPT

10月17日&#xff0c;微软在官网宣布&#xff0c;现在可以在Azure OpenAI公共预览版中对GPT-3.5-Turbo、Babbage-002 和Davinci-002模型进行数据微调。 使得开发人员通过自己的数据集&#xff0c;便能打造独一无二的ChatGPT。例如&#xff0c;通过海量医疗数据进行微调&#x…

微信小程序一键获取位置

需求 有个表单需要一键获取对应位置 并显示出来效果如下&#xff1a; 点击一键获取获取对应位置 显示在 picker 默认选中 前端 代码如下: <view class"box_7 {{ showChange1? change-style: }}"><view class"box_11"><view class"…

大鼠药代动力学(PK参数/ADME)+毒性 实验结果分析

在真实做实验的时候&#xff0c;出现了下面真实测试的一些参数&#xff0c;一起学习一下&#xff1a; 大鼠药代动力学&#xff1a; 为了进一步了解化合物 96 的药代动力学性质&#xff0c;我们选择化合物 500 进行 SD大鼠药代动力学评估。 经静脉注射和口服给药后观察大鼠血药…

互联网行业汇总

互联网行业汇总&#xff0c;全网最全&#xff01;选行业不愁 从事互联网选什么行业&#xff1f;这似乎是很多朋友的困惑。 所以这里给大家把互联网行业做个细致的汇总&#xff0c;每个行业列举几个典型的APP&#xff0c;简单拆解下各自的盈利模式&#xff0c;希望能给大家提供参…