引例:单线程不能满足"同时"的需求
假如我要实现如下功能
程序不停地在屏幕上输出一句问候的语句(比如“你好”)
“同时”,当我通过键盘输入固定输入的时候,程序停止向屏幕输出问候的语句(比如说输入exit)
单线程
无法满足同时的需求
package _19thread01.com.cskaoyan._01introduction;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;
/**
* @description:
* @author: 景天
* @date: 2022/6/27 15:54
**/
/*
假如我要实现如下功能
程序不停地在屏幕上输出一句问候的语句(比如“你好”)
“同时”,当我通过键盘输入固定输入的时候,程序停止向屏幕输出问候的语句(比如说输入gun)
单线程的
*/
public class Demo {
public static boolean flag = true;
public static void main(String[] args) {
System.out.println("say hello before");
sayHello();
System.out.println("say hello after");
System.out.println("wait before");
waitToStop();
System.out.println("wait after");
}
private static void waitToStop() {
// 创建Scanner对象
Scanner scanner = new Scanner(System.in);
// 循环
while (flag) {
// 键盘接收数据
String s = scanner.nextLine();
// 判断
if ("gun".equals(s)) {
// 结束 更改flag值
flag = false;
break;
}
}
}
private static void sayHello() {
// 不停输出你好
while (flag) {
System.out.println("你好");
// 休息一下
try {
// 暂停程序执行3s
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
多线程改进
package _19thread01.com.cskaoyan._01introduction;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;
/**
* @description:
* @author: 景天
* @date: 2022/6/27 15:54
**/
/*
假如我要实现如下功能
程序不停地在屏幕上输出一句问候的语句(比如“你好”)
“同时”,当我通过键盘输入固定输入的时候,程序停止向屏幕输出问候的语句(比如说输入gun)
多线程改进
*/
public class Demo2 {
public static boolean flag = true;
public static void main(String[] args) {
System.out.println("say hello before");
sayHello();
System.out.println("say hello after");
System.out.println("wait before");
waitToStop();
System.out.println("wait after");
}
private static void waitToStop() {
new Thread(new Runnable() {
@Override
public void run() {
// 创建Scanner对象
Scanner scanner = new Scanner(System.in);
// 循环
while (flag) {
// 键盘接收数据
String s = scanner.nextLine();
// 判断
if ("gun".equals(s)) {
// 结束 更改flag值
flag = false;
break;
}
}
}
}).start();
}
private static void sayHello() {
new Thread(new Runnable() {
@Override
public void run() {
// 不停输出你好
while (flag) {
System.out.println("你好");
// 休息一下
try {
// 暂停程序执行3s
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
操作系统基本概念
进程(process)与线程(thread)
进程
- 计算机程序在某个数据集合上的运行活动.进程是操作系统进行资源调度与分配的基本单位
- 正在运行的程序或者软件
线程
- 进程中有多个子任务,每个子任务就是一个线程. 从执行路径的角度看, 一条执行路径就是一个线程
- 线程是CPU进行资源调度与分配的基本单位
进程与线程的关系
- 线程依赖于进程而存在
- 一个进程中可以有多个线程(最少1个)
- 线程共享进程资源
- 举例: 迅雷
串行(serial),并行(parallel)与并发(concurrency)
串行
- 一个任务接一个任务按顺序执行
并行
- 在同一个时间点(时刻)上多个任务同时运行
并发
- 在同一时间段内,多个任务同时运行
同步(synchronization)与异步(asynchronization)
有2个任务(业务) A B
同步:
- A任务执行的时候B不能执行,按顺序执行
- 你走我不走
异步:
- A任务执行的时候,B任务可以执行
- 你走你的,我走我的,互相不干扰
- 多线程是天生异步的
假设去书店买本java书, 给老板打了个电话,问一下 有没有java编程思想这本书
同步: 老板接通电话,告诉我去找一下,电话没有挂断, 我在电话另一端等待老板给我回复
异步:老板接通电话,告诉我去找一下,电话挂断了,等到老板找到了,再打电话通知我.
单道批处理: 内存中只能有1个进程在运行
多道批处理: 内存中可以有多个进程在运行 , 进程的上下文切换
现代操作:引入了线程
java程序运行原理
java命令+主类类名运行原理
- java命令会启动jvm进程, 进程会去创建一个线程 , main线程
- 执行main线程的main方法
jvm是单线程还是多线程的
结论: 多线程的
至少还有1个线程是垃圾回收线程,负责回收垃圾
单线程的实现方式一:继承Thread类
线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程
文档示例请添加图片描述
步骤
- 定义一个类继承Thread类
- 重写run方法
- 创建子类对象
- 通过start方法启动线程
package _19thread01.com.cskaoyan._02implone;
/**
* @description: 多线程的实现方式一
* @author: 景天
* @date: 2022/6/27 17:15
**/
/*
1. 定义一个类继承Thread类
2. 重写run方法
3. 创建子类对象
4. 通过start方法启动线程
*/
public class Demo {
public static void main(String[] args) {
//3. 创建子类对象
MyThread t = new MyThread();
//4. 通过start方法启动线程
t.start();
}
}
// 1. 定义一个类继承Thread类
class MyThread extends Thread{
// 2. 重写run方法
@Override
public void run() {
System.out.println("线程执行了!");
}
}
注意事项
多线程的执行特点是什么?
执行是随机的
start方法跟run方法有什么区别?
run方法调用,并没有开启新的执行路径,还是单线程在运行, 还是按顺序执行的(相当于普通方法调用)
只要start方法才是开辟了新的执行路径,才会启动线程
使用run方法执行的结果
main start
0
1
2
3
4
5
6
7
8
9
main end
使用start方法执行的结果
main start
main end
0
1
2
3
4
5
6
7
8
9
同一个线程能否启动多次?
- 不能启动多次
- java.lang.IllegalThreadStateException
谁才代表一个线程?
Thread及其子类对象才代表线程
设置获取线程名称
获取名称
String | getName() 返回该线程的名称。 |
---|---|
默认名称Thread-“线程编号 从0开始” |
设置名称
void | setName(String name) 改变线程名称,使之与参数 name 相同。 |
---|---|
Thread(String name) 分配新的 Thread 对象. 构造方法也可以设置名字 |
如何获取主线程的名称
static Thread | currentThread() 返回对当前正在执行的线程对象的引用。 |
---|---|
package _19thread01.com.cskaoyan._02implone;
/**
* @description: 获取设置线程名称
* @author: 景天
* @date: 2022/6/27 17:40
**/
public class Demo4 {
public static void main(String[] args) {
// currentThread()返回当前正在运行的线程对象的引用
Thread thread = Thread.currentThread();
String name = thread.getName();
System.out.println("主线程名称:"+ name);
// 创建子类对象
MyThread4 t = new MyThread4("王道彭于晏");
// setName(String name)
// 设置线程名称
//t.setName("王道吴彦祖");
// start启动线程
t.start();
}
}
class MyThread4 extends Thread{
public MyThread4(String name) {
super(name);
}
// run
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+i);
}
}
}
线程的调度方式
什么是线程调度
给线程分配CPU处理权的过程
调度方式的分类
- 协同式线程调度
- 线程的执行时间由线程本身觉得,当线程执行完后报告操作系统,切换到别的线程执行
- 抢占式线程调度方式
- 线程的执行时间由系统决定,哪个线程抢到,哪个线程执行
java中采用哪种调度方式
java中采用的是抢占式的线程调度方式
线程的优先级(了解)
操作系统优先级
- 动态优先级
- 正在运行的线程会随着运行时间的延长,优先级会降低
- 正在等待的线程会随着等待的时间的延长,优先级升高
- 静态优先级
- 固定值
java中优先级
静态优先级(1-10)
每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程
static int | MAX_PRIORITY 线程可以具有的最高优先级。 10 |
---|---|
static int | MIN_PRIORITY 线程可以具有的最低优先级。 1 |
static int | NORM_PRIORITY 分配给线程的默认优先级。 5 |
获取设置线程优先级
int | getPriority() 返回线程的优先级。 |
---|---|
void | setPriority(int n) 设置线程的优先级 |
package _20thread02.com.cskaoyan._01api;
/**
* @description:
* @author: 景天
* @date: 2022/6/28 9:28
**/
public class PriorityDemo {
public static void main(String[] args) {
// 创建子类对象
MyThread t = new MyThread();
// 设置优先级
t.setPriority(Thread.MAX_PRIORITY);
// 获取线程的优先级
int priority = t.getPriority();
System.out.println(priority);
// start
t.start();
}
}
class MyThread extends Thread{
// run
@Override
public void run() {
System.out.println("111111");
}
}
练习:
创建并启动2个线程 A B
要求2个线程分别打印10个数
A线程为最高优先级 B线程为最低优先级
===> A先打印10个数 然后B再去打印?
package _20thread02.com.cskaoyan._01api;
/**
* @description:
* @author: 景天
* @date: 2022/6/28 9:28
**/
public class PriorityDemo2 {
public static void main(String[] args) {
// 创建子类对象
MyThread2 t1 = new MyThread2();
MyThread2 t2 = new MyThread2();
// 设置优先级
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
t1.setName("A");
t2.setName("B");
// start
t1.start();
t2.start();
}
}
class MyThread2 extends Thread{
// run
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+"---"+i);
}
}
}
总结
结论: java中的优先级用处不大, 作为了解
然而,我们在java语言中设置的线程优先级,它仅仅只能被看做是一种"建议"(对操作系统的建议),
实际上,操作系统本身,有它自己的一套线程优先级 (静态优先级 + 动态优先级)
java官方: 线程优先级并非完全没有用,我们Thread的优先级,它具有统计意义,总的来说,高优先级的线程
占用的cpu执行时间多一点,低优先级线程,占用cpu执行时间,短一点
线程控制API
线程休眠sleep
static void | sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行), |
---|---|
package _20thread02.com.cskaoyan._01api;
import java.util.concurrent.TimeUnit;
/**
* @description:
* @author: 景天
* @date: 2022/6/28 9:57
**/
public class SleepDemo {
public static void main(String[] args) {
// 创建子类对象并启动
new ThreadSleep().start();
}
}
class ThreadSleep extends Thread{
// run
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
// 执行sleep 跟TimeUnit 效果一样
try {
//Thread.sleep(1000);
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程合并join
void | join() 等待该线程终止 |
---|---|
- 谁等待?
- 主线程等待子线程,join这行代码在哪个线程上运行 , 就是哪个线程等待
- 等待谁?
- 等待的是子线程, 哪个线程调用了join,等待的就是哪个线程
Demo
package _20thread02.com.cskaoyan._01api;
/**
* @description:
* @author: 景天
* @date: 2022/6/28 10:00
**/
// 线程合并
public class JoinDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println("main start");
// 创建子类对象
ThreadJoin t = new ThreadJoin();
// 启动线程
t.start();
t.join();
// 让main 打印
for (int i = 0; i <3; i++) {
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("main end");
}
}
class ThreadJoin extends Thread{
// run
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+"---"+i);
// 休眠
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
没有使用join之前的结果
main start
0
Thread-0---0
1
Thread-0---1
2
Thread-0---2
main end
Thread-0---3
Thread-0---4
Thread-0---5
Thread-0---6
Thread-0---7
Thread-0---8
Thread-0---9
调用join之后的结果
main start
Thread-0---0
Thread-0---1
Thread-0---2
Thread-0---3
Thread-0---4
Thread-0---5
Thread-0---6
Thread-0---7
Thread-0---8
Thread-0---9
0
1
2
main end
线程礼让yield
static void | yield() 暂停当前正在执行的线程对象,并执行其他线程。 |
---|---|
练习:
启动2个线程 A B , 分别打印10个数
要求A打印0, B打印0, A打印1, B打印1…
结论 : 实现不了
// java中采用抢占式的调度方式 虽然这个线程通过yield放弃了CPU的执行权
// 但是还能参与下轮的CUP的竞争 下轮谁抢到谁执行
package _20thread02.com.cskaoyan._01api;
/**
* @description: 线程礼让
* @author: 景天
* @date: 2022/6/28 10:14
**/
/*
启动2个线程 A B , 分别打印10个数
要求A打印0, B打印0, A打印1, B打印1.....
*/
public class YieldDemo {
public static void main(String[] args) {
// 创建2个线程并启动
ThreadYield t1 = new ThreadYield();
ThreadYield t2 = new ThreadYield();
t1.setName("A");
t2.setName("B");
// start
t1.start();
t2.start();
}
}
class ThreadYield extends Thread{
// run
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+"---"+i);
//立刻执行yield 暂停当前正在执行的线程对象,并执行其他线程。
Thread.yield();
// java中采用抢占式的调度方式 虽然这个线程通过yield放弃了CPU的执行权
// 但是还能参与下轮的CUP的竞争 下轮谁抢到谁执行
}
}
}
守护线程setDaemon
线程分类:
- 用户线程
- 系统的工作线程(默认的)
- 守护线程
- 为系统服务的后台线程(比如垃圾回收线程) , 守护线程就是为用户线程服务, 当做用户线程的"奴仆,佣人"
void | setDaemon(boolean on) 将该线程标记为守护线程或用户线程。 |
---|---|
on - 如果为 true,则将该线程标记为守护线程 |
注意
- 当正在运行的线程都是守护线程时,Java 虚拟机退出。
- 该方法必须在启动线程前调用。 (start前) java.lang.IllegalThreadStateException
package _20thread02.com.cskaoyan._01api;
/**
* @description: 守护线程
* @author: 景天
* @date: 2022/6/28 10:51
**/
public class DaemonDemo {
public static void main(String[] args) {
// 创建子类对象
ThreadDaemon t = new ThreadDaemon();
// 设置为守护线程
t.setDaemon(true);
// start
t.start();
for (int i = 0; i < 3; i++) {
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class ThreadDaemon extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+"---"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程中断stop(已过时,了解)
void | stop() 已过时。 该方法具有固有的不安全性 |
---|---|
package _20thread02.com.cskaoyan._01api;
/**
* @description: 中断线程
* @author: 景天
* @date: 2022/6/28 10:59
**/
public class StopDemo {
public static void main(String[] args) {
// 创建线程对象并启动
// 创建子类对象
ThreadStop t = new ThreadStop();
// start
t.start();
for (int i = 0; i < 3; i++) {
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// stop 强制中断线程
t.stop();
}
}
class ThreadStop extends Thread{
// run
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+"---"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
安全中断线程
主要通过flag标记去做
练习:
创建启动1个线程 要求打印10个数 每打印1个休眠1s
如果没有中断, 正常打印
如果此时发生了中断, 把中断线程保存到log.txt文件中, 格式 年月日 时分秒 哪个线程发生了中断
主线程打印3个数 每打印1个休眠1s 当打印完之后, 中断子线程 (通过更改flag值)
package _20thread02.com.cskaoyan._01api;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @description:
* @author: 景天
* @date: 2022/6/28 11:11
**/
/*
主要通过flag标记去做
练习:
创建启动1个线程 要求打印10个数 每打印1个休眠1s
如果没有中断, 正常打印
如果此时发生了中断, 把中断线程保存到log.txt文件中, 格式 年月日 时分秒 哪个线程发生了中断
主线程打印3个数 每打印1个休眠1s 当打印完之后, 中断子线程 (通过更改flag值)
*/
public class SecurityStop {
public static void main(String[] args) throws InterruptedException {
// 创建子类对象
ThreadStop2 t = new ThreadStop2();
// start
t.start();
// main打印
for (int i = 0; i < 3; i++) {
System.out.println(i);
Thread.sleep(1000);
}
// 中断子线程 更改flag
t.flag = false;
}
}
class ThreadStop2 extends Thread{
// 定义成员变量
boolean flag = true;
//run
@Override
public void run() {
for (int i = 0; i < 10; i++) {
// 判断一下flag状态
if (flag) {
// 如果为true 没有中断 正常打印
System.out.println(getName() + "---" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
// 如果为false 发生了中断, 把错误信息保存log.txt文件中
// 创建输出流对象
FileWriter fileWriter = null;
try {
fileWriter = new FileWriter("log.txt");
// 日期
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date = sdf.format(new Date());
// 把要保存的数据写入文件
fileWriter.write(date + getName() + "发生了中断!");
// flush
fileWriter.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
// close
if (fileWriter != null) {
try {
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
}
线程的生命周期
线程的几种状态
理论层面的状态
新建
- 刚new出来,还没有start
就绪
- 执行star