第十章 线程

1. 提纲

2. 线程的基本概念
进程是一个静态的概念,严格意义上讲并不能执行,我们所说的进程执行指的是进程里的主线程(main()方法)开始执行了

3. 线程的创建和启动
只要可以使用接口就不要使用继承,接口更加灵活。

实现Runnable接口创建线程简例:

效果图:

继承Thread类创建线程简例:

效果图:

4. 线程的状态转换

5. 线程控制的基本方法

  • sleep()方法简例:
import java.util.Date;
public class TestInterrupt {public static void main(String[] args) {MyThread thread = new MyThread();thread.start();try {Thread.sleep(10000);       // 1秒(s)=1000毫秒(ms)} catch (InterruptedException e) {}thread.interrupt(); //中断线程}
}class MyThread extends Thread {public void run() {while(true) {System.out.println("==="+new Date()+"===");try {sleep(1000);  } catch(InterruptedException e) {return;}}}
}

效果图:

分析:


interrupt()虽然不好但是仍然可以捕获异常,此时可以关闭已经打开的资源文件;但如果使用stop()方法将直接杀死线程,目前已经被废弃,不建议使用。最佳关闭方式是设置一个标志量flag,当flag为true时调用线程,flag为false时结束线程。下文有改进的代码。

  • join()方法简例:
public class TestJoin {public static void main(String[] args) {MyThread2 t1 = new MyThread2("t1");t1.start();try {t1.join();} catch (InterruptedException e) {}for(int i=0;i<=10;i++) {System.out.println("I am main thread");}}
}class MyThread2 extends Thread {MyThread2 (String s) {super(s);   //给线程起名字。}public void run() {for(int i=0;i<=10;i++) {System.out.println("i am "+getName());try {sleep(1000);} catch (InterruptedException e) {return;}}}
}

效果图:

join相当于方法调用,就是先执行线程t1,再继续执行主线程。

  • yield()方法简例:
public class TestYield {public static void main(String[] args) {MyThread3 t1 = new MyThread3("t1");MyThread3 t2 = new MyThread3("t2");t1.start();t2.start();}
}class MyThread3 extends Thread {MyThread3(String s) {super(s);}public void run() {for (int i=0;i<=100;i++) {System.out.println(getName() + ":" + i);if(i%10==0) {yield();}}}
}

效果图:

yield()方法,会向调度程序提示当前线程愿意放弃当前对处理器的使用,但调度程序可以忽略此提示,并继续这一线程的执行。

6. 线程的优先级别

简例代码:

public class TestPriority {public static void main(String[] args) {Thread t1 = new Thread(new T1());Thread t2 = new Thread(new T2());t1.setPriority(Thread.NORM_PRIORITY + 4);t1.start();t2.start();}
}class T1 implements Runnable {public void run() {for(int i=0;i<1000;i++) {System.out.println("T1:" + i);}}
}class T2 implements Runnable {public void run() {for(int i=0;i<1000;i++) {System.out.println("++++T2:" + i);}}
}

人为调高线程t1的优先级,使线程t1可以占用更多的时间片,优先完成。

线程小例子1:

线程小例子2:

线程小例子3:

线程小例子4:

线程小例子5:

7. 线程同步

首先使用一个小例子说明线程同步的重要性:

先要的效果是:一个是第一线程,一个是第二线程;但代码运行时产生的显示都为第二线程,这是因为第一个线程执行到num++(此时num=1)时睡眠,并未执行到输出语句,就开始了第二个线程的执行,又一次num++(num=2),并输出。此时第一个线程苏醒并输出,就造成了上图的效果。下面使用synchronized关键字锁定当前对象(线程)进行修改。


模拟死锁—(著名的哲学家吃饭问题)

解决方法(粗略):
解决线程死锁最好只锁定一个对象,别锁定俩。把锁的粒度加粗(解决方法其一),直接锁定当前的整个对象就好了,不要锁定其中两个小对象。

  • 一道经典的面试题:请问此时输出什么?

    输出的结果:

    分析(这个结果说明了什么?):
    程序执行的过程是:先执行线程t,此时b=1000,然后线程睡眠。同时主线程睡眠后苏醒调用m2()方法,输出1000,然后线程t苏醒,输出b=1000,这说明synchronization锁定的是当前对象,即别的线程无法访问被锁定函数的下文,但别的线程可以访问没有被锁定的方法。如果不可以访问的话,此时第一次输出的值应该为100。

  • 延伸题(此时输出是什么?):

    结果:

    分析:这个是对上面面试题所分析的结果进行论证,可以想象为银行账户,当我们决定对同一个对象设置不同的方法时,当我们设置了其中一个为同步方法,就要考虑别的方法是否要设置同步,不设置同步是否会对资源造成不良访问。

  • 延伸题:此时的输出是什么?

    答案:

    分析:一个方法做了同步,另一个方法没有做同步,别的线程可以自由访问未做同步的那个方法,并且可能会对做了同步的方法产生影响;也就是说要保护好某一个类里的某一个对象时(要保护好要同步的对象时)必须对访问这个对象的所有方法仔细地考虑加不加同步,加了同步很有可能效率会变低,不加同步有可能产生数据不一致的现象。

  • 生产者消费者问题
    代码:

public class ProducerConsumer {public static void main (String[] args) {SyncStack ss = new SyncStack();Producer p = new Producer(ss);Consumer c = new Consumer(ss);new Thread(p).start();new Thread(c).start();}
}
//窝头类
class WoTou {int id;   //每个窝头有一个idWoTou(int id) {this.id = id;}public String toString() {  return "WoTou:" + id;}
}
//容器类———>先进后出(栈)
class SyncStack {int index = 0;  //栈指针WoTou[] arrWT = new WoTou[6];   //假设篮子只能存放6个public synchronized void push(WoTou wt) {    //放窝头while(index == arrWT.length) {    try {this.wait();   //当篮子装满6个要使生产者线程进行等待} catch(InterruptedException e) {e.printStackTrace();}}this.notify();    //叫醒消费者线程,进行消费arrWT[index]  = wt;  //index位置放入窝头index ++;  //栈顶指针加一}public synchronized WoTou pop() {    //取窝头while(index == 0) {try {this.wait();   //当篮子里没有窝头时消费者线程进行等待} catch(InterruptedException e) {e.printStackTrace();}}this.notify() ;  //叫醒生产者线程开始生产index --; //栈顶指针下移return arrWT[index];  //得到窝头}
}class Producer implements Runnable {   //生产者类SyncStack ss = null;Producer(SyncStack ss){       //每一个生产者都要指向一个篮子this.ss = ss;}public void run() {for(int i=0;i<20;i++) {      //每个生产者最多生产20个WoTou wt = new WoTou(i);ss.push(wt);       //每生产一个就放入篮子System.out.println("+++Producer :" + wt);try {Thread.sleep((int)(Math.random() * 200));} catch(InterruptedException e) {e.printStackTrace();}}}
}class Consumer implements Runnable {  //消费者SyncStack ss = null;Consumer(SyncStack ss){     //每一个消费者也要对应一个篮子进行消费this.ss = ss;}public void run() {for(int i=0;i<20;i++) {     //一个消费者要消费20个窝头WoTou wt = ss.pop();     //将篮子里的窝头从篮子里拿出来System.out.println("***Consumer:" + wt);try {Thread.sleep((int)(Math.random() * 1000));} catch(InterruptedException e) {e.printStackTrace();}}}
}

分析:

  • wait()方法:这个方法并不是来自Thread类,而是来自Object类,这个方法只适用于synchronization修饰的方法,没有锁定就谈不上等待。使用的意义是,每个篮子只能装6个,假设消费速度远不及生产速度,当篮子中装满6个还在向里装,就会导致数组越界问题,所以要检测当篮子已经装满时就要让生产者开始等待。即当前正在这个对象访问的线程(拿到对象锁的线程)等待。与sleep()不同的是,当线程wait()时,别的线程可以访问被锁定对象,而sleep()时别的线程不可以访问被锁定的对象。
  • notify()方法:和wait()方法成对使用,叫醒当前对象上等待的线程。使用意义:当篮子装满时,生产者线程开始等待,然后消费者线程运行,当篮子为空时要让消费者线程等待,同时唤醒生产者线程开始生产。
  • notifyAll()方法:作用同nitify(),但此时是唤醒当前对象上等待的多个线程。
  • 为什么使用while循环而不是if,如果使用if,当篮子装满6个,这时候假设被打断捕获InterruptedException,然后跳出if而没有使生产者线程等待,继续向下依次执行,this.notify(); arrWT[index] = wt; index ++; 这个时候就会溢出。而要是while的话,即使捕获异常这时候仍然会检验条件(篮子有多少个)才会继续向下进行。

8. 总结

注:本博客由溺水狗原创但其学习资源资料来源于网络(马士兵老师的教学视频),特此感谢马士兵老师

《J2SE 回炉再造16》-------溺水狗相关推荐

  1. 《J2SE 回炉再造06》-------溺水狗

    第四章 面向对象编程基础篇(2) Java与面向对象的缘分 为什么要使用对象,意义是什么? Java中的类如何定义? 类的定义包括静态(成员变量/属性)和动态(方法)两部分 成员变量与局部变量的有关事 ...

  2. 《J2SE 回炉再造18》-------溺水狗

    第十二章 GUI编程 1. 提纲 2. AWT包 3. Component和Container 4. Frame类 代码1: import java.awt.*;public class TestFr ...

  3. 《J2SE 回炉再造17》-------溺水狗

    第十一章 网络编程 1. 提纲 值得注意的是网络编程不等同于网站编程 2. 网络基础概念 3. 网络通信协议及接口 4. 数据分层的思想 5. 数据封装和数据拆封 6. IP协议 IPV4协议中用4个 ...

  4. 《J2SE 回炉再造15》-------溺水狗

    第九章 输入/输出流.字节/字符流.节点/处理流 1. 提纲 2. 概述 注1:输入/输出流:都是站在程序的角度来说的,而不是文件的角度.参考理解 注2:输入流:将其他资源传送到内存(程序):输出流: ...

  5. 《J2SE 回炉再造13》-------溺水狗

    第七章 String.StringBuffer.包装类.Math类.File类.枚举类 提纲: String类(不可变字符序列) 练习一参考答案 练习二参考答案 StringBuffer类(可变的字符 ...

  6. 《J2SE 回炉再造14》-------溺水狗

    第八章 容器类 Collection接口(Set.List.Map).Iterator接口.Comparable接口 提纲 容器的概念 Set无序不可重复,List有序可重复,两者是否equals C ...

  7. 《J2SE 回炉再造12》-------溺水狗

    第六章 数组 提纲: 一维数组概述: 元素类型为引用类型的数组: 3. 数组的初始化 注: 理解main函数中的参数:String[] args 约瑟夫环(逢三减一):选择排序法.冒泡排序法以及数组模 ...

  8. 《J2SE 回炉再造11》-------溺水狗

    第五章 异常 1. 提纲 2. Java异常概念 注:catch到的所有异常都必须进行操作,不然容易被忽略吞噬,造成安全隐患. 实例 3. 异常的分类 注:异常分类图十分重要,要分清哪种Excepti ...

  9. 《J2SE 回炉再造10》-------溺水狗

    第四章 面向对象编程基础篇(6) Java相关API介绍 当大家从"HelloWorld"接触到Java时,证明我们正式诞生Java世界的新手村中,当我们在新手村练到一定等级时,渴 ...

最新文章

  1. 关于android 5.0报错:dlopen failed: couldn't map ... Permission denied
  2. python爬虫鼠标模拟悬停并点击
  3. 微软启用全新域名邮箱平台 Outlook.com
  4. SPT20 协议_至为芯科技IP5356又一款支持PD输出20W的全协议快充移动电源SOC
  5. python图标icon_用Python提取exe图标icon
  6. 解决xhost:unable to open display的问题
  7. Ajax--WebService返回复杂二维数组
  8. Android Studio配置Gradle(包括signingConfigs、buildTypes和productFlavors等)
  9. 雷军:小米有机会重返世界前三;苹果为 Siri 泄露隐私事件致歉;Apache Tomcat 9.0.24 发布 | 极客头条...
  10. UIDynamic(物理仿真)
  11. django连接mysql
  12. i310100和i59400f哪个好 i3 10100和i5 9400f差距大吗
  13. 信令传送协议-SCTP协议解析
  14. 台电tbook10s官方固件_【11月1日】台电官方系统固件更新
  15. MD5加密算法特点及简单实现(Java)
  16. java 期刊杂志参考_参考文献可以引用整本期刊杂志吗?格式怎么写?
  17. 直播云平台架构如何构建?
  18. Error: Converting circular structure to JSON解决方法
  19. 【问题解决】Cause: java.io.IOException: Could not find resource com/kuang/dao/UserMapper.xml
  20. 计算机终端网络准入管理规定,网络准入与终端安全.doc

热门文章

  1. python爬虫设计_python3爬虫之设计签名小程序
  2. Rockchip RK3588获取芯片的实时温度
  3. 从零讲JAVA,给你一条清晰地学习道路,该学什么就学什么
  4. sigmatube软件破解思路与方向
  5. HTML5 从零基础到实战(一)HTML基本介绍
  6. 业余程序员余流 - 杂谈 之 《不知道怎么说出口》
  7. 从校园到职场 - 技能与职位
  8. 25岁上下的你,现在混得怎么样?豆瓣6000 热帖,看完心里真的好难受!
  9. FEC-Reed-Solomon算法浅析(一)
  10. CF538G Berserk Robot