1 线程基础知识
一个操作系统可以开启多个进程,一个进程可以包含多个线程。
举例说明:利用安卓手机打开weichat应用,并用其发送文字和语音的过程中;启动weichat引用就是开启了一个进程,发送文字和发送语音就是这个进程中的两个线程
2 创建线程
线程实例可以利用Thread和Runnable创建
2.1 利用Thread创建
》继承Thread类
》重写 run() 方法
技巧01:run() 方法中的方法体就是线程需要执行的逻辑
》利用new创建的Thread子类实例就是一个Thread实例
2.2 利用Runnable实现
》实现Tunnable接口
》实现 run() 方法
》借助Thread类创建Thread实例,将实现了Runnable接口的实现类实例作为参数传入
2.3 代码汇总
package demo10_thread.case01_create; /** * @author 王杨帅 * @create 2018-08-12 20:13 * @desc 利用Thread类创建线程 **/ public class CreateThreadByThread extends Thread { @Override public void run() { System.out.println("集成Thread创建的线程类"); } }
package demo10_thread.case01_create; /** * @author 王杨帅 * @create 2018-08-12 20:16 * @desc 利用Runnable接口创建实例 **/ public class CreateThreadByRunnable implements Runnable { @Override public void run() { System.out.println("利用Runnable接口创建线程"); } }
package demo10_thread.case01_create; /** * @author 王杨帅 * @create 2018-08-12 20:15 * @desc **/ public class Test01 { public static void main(String[] args) { // 01 创建线程方式01 CreateThreadByThread createThreadByThread = new CreateThreadByThread(); createThreadByThread.setName("利用Thread创建"); createThreadByThread.start(); // 02 创建线程方式02 CreateThreadByRunnable createThreadByRunnable = new CreateThreadByRunnable(); Thread thread = new Thread(createThreadByRunnable, "利用Runnable创建"); thread.start(); } }
2.4 Thread和Runnable的差别
2.4.1 需求
大足南站还剩5张从大足南站开往广州南站的春运高铁票,现在大足南站有三个售票窗口都可以售卖这些票,利用线程如何实现
2.4.2 思路
》在Thread子类或者Runnable实现类中利用一个实例变量ticketCount来代表剩余的车票
》每个售票窗口看成一个线程
2.4.3 利用Thread实现
2.4.4 利用Runnable实现
2.4.5 分析接代码汇总
利用Thread实现时,每个窗口都有五张票可以售卖;利用Runnable实现时只有5张票供三个窗口共同售卖;所以利用Runnable可以实现共享资源
技巧01:如果共享资源不在线程里面,那么Thread和Runnable实现的线程都可以实现资源共享
package demo10_thread.case02_maipiao; /** * @author 王杨帅 * @create 2018-08-12 20:23 * @desc **/ public class TicketThread extends Thread { public Integer ticketCount = 5; @Override public void run() { while (ticketCount > 0) { ticketCount -= 1; System.out.println(getName() + "卖了一张票,剩余票数:" + ticketCount); } } }
package demo10_thread.case02_maipiao; /** * @author 王杨帅 * @create 2018-08-12 20:36 * @desc **/ public class TicketRunnable implements Runnable { private Integer ticketCount = 5; @Override public void run() { synchronized (this) { while (ticketCount > 0) { ticketCount -= 1; System.out.println(Thread.currentThread().getName() + "卖了一张票,剩余票数:" + ticketCount); } } } }
package demo10_thread.case02_maipiao; /** * @author 王杨帅 * @create 2018-08-12 20:27 * @desc **/ public class Test { public static void main(String[] args) { // testThread(); testRunnable(); } public static void testRunnable() { TicketRunnable ticketRunnable = new TicketRunnable(); Thread thread01 = new Thread(ticketRunnable, "窗口01"); Thread thread02 = new Thread(ticketRunnable, "窗口02"); Thread thread03 = new Thread(ticketRunnable, "窗口03"); thread01.start(); thread02.start(); thread03.start(); /** * 笔记:利用Runnable创建线程你是会共享资源 */} public static void testThread() { TicketThread ticketThread01 = new TicketThread(); ticketThread01.setName("窗口01"); TicketThread ticketThread02 = new TicketThread(); ticketThread02.setName("窗口02"); TicketThread ticketThread03 = new TicketThread(); ticketThread03.setName("窗口03"); ticketThread01.start(); ticketThread02.start(); ticketThread03.start(); /** * 问题:总共有5张票,却卖出了15张票 */} }
3 线程分类
3.1 用户线程
运行在前台,执行具体的任务(看得到,摸不着),也叫作“前台线程”
例如:程序的主线程、连接网络的子线程等都是用户线程
3.2 守护线程
运行在后台,为其他前台线程服务
技巧01:一旦所有的用户线程都结束运行,守护线程就会随JVM一起结束工作
应用:数据库连接池中的检测线程、JVM虚拟机启动后的检测线程、垃圾回收线程
开启守护线程:通过调用线程实例的setDaemon(true)就可以将当前线程设置为守护线程
技巧01:setDaemon(true) 必须在start() 方法之前调用,否则就会抛出IllegalThreadStateException异常
技巧02:在守护线程中产生的新线程也会自动成为守护线程
坑01:并不是一切任务都可以分配个守护线程来执行的,比如读写操作,因为当进行读写操作的守护线程读写到一半的时候如果前台线程结束运行啦,此时守护线程也会跟着结束,从而造成读写不完整的Bug.
3.3 利用守护线程实现文件写操作
【PS: 实际开发中不能将文件读写操作放到守护线程中执行,此处只是为了演示前台线程结束后守护线程也会跟着结束的效果】
package demo10_thread.case03_daemaon; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.OutputStream; /** * @author 王杨帅 * @create 2018-08-12 21:57 * @desc **/ public class DaemonThread implements Runnable { @Override public void run() { System.out.println("进入守护线程:" + Thread.currentThread().getName()); try { writeInfo(); } catch (Exception e) { e.printStackTrace(); } System.out.println("退出守护线程:" + Thread.currentThread().getName()); } private void writeInfo() throws Exception { File file = new File("d:" + File.separator + "test.txt"); OutputStream outputStream = new FileOutputStream(file); Integer count = 0; while (count < 99) { outputStream.write((" word" + count).getBytes()); System.out.println("守护线程:" + Thread.currentThread().getName() + "向test.txt文件中写入了 word" + count++); Thread.sleep(1000); } } }
package demo10_thread.case03_daemaon; import java.util.Scanner; /** * @author 王杨帅 * @create 2018-08-12 21:58 * @desc **/ public class Test { public static void main(String[] args) throws InterruptedException { System.out.println("进入主线程:" + Thread.currentThread().getName()); DaemonThread daemonThread = new DaemonThread(); Thread thread = new Thread(daemonThread, "fury"); thread.setDaemon(true); thread.start(); // 阻塞操作 // Thread.sleep(10000); Scanner scanner = new Scanner(System.in); scanner.next(); System.out.println("退出主线程:" + Thread.currentThread().getName()); } }
4 查看线程信息
4.1 jstack
jstack是安装JDK时自带的一个线程分析工具,位于JDK安装目录中bin目录下
4.2 查看步骤
》启动java应用,到任务管理器中去寻找启动的java应用的PID信息
》在控制台中执行命令
》找到对应线程的名称,查看线程当前信息
》线程状态说明
Thread 位于java.lang包下
1 剧情简介
欢迎各位关总观看《隋唐演义》——旁白
观众陆续入场......(模拟时间:5s)——旁白
舞台大幕缓缓拉开,广播提示:“请各位观众将自己的手机调味静音,演出即将开始。”(模拟时间5s)——旁白
隋朝末年,隋军和农民军杀得浑天黑地......——旁白
推翻隋朝,使百姓安居乐业。
推翻隋朝,使百姓安居乐业。
隋军
镇压刁明,为朝廷除害。
起义
推翻隋朝,使百姓安居乐业。
隋军
镇压刁明,为朝廷除害。
镇压刁明,为朝廷除害。
正当双方激战正酣时,半路杀出一个程咬金——旁白
程咬金
程咬金的理想就是结束战争,使百姓安居乐业!
双方军队停止战斗,主角程咬金开始施展才华。——旁白
经过程咬金的奋力拼杀战争终于结束,人民安居乐业,程咬金实现了他的人生梦想......——旁白
谢谢观看《隋唐演义》,请观众有序退场。
——旁白
2 程序模拟
2.1 程序说明
测试
2.2 程序实现
》创建一个军队Runnable类,创建一个主角Thread类,创建一个舞台Thread类
》在舞台类中创建两个军队线程实例来模拟隋军和起义军,创建一个主角线程实例来模拟程咬金
》两个军队线程交替执行来模拟军队的厮杀
》厮杀一段时间后,程咬金线程登场,然后关闭所有军队线程来模拟程咬金的主角光环
》关闭程咬金线程,整个舞台线程中的逻辑执行完毕