时间:2017年07月08日星期六
说明:本文部分内容均来自慕课网。@慕课网:http://www.imooc.com
教学源码:无
学习源码:https://github.com/zccodere/s...
第一章:课前准备
1-1 前言
课程说明
比较Thread和Runnable这两种线程创建的方式,需要知道Thread和Runnable的基本创建方式。
课程目标和学习内容
线程创建的两种方式比较
线程的生命周期
线程的守护神:守护线程
第二章:Thread VS Runnable
2-1 回顾线程创建的两种方式
方式一:继承Thread类
方式二:实现Runnable接口
线程创建的两种方式
2-2 应用Thread模拟卖票
两种方式的比较
Runnable方式可以避免Thread方式由于Java单继承特性带来的缺陷
Runnable的代码可以被多个线程(Thread实例)共享,适合于多个线程处理同一个资源的情况
案例:模拟买票
代码演示
1.编写MyThread类
package com.myimooc.ticketsthread;
/**
* 使用 Thread 创建线程
* @author ZhangCheng on 2017-07-08
*
*/
public class MyThread extends Thread {
/** 一共有5张火车票 */
private int ticketsCont = 5;
/** 窗口,也即是线程的名字 */
private String name;
public MyThread(String name){
this.name = name;
}
// 写买票逻辑
@Override
public void run() {
while(ticketsCont > 0 ){
// 如果还有票,就卖掉一张
ticketsCont--;
System.out.println(name + "卖了1张票,剩余票数为:"+ticketsCont);
}
}
}
2.编写TicketsThread类
package com.myimooc.ticketsthread;
/**
* 主类-启动线程类
* @author ZhangCheng on 2017-07-08
*
*/
public class TicketsThread {
public static void main(String[] args) {
// 创建三个线程,模拟三个窗口卖票
MyThread mt1 = new MyThread("窗口1");
MyThread mt2 = new MyThread("窗口2");
MyThread mt3 = new MyThread("窗口3");
// 启动这三个线程,即窗口开始卖票
mt1.start();
mt2.start();
mt3.start();
}
}
运行结果
总共有5张票,但是三个窗口加在一起卖了15张票。造成有些人买了票,上不了车,这种情况不是我们愿意看到的。具体原因,写完Runnable后,会讲解。
2-3 应用Runnable模拟卖票
代码演示
1.编写MyThread类
package com.myimooc.ticketsrunnable;
/**
* 使用 Runnable 创建线程
* @author ZhangCheng on 2017-07-08
*
*/
public class MyThread implements Runnable {
/** 一共有5张火车票 */
private int ticketsCont = 5;
// 写买票逻辑
@Override
public void run() {
while(ticketsCont > 0 ){
// 如果还有票,就卖掉一张
ticketsCont--;
System.out.println(Thread.currentThread().getName() + "卖了1张票,剩余票数为:"+ticketsCont);
}
}
}
2.编写TicketsRunnable类
package com.myimooc.ticketsrunnable;
/**
* 主类-启动线程类
* @author ZhangCheng on 2017-07-08
*
*/
public class TicketsRunnable {
public static void main(String[] args) {
MyThread mt = new MyThread();
// 创建三个线程,模拟三个窗口卖票
Thread th1 = new Thread(mt,"窗口1");
Thread th2 = new Thread(mt,"窗口2");
Thread th3 = new Thread(mt,"窗口3");
// 启动这三个线程,即窗口开始卖票
th1.start();
th2.start();
th3.start();
}
}
运行结果
2-4 应用揭秘
两种方式的区别
第三章:线程的生命周期和守护线程
3-1 线程的生命周期
线程的生命周期
创建
新建一个线程对象,如Threaf thd = new Thread()
就绪
创建了线程对象后,调用了线程的start()方法(注意:此时线程只是进入了线程队列,等待获取CPU服务,具备了运行的条件,但并不一定已经开始运行了)
运行
处于就绪状态的线程,一旦获取了CPU资源,便进入到运行状态,开始执行run()方法里面的逻辑
终止
线程的run()方法执行完毕,或者线程调用了stop()方法,线程便进入终止状态
阻塞
一个正在执行的线程在某些情况下,由于某种原因而暂时让出了CPU资源,暂停了自己的执行,便进入了阻塞状态,如调用了sleep()方法
阻塞状态示意图
3-2 守护线程理论知识
Java线程有两类
用户线程:运行在前台,执行具体的任务
程序的主线程、连接网络的子线程等都是用户线程
守护线程:运行在后台,为其他前台线程服务
特点:一旦所有用户线程都结束运行,守护线程会随JVM一起结束工作
应用:数据库连接池中的监测线程、JVM虚拟机启动后的监测线程
最常见的守护线程:垃圾回收线程
如何设置守护线程
可以通过调用Thread类的setDaemon(true)方法来设置当前的线程为守护线程
注意事项
setDaemon(true)必须在start()方法之前调用,否则会抛出IllegalThreadStateException异常
在守护线程中产生的新线程也是守护线程
不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑
3-3 守护线程代码示例
模拟场景示意图
模拟场景说明
一共有两个线程,一个主线程,一个守护线程。守护线程会在很长的时间内不停的往文件中写数据,主线程会阻塞等待来自键盘的输入。一旦主线程获取到了用户的输入,这时候,阻塞就会解除掉,主线程继续运行,直到结束。而一旦主线程结束,用户线程就没有了。这时候即使数据还没有写完,守护线程也会随虚拟机一起结束运行。
代码演示
1.编写DaemonThread类
package com.myimooc.daemonthread;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
/**
* 守护线程。使用 Runnable 创建线程
* @author ZhangCheng on 2017-07-08
*
*/
public class DaemonThread implements Runnable {
@Override
public void run() {
System.out.println("进入守护线程" + Thread.currentThread().getName());
try {
writeToFile();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("退出守护线程" + Thread.currentThread().getName());
}
private void writeToFile() throws Exception{
File fileName = new File("d:" + File.separator + "daemon.txt");
// 向文件中追加数据
OutputStream os = new FileOutputStream(fileName,true);
int count = 0;
while(count < 999){
os.write(("\r\nword" + count).getBytes());
System.out.println("守护线程" + Thread.currentThread().getName()
+ "向文件中写入了word" + count);
count++;
// 线程休眠1秒
Thread.sleep(1000);
}
os.close();
}
}
2.编写DaemonThreadDemo类
package com.myimooc.daemonthread;
import java.util.Scanner;
/**
* 主线程
* @author ZhangCheng on 2017-07-08
*
*/
public class DaemonThreadDemo {
public static void main(String[] args) {
System.out.println("进入主线程" + Thread.currentThread().getName());
DaemonThread daemonThread = new DaemonThread();
Thread thread = new Thread(daemonThread);
thread.setDaemon(true);
thread.start();
Scanner sc = new Scanner(System.in);
sc.next();
sc.close();
System.out.println("退出主线程" + Thread.currentThread().getName());
}
}
3-4 使用jstack生成线程快照
常用查看线程工具
jstack
作用:生成JVM当前时刻线程的快照(threaddump,即当前进程中所有线程的信息)
目的:帮助定位程序问题出现的原因,如长时间停顿、CPU占用率过高等
使用命令
jstack -l PID
生成线程快照
快照案例
2017-07-08 23:49:46
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.101-b13 mixed mode):
"Thread-0" #10 daemon prio=5 os_prio=0 tid=0x000000001d209800 nid=0x2e00 waiting on condition [0x000000001dd2f000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at com.myimooc.daemonthread.DaemonThread.writeToFile(DaemonThread.java:39)
at com.myimooc.daemonthread.DaemonThread.run(DaemonThread.java:19)
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- None
"Service Thread" #9 daemon prio=9 os_prio=0 tid=0x000000001d1b9800 nid=0x2480 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"C1 CompilerThread2" #8 daemon prio=9 os_prio=2 tid=0x000000001d13b000 nid=0x2078 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"C2 CompilerThread1" #7 daemon prio=9 os_prio=2 tid=0x000000001be2d800 nid=0x24f4 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"C2 CompilerThread0" #6 daemon prio=9 os_prio=2 tid=0x000000001bddf000 nid=0x2f64 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000001bdde000 nid=0x1c1c waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000000001bdc8800 nid=0x247c runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000000001bdba800 nid=0x1f10 in Object.wait() [0x000000001d12f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076b108ee0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
- locked <0x000000076b108ee0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)
Locked ownable synchronizers:
- None
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x00000000027a2800 nid=0x2214 in Object.wait() [0x000000001d02f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076b106b50> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x000000076b106b50> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
Locked ownable synchronizers:
- None
"main" #1 prio=5 os_prio=0 tid=0x000000000099d800 nid=0xf2c runnable [0x000000000228e000]
java.lang.Thread.State: RUNNABLE
at java.io.FileInputStream.readBytes(Native Method)
at java.io.FileInputStream.read(FileInputStream.java:255)
at java.io.BufferedInputStream.read1(BufferedInputStream.java:284)
at java.io.BufferedInputStream.read(BufferedInputStream.java:345)
- locked <0x000000076b159560> (a java.io.BufferedInputStream)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
- locked <0x000000076b1b9ce8> (a java.io.InputStreamReader)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.Reader.read(Reader.java:100)
at java.util.Scanner.readInput(Scanner.java:804)
at java.util.Scanner.next(Scanner.java:1369)
at com.myimooc.daemonthread.DaemonThreadDemo.main(DaemonThreadDemo.java:22)
Locked ownable synchronizers:
- None
"VM Thread" os_prio=2 tid=0x000000001bd98000 nid=0x2998 runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00000000026c6800 nid=0x11b0 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00000000026c8000 nid=0x26a4 runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00000000026c9800 nid=0x2d6c runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00000000026cb000 nid=0xab0 runnable
"VM Periodic Task Thread" os_prio=2 tid=0x000000001d1f3000 nid=0x2b08 waiting on condition
JNI global references: 6
总结
1.如果有daemon标记,则当前线程为守护线程
2.通过查看线程状态(java.lang.Thread.State: TIMED_WAITING (sleeping))
可以帮助我们定位到导致程序出现死锁,或者是阻塞问题的原因
3.tid和nid字段可以帮助我们找到CPU占有率很高的线程
第四章:课程总结
4-1 课程总结
课程总结
线程创建的两种方式回顾
线程创建的两种方式比较
线程的声明周期
守护线程
jsrack生成线程快照
建议
多使用Runnable这种方式创建线程
补充
1.程序中的同一资源指的是同一个Runnable对象
2.安全的卖票程序中需要加入同步(Synchronized)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。