细说多线程之Thread VS Runnable
线程创建的两种方式
继承 Thread 类:
class MyThread extends Thread{
....
@Override
public void run(){
.....
}
}
MyThread mt = new MyThread(); // 创建线程
mt.start(); // 启动线程 - 调用start方法,线程进入线程队列中,等待CPU服务,一旦获取到CPU的时间片,就会转到run方法中执行,线程启动。run方法执行完,线程消亡。
实现Runnable接口:
class MyThread implements Runnable{
....
@Override
public void run(){
.....
}
}
MyThread mt = new MyThread();
Thread td = new Thread(mt); // 创建线程
td.start(); // 启动线程
-
Runnable方式可以避免Thread方式由于Java单继承特性带来的缺陷
-
Runnable代码可以被多个线程(Thread实例)共享,适合于多个线程处理同一资源的情况
/**
* Thread模拟火车站卖票
*
* @author Administrator
*
*/
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();
}
}
class MyThread extends Thread {
private int ticketsCont = 5; // 一共5张火车票
private String name; // 窗口,也即是线程的名字
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
while (ticketsCont > 0) {
ticketsCont--; // 如果还有票就卖掉一张
System.out.println(name + "卖了一张票,剩余票数为:" + ticketsCont);
}
}
}
/**
* Runnable模拟火车站卖票
*
* @author Administrator
*
*/
public class TicktsRunnable {
public static void main(String[] args) {
// 创建三个线程,模拟三个窗口卖票(共享一个Runnable对象)
MyRunnable mr = new MyRunnable();
Thread th1 = new Thread(mr,"窗口1") ;
Thread th2 = new Thread(mr,"窗口2") ;
Thread th3 = new Thread(mr,"窗口3") ;
/* 不共享
MyRunnable mr1 = new MyRunnable();
MyRunnable mr2 = new MyRunnable();
MyRunnable mr3 = new MyRunnable();
Thread th1 = new Thread(mr1,"窗口1") ;
Thread th2 = new Thread(mr2,"窗口2") ;
Thread th3 = new Thread(mr3,"窗口3") ;
*/
// 启动三个线程,即窗口开始卖票
th1.start();
th2.start();
th3.start();
}
}
class MyRunnable implements Runnable {
private int ticketsCont = 5; // 一共5张火车票
@Override
public void run() {
while (ticketsCont > 0) {
ticketsCont--; // 如果还有票就卖掉一张
System.out.println(Thread.currentThread().getName() + "卖了一张票,剩余票数为:" + ticketsCont);
}
}
}
线程的生命周期
003.png创建:新建一个线程对象,如 Thread thd = new Thread()
就绪状态:创建了线程对象之后,调用了线程的start()方法(注意:此时线程只是进入了线程队列,等待获取CPU服务,具备了运行的条件,但并不一定已经开始运行了)
运行:处于就绪状态的线程,一旦获取了CPU资源,便进入到运行状态,开始执行run()方法里面的逻辑。
终止:线程的run()方法执行完毕,或者线程调用了stop()方法(已淘汰),线程便进入终止状态
阻塞:一个正在执行的线程在某些情况下,由于某种原因而暂时让出CPU资源,暂停了自己的执行,便进入了阻塞状态,如调用了sleep()方法。
004.png守护线程
用户线程:运行在前台,执行具体的任务。程序的主线程、连接网络的子线程等都是用户线程。
守护线程:运行在后台,为其他前台线程服务。
特点:一旦所有用户线程都结束运行,守护线程会随JVM一起结束工作。
应用:数据库连接池中的监测线程
JVM虚拟机启动后的监测线程
最常见的守护线程:垃圾回收线程
如何设置守护线程:
可以通过调用Thread类的setDaemon(true)方法来设置当前的线程为守护线程
注意:
- setDaemon(true)必须在start()方法之前调用,否则会抛出IllegalThreadStateException异常
- 在守护线程中产生的新线程也是守护线程
- 不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑(一旦所有的用户线程都退出运行,守护线程失去存在的必要,随JVM一起结束工作,如果在守护线程中一些读写操作执行到一半,所有用户线程退出,守护线程结束,读写操作未进行完,程序崩溃)
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.Scanner;
/**
* 模拟场景:两个线程,一个主线程,一个守护线程; 守护线程很长时间内不断往文件写数据,主线程阻塞等待来自键盘的输入,
* 一旦主线程获取用户的输入,阻塞结束,主线程继续运行直至结束。 一旦主线程结束,用户线程则消亡。这是及时数据未写完,守护线程也会随虚拟机一起结束运行。
*
* @author Administrator
*
*/
public class DaemonThread {
public static void main(String[] args) {
System.out.println("程序进入主线程" + Thread.currentThread().getName());
DaemonRunnable dr = new DaemonRunnable();
Thread tr = new Thread(dr);
tr.setDaemon(true);
tr.start();
Scanner sc = new Scanner(System.in);
sc.next();
System.out.println("程序退出主线程" + Thread.currentThread().getName());
}
}
class DaemonRunnable 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("E:" + 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++);
Thread.sleep(1000);
}
}
}
jmap.exe :生成堆快照
jstat.exe :监测虚拟机运行状态,可以查看JVM中类加载情况,所持有的情况
jconsole.exe:界面化工具,和jstat一样,也是用来监测虚拟机的运行状态
jstack
- 作用:生成JVM当前时刻线程的快照(threaddump,即当前进程中所有线程的信息)
- 目的:帮助定位程序问题出现的原因,如长时间停顿、CPU占用率过高等。
线程状态。 线程可以处于以下状态之一:
- NEW
尚未启动的线程处于此状态。 - RUNNABLE
在Java虚拟机中执行的线程处于此状态。 - BLOCKED
被阻塞等待监视器锁定的线程处于此状态。 - WAITING
正在等待另一个线程执行特定动作的线程处于此状态。 - TIMED_WAITING
正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。 - TERMINATED
已退出的线程处于此状态。
一个线程可以在给定时间点处于一个状态。 这些状态是不反映任何操作系统线程状态的虚拟机状态。
$ jstack -l 58860 // jstack -l pid
2018-04-14 14:18:08 // 当前线程快照生成的时间
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.144-b01 mixed mode): // Eclipse运行时使用的JRE的类型和版本(HotSpot(TM) 64-Bit),虚拟机分为服务器端(Server)版本和客户端(Client)版本
// daemon表示是守护线程,tid nid 标识线程的参数,都是16进制的,结合top -h指令可以很方便的定位CPU占用率很高的线程
"Thread-0" #10 daemon prio=5 os_prio=0 tid=0x000000001adaf000 nid=0xe56c waiting on condition [0x000000001b87f000]
java.lang.Thread.State: TIMED_WAITING (sleeping) // 线程状态 - 阻塞状态
at java.lang.Thread.sleep(Native Method) // sleep()方法
at com.thread.imooc.DaemonRunnable.writeToFile(DaemonThread.java:61)
at com.thread.imooc.DaemonRunnable.run(DaemonThread.java:44)
at java.lang.Thread.run(Thread.java:748)
Locked ownable synchronizers: // 告诉我们当前线程是否处于线程块内
- None // 没有
"Service Thread" #9 daemon prio=9 os_prio=0 tid=0x000000001ace2800 nid=0xe460 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"C1 CompilerThread2" #8 daemon prio=9 os_prio=2 tid=0x000000001acd1000 nid=0xe474 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"C2 CompilerThread1" #7 daemon prio=9 os_prio=2 tid=0x000000001acce000 nid=0xe520 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"C2 CompilerThread0" #6 daemon prio=9 os_prio=2 tid=0x000000001accb800 nid=0xd95c waiting on condition [0x0000000000000000] // 调用JITing,实时编译装卸class
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000001acca000 nid=0xe540 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x0000000019a7e000 nid=0xe580 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE // 负责分发内部事件
Locked ownable synchronizers:
- None
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x0000000019a48000 nid=0xe578 in Object.wait() [0x000000001ac7f000]
java.lang.Thread.State: WAITING (on object monitor) // 负责调用Finalizer方法
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000d6e08f20> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
- locked <0x00000000d6e08f20> (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=0x0000000019a43800 nid=0xe574 in Object.wait() [0x000000001ab7f000] // 负责处理引用
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000d6e06ba8> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x00000000d6e06ba8> (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=0x0000000000b71000 nid=0xe174 runnable [0x0000000004d2f000]
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 <0x00000000d6e5a1b8> (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 <0x00000000d6ed7398> (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.thread.imooc.DaemonThread.main(DaemonThread.java:28)
Locked ownable synchronizers:
- None
"VM Thread" os_prio=2 tid=0x0000000019a3c800 nid=0xe59c runnable // 虚机内部线程,在Jconsole下看不见
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000000b87800 nid=0xe5bc runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000000b89800 nid=0xe558 runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x0000000000b8b000 nid=0xe55c runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x0000000000b8c800 nid=0xe52c runnable
"VM Periodic Task Thread" os_prio=2 tid=0x000000001aced000 nid=0xe470 waiting on condition // 虚机内部线程,在Jconsole下看不见
JNI global references: 6