2

时间:2017年07月08日星期六
说明:本文部分内容均来自慕课网。@慕课网:http://www.imooc.com
教学源码:无
学习源码:https://github.com/zccodere/s...

第一章:课前准备

1-1 前言

课程说明

比较Thread和Runnable这两种线程创建的方式,需要知道Thread和Runnable的基本创建方式。

课程目标和学习内容

线程创建的两种方式比较
线程的生命周期
线程的守护神:守护线程

第二章:Thread VS Runnable

2-1 回顾线程创建的两种方式

方式一:继承Thread类

clipboard.png

方式二:实现Runnable接口

clipboard.png

线程创建的两种方式

clipboard.png

2-2 应用Thread模拟卖票

两种方式的比较

Runnable方式可以避免Thread方式由于Java单继承特性带来的缺陷
Runnable的代码可以被多个线程(Thread实例)共享,适合于多个线程处理同一个资源的情况

案例:模拟买票

clipboard.png

代码演示

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();
    }
    
}

运行结果

clipboard.png

clipboard.png

总共有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();
        
    }
    
}

运行结果

clipboard.png

2-4 应用揭秘

两种方式的区别

clipboard.png

第三章:线程的生命周期和守护线程

3-1 线程的生命周期

线程的生命周期

clipboard.png

创建

新建一个线程对象,如Threaf thd = new Thread()

就绪

创建了线程对象后,调用了线程的start()方法(注意:此时线程只是进入了线程队列,等待获取CPU服务,具备了运行的条件,但并不一定已经开始运行了)

运行

处于就绪状态的线程,一旦获取了CPU资源,便进入到运行状态,开始执行run()方法里面的逻辑

终止

线程的run()方法执行完毕,或者线程调用了stop()方法,线程便进入终止状态    

阻塞

一个正在执行的线程在某些情况下,由于某种原因而暂时让出了CPU资源,暂停了自己的执行,便进入了阻塞状态,如调用了sleep()方法

阻塞状态示意图

clipboard.png

clipboard.png

clipboard.png

clipboard.png

3-2 守护线程理论知识

Java线程有两类

用户线程:运行在前台,执行具体的任务
    程序的主线程、连接网络的子线程等都是用户线程
守护线程:运行在后台,为其他前台线程服务
    特点:一旦所有用户线程都结束运行,守护线程会随JVM一起结束工作
    应用:数据库连接池中的监测线程、JVM虚拟机启动后的监测线程
    最常见的守护线程:垃圾回收线程

如何设置守护线程

可以通过调用Thread类的setDaemon(true)方法来设置当前的线程为守护线程

注意事项

setDaemon(true)必须在start()方法之前调用,否则会抛出IllegalThreadStateException异常
在守护线程中产生的新线程也是守护线程
不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑

3-3 守护线程代码示例

模拟场景示意图

clipboard.png

模拟场景说明

一共有两个线程,一个主线程,一个守护线程。守护线程会在很长的时间内不停的往文件中写数据,主线程会阻塞等待来自键盘的输入。一旦主线程获取到了用户的输入,这时候,阻塞就会解除掉,主线程继续运行,直到结束。而一旦主线程结束,用户线程就没有了。这时候即使数据还没有写完,守护线程也会随虚拟机一起结束运行。

代码演示

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生成线程快照

常用查看线程工具

clipboard.png

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)

妙手空空
1.3k 声望368 粉丝

博观而约取,厚积而薄发