带着新人看java虚拟机07(多线程篇)
这一篇说一下比较枯燥的东西,为什么说枯燥呢,因为我写这都感觉很无聊,无非就是几个阻塞线程的方法和唤醒线程的方法。。。
1.线程中断
首先我们说一说怎么使得一个正在运行中的线程进入阻塞状态,这也叫做线程中断,最常见的就是Thread.sleep(1000)这种方式的,我们直接看一个简单粗暴的图:
此图应该列举了所有中断,我们选择几个比较重要的说说,其中有几个方法已经被废弃了,原因要么就是不安全,要么就是容易产生死锁等等,图中已经划去了!
2.等待通知和收到通知(wait、notify、notifyAll)
由于这wait/notify这种不怎么好理解,我们就详细说说,其他的随便看看就好。。。
大家不知道有木有发现,java中几乎所有的数据类型都重写了Object的wait()、notify()、notifyAll()这三个方法,那这三个方法到底是干什么的呢?
前提:要用这三个方法必须要放在synchronized里面,这是规则!!!至于调用这三个方法必须是锁定(也就是我前面说的锁芯)才能调用,还有,锁定几乎可以是任何类型的!
· 举例下面两种用法:
介绍一个东西,名字叫做“wait set”,这是个什么东西呢?你就把这个看作是一个存阻塞线程的集合(大白话来说就是线程的休息室,把线程用wait阻塞了就相当于让线程去休息室休息,并且线程很有自觉,去休息了之后就会释放锁),而且每个锁定(也就是我前面说的锁芯)都有一个。比如一个线程调用obj.wait()之后,那么这个线程就被阻塞了,这个阻塞的线程就被放在了obj的“wait set”中了,我们可以用一个图来表示:
当线程A阻塞之后就被丢进了obj的wait set中之后,线程A就会释放当前的锁,此时线程B就可以访问这个方法或相同锁定的方法;但是假如在B中调用了notify()方法,那么就是从obje的wait set中唤醒A线程,然后直到B线程结束后释放锁,A线程才变成准备就绪状态,可以随时被CPU调度再次获得这个锁;
注意:必须等B线程执行完之后释放锁,线程A才能变成准备就绪状态(代码是从wait方法后面的代码开始执行,不是重新开始)
根据上面两个图随意举个小例子:
package com.wyq.thread; public class Bank {
Object obj = new Object();//我们随便创建一个锁定 public void tomoney(Integer money){
//在转账方法的锁中调用wait方法,此时执行这个方法的线程会中断,保存在obj的wait set中,并且该线程会释放锁其他线程可以访问相同锁定的锁
synchronized(obj){
try {
obj.wait();
System.out.println("转账:"+money+"元");
} catch (InterruptedException e) {
e.printStackTrace();
} } }
public void save(Integer money){
//在存钱方法的锁中我们调用notify从obj的wait set中唤醒存在其中的某一个线程,那个被唤醒的线程不会马上变成准备就绪状态,
//必须要等本存钱方法的线程执行完毕释放锁,才会进入准备就绪状态
synchronized(obj){
obj.notify();
System.out.println("存钱:"+money+"元");
} } public static void main(String[] args) {
Bank bank = new Bank();
//我们可以多次运行这两个线程,总是先执行存钱方法,然后才是转账方法(其实转账线程可以利用for循环创建几十个,这样效果更明显)
new Thread(new Runnable() { @Override
public void run() {
bank.tomoney(100);
}
}).start(); new Thread(new Runnable() { @Override
public void run() {
bank.save(100);
}
}).start(); }
}
运行结果如下:
notify()是唤醒wait set集合中随意一个线程;而那个notifyAll()方法可以唤醒wait set集合中所有的线程,用法和notify一样,就不举例子了;那么我们平常应该用哪一个呢?用notify的刷唤醒的线程比较少效率高一点,但是缺点就是到底唤醒哪一个线程的实现可能有点难度,一个处理不好程序就可能挂掉了;但是用notifyAll的话效率比较低一点,但是却比较可靠稳定;
所以啊,如果我们对于程序代码都理解得十分透彻,那就用notify比较好,否则还是用稳妥一点的notifyAll吧!
顺便说一点,有的时候我们把一个线程阻塞之后放进wait set中之后,却忘记调用notify/notifyAll了,那么这些阻塞线程就会一直留在了wait set中,我们可以在wait()方法指定一个时间,在规定时间内如果没有被notify唤醒,那么放在wait set中的该线程就会自动唤醒!还有obj.wait()方法其实本质是调用obj.wait(0),wait(long timeout)是一个Native方法!比如obj.wait(3000)表示三秒之后会自动唤醒!这里就是随意提一下,一般很少去指定这个超时时间的
补充wait set的定义:wait set是一个虚拟的概念,它既不是实例的字段,也不是可以获取在实例上wait中线程的列表的方法。
3.sleep和interrupt方法
有没有觉得上面的这种用法比较麻烦,虽然在某些情况下比较适用,但是我们平常测试用的话这也太麻烦了,还有个什么锁定这种鬼东西,有没有比较简单的用法啊!
于是我们有了sleep方法对应于wait方法,interrupt方法对应于notify方法;(注意,这里只是功能上面的对应,但是其中的原理是不相同的!)
首先说说sleep方法,这个应该比较熟悉,直接就是Thread.sleep(xxx)这种方式来使得当前线程阻塞一定时间(注意sleep方法和wait方法最大的区别就是sleep方法不会释放锁),如果没有到达相应时间我们非要让阻塞状态的线程又重新变成准备就绪状态,就使用a.interrupt()方法,其中a指的是当前线程的实例;
我们看一个最简单的例子:
package com.wyq.thread; public class Bank { public void tomoney(Integer money){
try {
//将运行这个方法的线程停止一个星期
Thread.sleep(604800000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("转账:"+money+"元"); } public static void main(String[] args) {
Bank bank = new Bank(); Thread thread = new Thread(new Runnable() { @Override
public void run() {
bank.tomoney(100);
}
});
thread.start();
//如果没有调用interrupt方法,那么thread这个线程就会暂停一个星期
thread.interrupt(); }
}
会抛出一个异常这也是用interrupt方法的特色;
随意一提:这个interrupt方法比较厉害,即使线程中断是由于wait(),join(),sleep()造成的,但是都可以用interrupt方法进行唤醒,比较厉害!
4.join()方法
由于线程的执行是随机的,那么我们有没有设什么方法可以让线程以一定的顺序执行呢?虽然可能会有点影响性能,但这不是我们暂时关心的。
join()方法可以让一个线程B在线程A之后才执行,我们继续看一个简单的例子;
package com.wyq.thread; public class Bank { public void tomoney(String name,Integer money){ System.out.println(name+"转账:"+money+"元"); } public static void main(String[] args) {
Bank bank = new Bank(); Thread A = new Thread(new Runnable() { @Override
public void run() {
bank.tomoney("A",100);
}
}); Thread B = new Thread(new Runnable() { @Override
public void run() {
bank.tomoney("B",200);
}
}); A.start();
try {
//必要要等到A线程执行完毕才会执行其他的线程
A.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
//这个时候查看一下B的线程是NEW,说明B线程还没调用start()方法,
//调用start方法之后就成Runnable状态了
System.out.println(B.getState());
B.start(); }
}
5.线程优先级和yield()方法
其实线程优先级这这种东西比较坑,这是一个概率问题,优先级分为10级,1级最低,10级最高,我们一般创建的线程默认5级;但是优先级高的线程不一定先执行,这个优先级的意思就是线程获取CPU调用的概率比较大,所以这是一个概率问题,基本用法是Thread thread = new Thread(xxx); thread.setPriority(1); thread.start();
那么yield方法是干什么的呢?yield的意思是放手,让步,投降,在多线程中是对一个正在运行的A线程用这个方法表示该线程放弃CPU的调度,重新回到准备就绪状态,然后让CPU去执行和A线程相同优先级的线程,而且有可能又会执行A线程;而且还有可能调用yield方法无效,emmmm......日了狗了!
我表示最这个方法没有什么好感,感觉很难控制,这个方法是Thread的静态方法,直接用Thread.yield()直接用即可,我感觉我一辈子都不会用到这个....这个方法就不测试了,有兴趣的小伙伴自己查查别的资料吧!
6.总结
不知道大家有没有觉得多线程这个东西不能随便用,用好了虽然说可以提高效率,但是用的不好很容易出现不可控制的问题,这让我有一种错觉就是引入了多线程之后,又要解决由多线程引起的更多更麻烦的问题,emmm。。。。
带着新人看java虚拟机07(多线程篇)的更多相关文章
- 带着新人看java虚拟机06(多线程篇)
其实多线程还有很多的东西要说,我们慢慢来,可能会有一些东西没说到,那就没办法了,只能说尽量吧! 1.synchronized关键字 说到多线程肯定离不开这个关键字,为什么呢?因为多线程之间虽然有各自的 ...
- 带着新人看java虚拟机04(多线程篇)
我记得最开始接触多进程,多线程这一块的时候我不是怎么理解,为什么要有多线程啊?多线程到底是个什么鬼啊?我一个程序好好的就可以运行为什么要用到多线程啊?反正我是十分费解,即使过了很长时间我还是不是很懂, ...
- 带着新人看java虚拟机03
分享一篇博客:https://blog.csdn.net/yfqnihao/article/details/8289363,本篇有部分参考这篇博客!!! 还是继续说一下java虚拟机,为什么呢?因为我 ...
- 带着新人看java虚拟机02
上一节是把大概的流程给过了一遍,但是还有很多地方没有说到,后续的慢慢会涉及到,敬请期待! 这次我们说说垃圾收集器,又名gc,顾名思义,就是收集垃圾的容器,那什么是垃圾呢?在我们这里指的就是堆中那些没人 ...
- 带着新人看java虚拟机01
1.前言(基于JDK1.7) 最近想把一些java基础的东西整理一下,但是又不知道从哪里开始!想了好久,还是从最基本的jvm开始吧!这一节就简单过一遍基础知识,后面慢慢深入... 水平有限,我自己也是 ...
- 带着新人看java虚拟机05(多线程篇)
上一篇我们主要是把一些基本概念给说了一下以及怎么简单的使用线程池,我们这一节就来看看线程池的实现: 1.线程池基本参数 以Executors.newFixedThreadPool()这种创建方式为例: ...
- java基础07 多线程
在学习操作系统时,我们会学习进程和线程,那么进程和线程又是什么东西呢? 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位. 线程(thread) ...
- Java基础之多线程篇(线程创建与终止、互斥、通信、本地变量)
线程创建与终止 线程创建 Thread类与Runnable接口的关系 public interface Runnable { public abstract void run(); } public ...
- 深入理解java虚拟机系列初篇(一):为什么要学习JVM?
前言 本来想着关于写JVM这个专栏,直接写知识点干货的,但是想着还是有必要开篇讲一下为什么要学习JVM,这样的话让一些学习者心里有点底的感觉比较好... 原因一:面试 不得不说,随着互联网门槛越来越高 ...
随机推荐
- 在cmd下运行Python脚本+如何使用Python Shell
原文:https://blog.csdn.net/flyfrommath/article/details/77447587?locationNum=2&fps=1
- C++ 进制转换 十进制十六进制八进制二进制相互转换
思路: 下面我把相互转换的所有类型都写出来了.实际上都是通过十进制中转的,这样比较简单,写出X进制转成十进制和从十进制转成X进制的两份代码直接拷贝就完成了剩余的部分.哦,对,自己封装了一个charTo ...
- HTML 学习笔记 day three
HTML学习笔记 Day three 7.2插入多媒体元素 插入音乐 语法结构:<embed src=”音乐文件的路径”></embed> 属性: Autostart:他只有 ...
- php中的抽象方法和抽象类,简单明了,一点通
1.什么是抽象方法? 我们在类里面定义的没有方法提的方法就是抽象方法.所谓的没有方法体指的是,在声明的时候没有大括号以及其中的内容,而是直接在声明时在方法名后加上分号结束,另外在声明抽象方法时方 ...
- windows和centos下安装ActiveMQ
版本:apache-activemq-5.10.2-bin.zip (版本5.11+需要jdk7+) 官网: http://activemq.apache.org/download.h ...
- Oracle客户端安装教程
链接:http://jingyan.baidu.com/article/3d69c55165bc94f0cf02d7d9.html (按照此链接不会安装错误)
- SSM-SpringMVC-22:SpringMVC中转发(forward)和重定向(redirect)
------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 转发和重定向大家都熟悉,都学到框架了,怎么能不了解转发和重定向呢? 如果有不熟悉的,可以去百度搜几篇博客去看看 ...
- ORACLE 快速启动监听及相关服务程序
windows7 系统下,鼠标移至任务栏右键启动任务管理器->选择服务->点击右下角服务选项 ->选中名称,键盘输入O(大写),快速找到ORACLE相关服务进程 ->将所有的O ...
- vm12 和14密钥
vm12:5A02H-AU243-TZJ49-GTC7K-3C61N vm14:FF31K-AHZD1-H8ETZ-8WWEZ-WUUVA
- 常见的web测试功能点测试思路
常见的功能点的测试思路: . 新增 或 创建(Add or Create) ) 操作后的页面指向 )操作后所有绑定此数据源的控件数据更新,常见的排列顺序为栈Stack类型,后进先出 ) 取消操作是否成 ...