菜鸟学习笔记:Java提升篇8(线程2——线程的基本信息、线程安全、死锁、生产者消费者模式、任务调度)

  • 线程的基本信息
  • 线程同步
    • 线程安全
    • 死锁
    • 生产者消费者模式
    • 任务调度(了解)

线程的基本信息

Java中为我们提供了许多方法可以用来获取线程的信息:

  • isAlive()判断线程是否还活着,即线程是否还未终止。
  • getPriority获得线程的优先级数值
  • setPriority设置线程的优先级数值
  • setName设置线程的名称
  • getName获取线程的名称
  • currentThread获取当前正在运行的线程

对这些方法的演示如下:

public class MyThread implements Runnable {private boolean flag =true;private int num =0;@Overridepublic void run() {while(flag){System.out.println(Thread.currentThread().getName()+"-->"+num++);}}public void stop(){this.flag=!this.flag;}
}
 public static void main(String[] args) throws InterruptedException {MyThread it =new MyThread();Thread proxy =new Thread(it,"挨踢");//设置线程名proxy.setName("test");//获取线程名System.out.println(proxy.getName());//获取当前正在运行的线程名System.out.println(Thread.currentThread().getName()); //mainproxy.start();System.out.println("启动后的状态:"+proxy.isAlive());Thread.sleep(200);it.stop();Thread.sleep(100);System.out.println("停止后的状态:"+proxy.isAlive());}

这里说明一下优先级代表线程运行的概率,不是绝对的先后顺序,Thread类中定义了三个静态常两来表示优先级。
MAX_PRIORITY 10
NORM_PRIORITY 5 (默认)
MIN_PRIORITY 1
程序优先级方法演示:

 public static void main(String[] args) throws InterruptedException {MyThread it =new MyThread();Thread p1 =new Thread(it,"挨踢1");MyThread it2 =new MyThread();Thread p2 =new Thread(it2,"挨踢2");p1.setPriority(Thread.MIN_PRIORITY); //设置优先级p2.setPriority(Thread.MAX_PRIORITY);//设置优先级p1.start();p2.start();Thread.sleep(100);it.stop();it2.stop();}

程序结果中p2运行次数要大于p1运行次数,大家可以拷贝代码自己尝试。

线程同步

线程安全

之前讲解中经常会提到线程安全这一概念,所谓线程安全说白了就是保证多个线程访问同一份资源时不会造成错误的结果,还是我们之前的12306例子:

 public static void main(String[] args) {//真实角色Web12306 web = new Web12306();//代理Thread t1 =new Thread(web,"路人甲");Thread t2 =new Thread(web,"黄牛已");Thread t3 =new Thread(web,"攻城师");//启动线程t1.start();t2.start();t3.start();}

我们对代码做出一定的修改:

class Web12306 implements Runnable {private int num =10;private boolean flag =true;@Overridepublic void run() {while(flag){test1();}}public void test1(){if(num<=0){flag=false; //跳出循环return ;}try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"抢到了"+num--);}
}

这时程序会打印:

为什么会出现0或者负数的情况那?我们分析test1,,500ms的时间足够三个线程完成一次执行,所以会出现10、9、8,7、6、5,4、3、2,同时出现。我们从num为1时开始分析,路人甲执行到Thread.sleep(500)时线程阻塞运行,判断num<=0成立,该线程进入休眠。在休眠过程中黄牛已线程运行num还是为1,黄牛已也进入休眠,攻城师也是一个道理。当路人甲休眠结束,执行抢票程序,打印1,num变为0。马上黄牛已休眠结束,由于已经执行完判断语句,所以会直接执行抢票程序,打印0,num变为-1,攻城师休眠结束后也是直接执行抢票程序,打印-1,num变为-2。这时路人甲再次进入程序进行判断,发现判断条件不成立结束程序。
以上情况就是多线程访问带来的错误,我们需要使用线程同步策略防止这个现象。
线程同步最简单的方法就是加上synchronized修饰符,它的作用相当于给方法加了一把锁,使得方法同时只允许一个线程进行访问。

加上之后的实例代码如下:

 public synchronized void test2(){if(num<=0){flag=false; //跳出循环return ;}try {Thread.sleep(500); //模拟 延时} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"抢到了"+num--);}

执行之后之前异常的情况消失,但是由于单线程访问的问题使得每次执行都必须等待500ms,执行效率大大降低。

除了用synchronized方法以外还可以用同步块的思想去实现这个功能:

 public void test3(){//this代表具体对象,也就是web。synchronized(this){if(num<=0){flag=false; //跳出循环return ;}try {Thread.sleep(500); //模拟 延时} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"抢到了"+num--);}}

死锁

同步虽然可以解决许多资源共享的问题,但它不仅会降低程序执行的效率,还会带来一些其他问题,死锁就是其中之一。
我们直接通过一段Java代码来解释死锁:

class Test implements Runnable{Object goods ;Object money ;public Test(Object goods, Object money) {super();this.goods = goods;this.money = money;}@Overridepublic void run() {while(true){test();}}public void test(){synchronized(goods){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized(money){ }       }System.out.println("一手给钱");}
}

上述类中test方法中先对goods进行了锁定,然后在100ms延迟后申请money资源。

class Test2  implements Runnable{Object goods ;Object money ;public Test2(Object goods, Object money) {super();this.goods = goods;this.money = money;}@Overridepublic void run() {while(true){test();}}public void test(){synchronized(money){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized(goods){  }       }System.out.println("一手给货");}
}

上述类中test方法中先对money进行了锁定,然后在100ms延迟后申请goods资源。
我们在主函数中这样进行调用:

 public static void main(String[] args) {Object g =new Object();Object m = new Object();Test t1 =new Test(g,m);Test2 t2 = new Test2(g,m);Thread proxy = new Thread(t1);Thread proxy2 = new Thread(t2);proxy.start();proxy2.start();}

执行后发现程序卡死了,这就是死锁现象。为什么会这样那?首先proxy.start()和proxy2.start()会启动两个线程,而g和m两个资源被两个线程共享,t1线程执行Test中的run方法,run方法中循环调用test方法,执行时会对g进行锁定,并延迟100ms,在这100ms中,t2线程也执行到Test2的test方法中,这里会对m进行锁定,并延迟100ms。当t1线程等待满100ms会在锁定money的基础上申请goods资源,此时goods资源已被t2所占有,所以会继续等待,而t2线程等待100ms后会在占有goods的基础上去申请money资源,此时money资源已被t1所占有,这样双方都需要另一方所占有的资源才能释放自己所占有的资源,进而陷入了无线等待中。从而造成了死锁现象。

生产者消费者模式

为了解决死锁问题我们引入了生产者和消费者模式。
在介绍之前先讲解两个方法。
Object.wait方法:在其他线程调用此对象的notify()或者notifyAll()方法前,当前线程等待,等待时释放所占有资源(这一点与sleep方法不同)。
Object.notify方法:唤醒此对象上等待的单个线程。
有了这个基础我们用代码来演示生产者消费者问题:
首先构建一个电影类(产品):

public class Movie {private String pic ;//信号灯//flag -->T 生产生产,消费者等待 ,生产完成后通知消费//flag -->F 消费者消费 生产者等待, 消费完成后通知生产private boolean flag =true;/*** 播放* @param pic*/public synchronized void play(String pic){if(!flag){ //生产者等待try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}//开始生产try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("生产了:"+pic);//生产完毕      this.pic =pic;//通知消费this.notify();//生产者停下this.flag =false;}public synchronized void watch(){if(flag){ //消费者等待try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}//开始消费try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("消费了"+pic);//消费完毕//通知生产this.notify();//消费停止this.flag=true;}
}

然后构建生产者:

public class Player implements Runnable {private Movie m ;public Player(Movie m) {super();this.m = m;}@Overridepublic void run() {for(int i=0;i<20;i++){if(0==i%2){m.play("左青龙");}else{m.play("右白虎");}}}}

在构建消费者:

public class Watcher implements Runnable {private Movie m ;public Watcher(Movie m) {super();this.m = m;}@Overridepublic void run() {for(int i=0;i<20;i++){m.watch();}}}

在主函数中进行启用:

 public static void main(String[] args) {//共同的资源Movie m = new Movie();//多线程Player p = new Player(m);Watcher w = new Watcher(m);new Thread(p).start();     new Thread(w).start();}

执行代码发现对象的生产和消费总是对应存在的:

这是什么原理那?我们可以看到watch类中通过一个flag位来标记是否以生产,所以我们假设当flag位为false时消费者先进入watch对象的watch()方法(这时还没有生产产品pro),此时flag位为false,所以消费者线程会执行wait方法等待。等待过程中生产者进入watch对象的play方法,生产了一个pro,然后执行notify方法,这时消费者方法被唤醒,对pro进行消费。当flag为true时生产者先进入也是同样的道理,这样就保证了pro这个资源只能在生产完成后才可以消费。避免了死锁的问题。

任务调度(了解)

任务调度的意思就是对一个方法在指定时间或者间隔多长时间对其进行调用。Java中一般通过Timer类来实现,该类使用非常简单,直接通过代码演示。

 public static void main(String[] args) {Timer timer =new Timer();timer.schedule(new TimerTask(){@Overridepublic void run() {System.out.println("so easy....");}}, new Date(System.currentTimeMillis()+1000), 200);//从当前时间开始1000ms后执行run方法,每间隔2000ms执行一次}

以上就是Java多线程的全部内容。
上一篇:菜鸟学习笔记:Java提升篇7(线程1——进程、程序、线程的区别,Java多线程实现,线程的状态))
下一篇:菜鸟学习笔记:Java提升篇9(网络1——网络基础、Java网络编程)

菜鸟学习笔记:Java提升篇8(线程2——线程的基本信息、线程安全、死锁、生产者消费者模式、任务调度)相关推荐

  1. 决策树算法学习笔记(提升篇)

    声明:本文虽有部分自己理解成分,但是大部分摘自以下链接. 决策树(decision tree)(三)--连续值处理 决策树参数讲解+实例 数据挖掘十大算法 C4.5算法的改进: 用信息增益率来选择属性 ...

  2. 实现多线程、进程、线程、设置和获取线程名称、线程调度、线程控制、线程生命周期、同步代码块、线程安全的类、Lock锁、生产者消费者模式

    实现多线程:

  3. 学习笔记-Java并发(一)

    学习笔记-Java并发(一) 目录 学习笔记-Java并发一 目录 Executer Callable和Future 后台线程 线程加入 小计 今天看了这一篇 Java编程思想-java中的并发(一) ...

  4. RabbitMQ学习笔记(高级篇)

    RabbitMQ学习笔记(高级篇) 文章目录 RabbitMQ学习笔记(高级篇) RabbitMQ的高级特性 消息的可靠投递 生产者确认 -- confirm确认模式 生产者确认 -- return确 ...

  5. 菜鸟学习笔记:Java提升篇10(网络2——UDP编程、TCPSocket通信、聊天室案例)

    菜鸟学习笔记:Java提升篇10(网络2--UDP编程.TCPSocket通信) UDP编程 TCP编程(Socket通信) 单个客户端的连接 多个客户端的连接(聊天室案例) UDP编程 在上一篇中讲 ...

  6. 菜鸟学习笔记:Java提升篇9(网络1——网络基础、Java网络编程)

    菜鸟学习笔记:Java提升篇9(网络1--网络基础.Java网络编程) 网络基础 什么是计算机网络 OS七层模型 Java网络编程 InetAddress InetSocketAddress URL类 ...

  7. 菜鸟学习笔记:Java提升篇6(IO流2——数据类型处理流、打印流、随机流)

    菜鸟学习笔记:Java IO流2--其他流 字节数组输入输出流 数据类型处理流 基本数据类型 引用类型 打印流 System.in.System.out.System.err 随机流RandomAcc ...

  8. 菜鸟学习笔记:Java提升篇4(容器4——Collections工具类、其他容器)

    菜鸟学习笔记:Java容器4--Collections工具类.其他容器 Collections工具类 容器其他知识点 队列Queue Enumeration接口 Hashtable Propertie ...

  9. 菜鸟学习笔记:Java提升篇12(Java动态性2——动态编译、javassist字节码操作)

    菜鸟学习笔记:Java提升篇12(Java动态性2--动态编译.javassist字节码操作) Java的动态编译 通过脚本引擎执行代码 Java字节码操作 JAVAssist的简单使用 常用API ...

最新文章

  1. Rocksdb 获取当前db内部的有效key个数 (估值)
  2. opencv 图像上画出目标运动的轨迹_基于opencv的单目和双目标定平台手眼标定
  3. 设计模式(面向对象)设计的七大原则
  4. 《C语言程序设计》第三章练习与习题答案
  5. 试用VisualStudio2005Beta2出现的小BUG
  6. emmx用xmind打开_XMind: ZEN 2020 正式更名 XMind 2020!新名字,新招式。
  7. 请非技术人员不要对技术人员说这很容易实现
  8. R中Matrix and TMB package version issues
  9. php 上传微信媒体,关于微信公众号API多媒体上传
  10. his软件测试文档,HIS系统各子系统功能描述
  11. dreamweaver cc php mysql_Dreamweaver cc 2015 +PHP+MySQL动态网站开发案例教程集合
  12. 数据挖掘基础学习笔记
  13. echarts社区饼图 echart饼图 玫瑰图
  14. 传奇世界3D手游电脑版使用教程:无需安卓模拟器,tcgames完美适配
  15. c语言调用even函数,定义一个判断奇偶数的函数even(int n),当n为偶数时返回1,否则返回0,并实现对其调用。...
  16. 我真要戒游戏了毒奶粉再见!
  17. 使用Ajax实现百度下拉框
  18. 查看word的版本型号
  19. 杂谈---令人抓狂的数据库行级锁问题
  20. 解决xampp安装后遇到的常见问题

热门文章

  1. oracle12c清理游标,Oracle专题12之游标
  2. 清除mysql数据碎片_MySQL 清除表空间碎片方法总结
  3. mysql连接查询on_MySql 连接查询中 on 和 where 的区别
  4. ajax如何进行逻辑判断,如何使Ajax的某些部分同步发生,而前端逻辑异步发生?...
  5. Mouse Event (Java AWT)
  6. 【Python爬虫学习笔记3】requests库
  7. 【bzoj1572】[Usaco2009 Open]工作安排Job 贪心+堆
  8. iOS开发日记46-倒计时效果的实现
  9. 编程基础C——常量,变量,运算符个人总结
  10. TextBox禁止手动输入但是允许刷卡输入