多线程概念
在我们的程序层面来说,多线程通常是在每个进程中执行的,相应的附和我们常说的线程与进程之间的关系。线程与进程的关系:线程可以说是进程的儿子,一个进程可以有多个线程。但是对于线程来说,只属于一个进程。再说说进程,每个进程的有一个主线程作为入口,也有自己的唯一标识PID,它的PID也就是这个主线程的线程ID。
对于我们的计算机硬件来说,线程是进程中的一部分,也是进程的的实际运作单位,它也是操作系统中的最小运算调度单位。多线程可以提高CPU的处理速度。当然除了单核CPU,因为单核心CPU同一时间只能处理一个线程。在多线程环境下,对于单核CP来说,并不能提高响应速度,而且还会因为频繁切换线程上下文导致性能降低。多核心CPU具有同时并行执行线程的能力,因此我们需要注意使用环境。线程数超出核心数时也会引起线程切换,并且操作系统对我们线程切换是随机的。
引入
共享资源,从而实现一些特殊任务。上面说了,多线程在进行切换时CPU随机调度的,假如我们直接运行多个线程操作共享资源的话,势必会引起一些不可控错误因素。为了解决多线程对同一共享变量的争夺。Java 线程通信的方式
主要介绍wait/notify,也有ReentrantLock的Condition条件变量的await/signal,LockSupport的park/unpark方法,也能实现线程之间的通信。主要是阻塞/唤醒通信模式。
首先说明这种方法一般都是作用于调用方法的所在线程。比如在主线程执行wait方法,就是将主线程阻塞了。
wait/notify机制
await/signal
park/unpark
如果一个线程先于被通知线程调用wait()前调用了notify(),等待的线程将错过这个信号。
flag=FALSE表示还没wait,在wait之前将设置flag=TRUE,在notify之后设置flag=FALSE。每次notify唤醒之前都判断flag=true是否已经wait,在wait中判断flag=false是否已经notify。核心代码演示
// 线程一使用LOCK1对象调用wait方法阻塞自己
executor.execute(new ThreadTest("线程一",LOCK1,LOCK2));
synchronized (LOCK1) {
System.out.println("main执行notify方法让线程一醒过来");
LOCK1.notify();
}
但是他很有可能醒不来,因为主线程调用LOCK1对象的notify方法,可能主线程已经执行完了,上面线程还没创建完成,也就是没有进入wait状态。就醒不来了。
解决方式:使用信号量标志进行判断是否已经进入wait
synchronized (LOCK1) {
while (true) {
if (FLAG.getFlag()) {
System.out.println("main马上执行notify方法让线程一醒过来" + "flag = " + FLAG.getFlag());
LOCK1.notify();
// 将标志位变为FALSE
FLAG.setFlag(Constants.WaitOrNoWait.NO_WAIT.getFlag());
System.out.println("main执行notify方法完毕" + "flag = " + FLAG.getFlag());
break;
}
}
}
由于莫名其妙的原因,线程有可能在没有调用过notify()和notifyAll()的情况下醒来。
synchronized (waitName) {
while (!flag.getFlag()) {
try {
// 将标志位设置为TRUE
flag.setFlag(Constants.WaitOrNoWait.WAIT.getFlag());
System.out.println("name;"+name+" 我睡着了进入阻塞状态" + "flag = " + flag.getFlag());
waitName.wait();
System.out.println("name;"+name+" 我醒来了" + "flag = " + flag.getFlag());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private final static Object LOCK1 = new Object();
private final static Object LOCK2 = new Object();
private final static Constants.WaitStatus FLAG = new Constants.WaitStatus(false);
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 5, 1, TimeUnit.DAYS, new ArrayBlockingQueue<>(4), new ThreadPoolExecutor.AbortPolicy());
executor.execute(new ThreadTest("线程一",LOCK1,LOCK2, FLAG));
// ···唤醒
}
class ThreadTest implements Runnable { //阻塞··· }
完整代码可以看这[Gitee仓库完整代码][https://gitee.com/malongfeistudy/javabase/tree/master/Java多线程_Study/src/main/java/com/mlf/thread/demo_wait_notify]
首先复习一下创建线程的几种方式和其的优缺点:
使用线程池的步骤
在主线程自定义线程池使用实例,这里需要根据实际情况定义锁对象,因为我们需要使用这些锁对象控制多线程之间的运行顺序以及线程之间的通信。在Java中每个对象都会在初始化的时候拥有一个监视器,我们需要利用好他进行并发编程。这种创建线程池的方法也是阿里巴巴推荐的方式,想想以阿里的体量多年总结出来的总没有错,大家还是提前约束自己的编码习惯等。安装一个阿里代码规范的插件对自己的程序员道路是比较nice的。
/**
* 每个使用对应唯一的对象作为监视器对象锁。
*/
public static final Object A_O = new Object();
public static final Object B_O = new Object();
/** 参数:
* int corePoolSize, 核心线程数
* int maximumPoolSize, 最大线程数
* long keepAliveTime, 救急存活时间
* TimeUnit unit, 单时间位
* BlockingQueue<Runnable> workQueue, 阻塞队列
* RejectedExecutionHandler handler 拒绝策略
**/
// 使用阿里巴巴推荐的创建线程池的方式
// 通过ThreadPoolExecutor构造函数自定义参数创建
ThreadPoolExecutor executor = new ThreadPoolExecutor(
3,
5,
1,
TimeUnit.DAYS,
new ArrayBlockingQueue<>(2),
new ThreadPoolExecutor.AbortPolicy());
class ThreadDiy implements Runnable {
private final String name;
/**
* 阻塞锁对象 等待标记
**/
private final Object waitFor;
/**
* 执行锁对象 下一个标记
**/
private final Object next;
public AlternateThread(String name, Object waitFor, Object next) {
}
@Override
public void run() {
// 线程的代码逻辑···
}
}
题目:现在有两个线程,不论线程的启动顺序,我需要指定线程一先执行,然后线程二再执行。
初始化两个对象锁作为线程监视器。
private final static Object ONE_LOCK = new Object();
private final static Object TWO_LOCK = new Object();
接下来初始化线程池,上面有具体的介绍,在这就不多说了
使用线程池去执行我们的两个线程,在这里我们需要分析的是
// 使用线程池创建线程
executor.execute(new DiyThread(1, ONE_LOCK, TWO_LOCK));
executor.execute(new DiyThread(2, TWO_LOCK, ONE_LOCK));
synchronized (ONE_LOCK) {
ONE_LOCK.notify();
}
创建线程类
我们使用继承Runnable的方式去创建线程对象,需要在这个类中实现每个线程执行的逻辑,我们根据题目可以得出,我们要控制每个线程的执行顺序,怎么办?那么就要实现所有线程之间的通信,通信方式采用wait-notify的方式即可。我们使用wait-notify的时候必须结合synchronized,那么就需要控制两个对象锁。因为我们不光是控制自己,还有另一个线程。
我们再分析一下题意,首先需要指定先后执行的顺序,那么就需要实现两个线程之间的通信。其次呢,我们得控制两个线程,那么就需要两个监视器去监视这两个线程。
我们定义这两个监视器对象为own和other。然后再新增一个属性threadId来标识自己。
private final int threadId;
private final Object own;
private final Object other;
接下来就是编写Run方法了
每个线程首先需要阻塞自己,等待唤醒。然后唤醒之后,再去唤醒另外一个线程。这样就实现了自定义顺序。至于先唤醒哪个线程,交给我们的主线程去完成。
这里需要注意的是,如果我们只是单纯地执行了多个线程对象,但是主线程没有主动去唤醒其中一个,这样就会形成类似于死锁的循环等待。你需要我唤醒,我需要你唤醒。这个时候需要主线程去插手唤醒其中的任意一个线程。
第一步阻塞自己own
synchronized (own) {
try {
own.wait();
System.out.println(num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
第二步唤醒other
synchronized (other) {
other.notify();
}
题目需求:现在需要使用三个线程轮流打印输出。说白了也就是多线程轮流执行罢了,和问题一控制两个线程打印顺序没什么区别
/**
* 阻塞锁对象 等待标记
**/
private final Object waitFor;
/**
* 唤醒锁对象 下一个标记
**/
private final Object next;
run方法的逻辑和上面的基本一样。 一个线程一旦调用了任意对象的wait()方法,它就释放了所持有的监视器对象上的锁,并转为非运行状态。
每个线程首先会调用 waitFor对象的 wait()方法,随后该线程进入阻塞状态,等待其他线程执行自己引用的该 waitFor对象的 notify()方法即可。
while (true) {
synchronized (waitFor) {
try {
waitFor.wait();
System.out.println(name + " 开始执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (next) {
next.notify();
}
}
主线程需要初始化线程池、执行三个线程,并且最后需要打破僵局,因为此时每个线程都是阻塞状态,他们没法阻塞/唤醒循环下去。
synchronized (A_O) {
A_O.notify();
}
模拟执行流程
/**
* 模拟执行流程
* 打印名(name) 等待标记(waitFor) 下一个标记(next)
* 1 A B
* 2 B C
* 3 C A
*
* 像不像Spring的循环依赖:确实很像,Spring中的循环依赖就是 BeanA 依赖 BeanB,BeanB 依赖 BeanA;
* 他们实例化过程中都需要先属性注入对方的实例,倘若刚开始的时候都没有实例化,初始化就会死等。类似于死锁。
**/
使用多线程轮流打印 01234····
具体代码请移步到Gitee仓库:[顺序打印自增变量][https://gitee.com/malongfeistudy/javabase/blob/master/Java多线程_Study/src/main/java/com/mlf/thread/print/AddNumberPrint2.java]
条件变量Condition的使用
如有问题,请留言评论。
我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此
我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("
在Cooper的书BeginningRuby中,第166页有一个我无法重现的示例。classSongincludeComparableattr_accessor:lengthdef(other)@lengthother.lengthenddefinitialize(song_name,length)@song_name=song_name@length=lengthendenda=Song.new('Rockaroundtheclock',143)b=Song.new('BohemianRhapsody',544)c=Song.new('MinuteWaltz',60)a.betwee
我正在检查一个Rails项目。在ERubyHTML模板页面上,我看到了这样几行:我不明白为什么不这样写:在这种情况下,||=和ifnil?有什么区别? 最佳答案 在这种特殊情况下没有区别,但可能是出于习惯。每当我看到nil?被使用时,它几乎总是使用不当。在Ruby中,很少有东西在逻辑上是假的,只有文字false和nil是。这意味着像if(!x.nil?)这样的代码几乎总是更好地表示为if(x)除非期望x可能是文字false。我会将其切换为||=false,因为它具有相同的结果,但这在很大程度上取决于偏好。唯一的缺点是赋值会在每次运行
?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------
1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,
Ⅰ软件测试基础一、软件测试基础理论1、软件测试的必要性所有的产品或者服务上线都需要测试2、测试的发展过程3、什么是软件测试找bug,发现缺陷4、测试的定义使用人工或自动的手段来运行或者测试某个系统的过程。目的在于检测它是否满足规定的需求。弄清预期结果和实际结果的差别。5、测试的目的以最小的人力、物力和时间找出软件中潜在的错误和缺陷6、测试的原则28原则:20%的主要功能要重点测(eg:支付宝的支付功能,其他功能都是次要的)80%的错误存在于20%的代码中7、测试标准8、测试的基本要求功能测试性能测试安全性测试兼容性测试易用性测试外观界面测试可靠性测试二、质量模型衡量一个优秀软件的维度①功能性功
目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称
@作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors 1、什么是behaviors 2、behaviors的工作方式 3、创建behavior 4、导入并使用behavior 5、behavior中所有可用的节点 6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors 1、什么是behaviorsbehaviors是小程序中,用于实现
遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg