这一篇说一下比较枯燥的东西,为什么说枯燥呢,因为我写这都感觉很无聊,无非就是几个阻塞线程的方法和唤醒线程的方法。。。

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(多线程篇)的更多相关文章

  1. 带着新人看java虚拟机06(多线程篇)

    其实多线程还有很多的东西要说,我们慢慢来,可能会有一些东西没说到,那就没办法了,只能说尽量吧! 1.synchronized关键字 说到多线程肯定离不开这个关键字,为什么呢?因为多线程之间虽然有各自的 ...

  2. 带着新人看java虚拟机04(多线程篇)

    我记得最开始接触多进程,多线程这一块的时候我不是怎么理解,为什么要有多线程啊?多线程到底是个什么鬼啊?我一个程序好好的就可以运行为什么要用到多线程啊?反正我是十分费解,即使过了很长时间我还是不是很懂, ...

  3. 带着新人看java虚拟机03

    分享一篇博客:https://blog.csdn.net/yfqnihao/article/details/8289363,本篇有部分参考这篇博客!!! 还是继续说一下java虚拟机,为什么呢?因为我 ...

  4. 带着新人看java虚拟机02

    上一节是把大概的流程给过了一遍,但是还有很多地方没有说到,后续的慢慢会涉及到,敬请期待! 这次我们说说垃圾收集器,又名gc,顾名思义,就是收集垃圾的容器,那什么是垃圾呢?在我们这里指的就是堆中那些没人 ...

  5. 带着新人看java虚拟机01

    1.前言(基于JDK1.7) 最近想把一些java基础的东西整理一下,但是又不知道从哪里开始!想了好久,还是从最基本的jvm开始吧!这一节就简单过一遍基础知识,后面慢慢深入... 水平有限,我自己也是 ...

  6. 带着新人看java虚拟机05(多线程篇)

    上一篇我们主要是把一些基本概念给说了一下以及怎么简单的使用线程池,我们这一节就来看看线程池的实现: 1.线程池基本参数 以Executors.newFixedThreadPool()这种创建方式为例: ...

  7. java基础07 多线程

    在学习操作系统时,我们会学习进程和线程,那么进程和线程又是什么东西呢? 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位. 线程(thread) ...

  8. Java基础之多线程篇(线程创建与终止、互斥、通信、本地变量)

    线程创建与终止 线程创建 Thread类与Runnable接口的关系 public interface Runnable { public abstract void run(); } public ...

  9. 深入理解java虚拟机系列初篇(一):为什么要学习JVM?

    前言 本来想着关于写JVM这个专栏,直接写知识点干货的,但是想着还是有必要开篇讲一下为什么要学习JVM,这样的话让一些学习者心里有点底的感觉比较好... 原因一:面试 不得不说,随着互联网门槛越来越高 ...

随机推荐

  1. git命令行常用几个指令(细节问题)

    GIT PUSH/PULL时总需要输入用户名密码的解决方案: git config --global credential.helper store 查看当前的配置信息: git config --s ...

  2. require.js配置路径的用法和css的引入

    前端开发在近一两年发展的非常快,JavaScript作为主流的开发语言得到了前所未有的热捧.大量的前端框架出现了,这些框架都在尝试着解决一 些前端开发中的共性问题,但是实现又不尽相同.通常一般的前端加 ...

  3. 从数据库读取数据并动态生成easyui tree构结

    一. 数据库表结构 二.从后台读取数据库生成easyui tree结构的树 1.TreeNode树结点类(每个结点都包含easyui tree 的基本属性信息) import java.io.Seri ...

  4. python爬虫入门(一)urllib和urllib2

    爬虫简介  什么是爬虫? 爬虫:就是抓取网页数据的程序. HTTP和HTTPS HTTP协议(HyperText Transfer Protocol,超文本传输协议):是一种发布和接收 HTML页面的 ...

  5. Android 开发TCP协议时,报错NetworkOnMainThreadException

    设想是通过Android应用连接PC的TCP服务器,通过点击按钮连接服务器, 最终在点击按钮后报错:networkonmainthreadexception 解决办法: 在MainActivity文件 ...

  6. 读《图解HTTP》有感-(了解web及网络基础)

    写在前面 <图解HTTP>是由上野宣先生著,于均良先生译 闲暇之余!写写博文甚是高兴.如有不准确,望各位斧正.共同学习! 正文 HTTP协议是什么?能做什么? HTTP(超文本传输协议)实 ...

  7. TensorFlow-谷歌深度学习库 文件I/O Wrapper

    这篇文章主要介绍一下TensorFlow中相关的文件I/O操作,我们主要使tf.gfile来完成. Exists tf.gfile.Exists(filename) 用来判断一个路径是否存在,如果存在 ...

  8. RedHat Linux下iptables防火墙设置

    一般情况下iptables已经包含在Linux发行版中.运行 # iptables --version 来查看系统是否安装iptables 启动iptables:# service iptables ...

  9. mysql事务隔离级别详解和实战

    A事务做了操作 没有提交 对B事务来说 就等于没做 获取的都是之前的数据 但是 在A事务中查询的话 查到的都是操作之后的数据 没有提交的数据只有自己看得到,并没有update到数据库. 查看InnoD ...

  10. 豆瓣API

    Api V2 索引 图书Api V2 电影Api V2 音乐Api V2 同城Api V2 广播Api V2 用户Api V2 日记Api V2 相册Api V2 线上活动Api V2 论坛Api V ...