操作系统基本概念(掌握)
例子:
程序不停地在屏幕上输出一句问候的语句(比如“你好”)
“同时”,当我通过键盘输入固定输入的时候,程序停止向屏幕输出问候的语句(比如说输入gun)
单线程
package com.cskaoyan._01introduction;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;
/**
* @description:
**/
/*
程序不停地在屏幕上输出一句问候的语句(比如“你好”)
“同时”,当我通过键盘输入固定输入的时候,程序停止向屏幕输出问候的语句(比如说输入gun)
*/
public class Demo {
public static boolean flag = true;
public static void main(String[] args) {
System.out.println("say hello before");
sayHelloRecycling();
System.out.println("say hello after");
System.out.println("wait stop before");
waitToStop();
System.out.println("wait stop after");
}
private static void waitToStop() {
Scanner scanner = new Scanner(System.in);
while (flag) {
String s = scanner.nextLine();
if ("gun".equals(s)) {
flag = false;
break;
}
}
}
private static void sayHelloRecycling() {
while (flag) {
System.out.println("在吗?");
// 暂停一下下
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
多线程
package com.cskaoyan._01introduction;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;
/**
* @description:
**/
/*
程序不停地在屏幕上输出一句问候的语句(比如“你好”)
“同时”,当我通过键盘输入固定输入的时候,程序停止向屏幕输出问候的语句(比如说输入gun)
*/
public class Demo2 {
public static boolean flag = true;
public static void main(String[] args) {
System.out.println("say hello before");
sayHelloRecycling();
System.out.println("say hello after");
System.out.println("wait stop before");
waitToStop();
System.out.println("wait stop after");
}
private static void waitToStop() {
new Thread(new Runnable() {
@Override
public void run() {
Scanner scanner = new Scanner(System.in);
while (flag) {
String s = scanner.nextLine();
if ("gun".equals(s)) {
flag = false;
break;
}
}
}
}).start();
}
private static void sayHelloRecycling() {
new Thread(new Runnable() {
@Override
public void run() {
while (flag) {
System.out.println("在吗?");
// 暂停一下下
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
进程
- 计算机程序在某个数据集合上的运行活动.进程是操作系统进行资源调度与分配的基本单位
- 简单理解: 正在运行的程序或者软件
线程
- 进程中可以有多个子任务,每1个子任务都是1个线程.从执行路劲的角度看,一个线程就是1个执行路径.线程是CPU进行资源调度与分配的基本单位
- 迅雷下载电影
进程与线程什么关系
- 线程依赖于进程而存在
- 1个进程可以有多个线程
- 线程共享进程的资源
串行
- 一个任务接一个任务的按顺序执行
并行
- 在同一个时间点,多个任务同时执行
并发
- 在同一个时间段内,多个任务同时执行
java程序运行原理(掌握)
java + 主类类名原理
- java命令会去创建1个jvm进程
- jvm进程会去创建1个线程,主线程 main
- 执行主线程中的main方法
jvm是单线程还是多线程的
jvm是多线程的
- 有1个main线程
- 还有1个垃圾回收线程
多线程的实现方式一:继承Thread类(重点)
线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程
文档示例
步骤
- 定义1个类,继承Thread类
- 重写run方法
- 创建子类对象
- 启动start()
package com.cskaoyan._02implone;
/**
* @description:
* @author: 景天
* @date: 2022/1/20 10:25
**/
/*
1. 定义1个类,继承Thread类
2. 重写run方法
3. 创建子类对象
4. 启动start()
*/
public class Demo1 {
public static void main(String[] args) {
// 3. 创建子类对象
MyThread myThread = new MyThread();
// 4. 启动start()
myThread.start();
}
}
//1. 定义1个类,继承Thread类
class MyThread extends Thread{
//2. 重写run方法
@Override
public void run() {
System.out.println("子线程执行了!!!");
}
}
注意事项
-
执行特点
-
随机性
-
package com.cskaoyan._02implone; /** * @description: **/ /* 1. 定义1个类,继承Thread类 2. 重写run方法 3. 创建子类对象 4. 启动start() */ public class Demo2 { public static void main(String[] args) { // 3. 创建子类对象 MyThread2 myThread = new MyThread2(); MyThread2 myThread2 = new MyThread2(); // 4. 启动start() myThread.start(); myThread2.start(); } } //1. 定义1个类,继承Thread类 class MyThread2 extends Thread{ //2. 重写run方法 @Override public void run() { for (int i = 0; i < 1000; i++) { System.out.println(i); } } }
-
-
run方法跟start方法有啥区别
-
直接调用run方法执行结果 before 0 1 2 3 4 5 6 7 8 9 after 使用start方法 执行结果 before after 0 1 2 3 4 5 6 7 8 9
-
run方法根本就没有开辟新的执行路径,还是按照顺序执行的.
-
直接调用run方法,相当于普通成员方法调用
-
start方法才是真正的去创建线程
-
只有run方法当中的代码才会执行在子线程中,我们要把我们的代码写到run方法中,并且启动的时候一定是start方法
-
run方法中掉用了别的方法
-
-
同1个线程,能否启动多次?
- 不可以 java.lang.IllegalThreadStateException 同一个线程 多次启动会报异常
-
谁才代表一个真正的线程?
- Thread类的对象及其子类的对象才代表1个线程
设置/获取线程名称
String | getName() 返回该线程的名称 |
---|---|
void | setName(String name) 改变线程名称,使之与参数 name 相同。 |
---|---|
Thread(String name) 分配新的 Thread 对象。 通过构造方法也可以设置线程名称 |
线程的默认的名字 Thread-编号
获取主线程名称
static Thread | currentThread() 返回对当前正在执行的线程对象的引用 |
---|---|
package com.cskaoyan._02implone;
/**
* @description: 设置获取线程名称
**/
public class Demo4 {
public static void main(String[] args) {
// 获取主线程名字
Thread thread = Thread.currentThread();
System.out.println("主线程名 = " + thread.getName());
// 创建子类对象
MyThread5 t1 = new MyThread5();
MyThread5 t2 = new MyThread5();
// 设置线程名称
// setName(String name)
t1.setName("王道彭于晏");
t2.setName("王道吴彦祖");
// start方法启动
t1.start();
t2.start();
}
}
// 继承Thread类
class MyThread5 extends Thread{
// run方法重写
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+"------"+i);
}
}
}
线程调度(掌握)
什么是线程调度?
系统给线程分配CPU资源的过程
分类
- 协同式线程调度方式
- 线程的执行时间由线程本身决定,线程完成了自己的工作,报告系统,切换到别的线程
- 抢占式线程调度方式
- 线程的执行时间不由线程决定,由系统决定.谁抢到了谁就执行
java中采用什么调度方式?
抢占式线程调度方式
线程优先级(了解)
操作系统线程优先级
- 静态优先级 固定值
- 动态优先级: 正在执行中的线程会随着执行时间的延长,优先级会降低.同时,正在等待的线程会随着等待时间的延长,优先级会升高
- 静态优先级 + 动态优先级
java中的线程优先级
static int | MAX_PRIORITY 线程可以具有的最高优先级。 10 |
---|---|
static int | MIN_PRIORITY 线程可以具有的最低优先级。 1 |
static int | NORM_PRIORITY 分配给线程的默认优先级。 5 |
设置获取线程优先级
int | getPriority() 返回线程的优先级。 |
---|---|
void | setPriority(int newPriority) 更改线程的优先级。 |
---|---|
package com.cskaoyan._02implone;
/**
* @description: 线程优先级
**/
public class Demo5 {
public static void main(String[] args) {
// 创建子类对象
MyThread6 t = new MyThread6();
// | static int | MAX_PRIORITY 线程可以具有的最高优先级。 10 |
//| static int | MIN_PRIORITY 线程可以具有的最低优先级。 1 |
//| static int | NORM_PRIORITY 分配给线程的默认优先级。 5 |
//setPriority(int pri)
t.setPriority(Thread.MAX_PRIORITY);
// 获取线程优先级
int priority = t.getPriority();
System.out.println("priority = " + priority);
// start方法启动
t.start();
}
}
class MyThread6 extends Thread{
// run
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}
java中的优先级到底有没有用?
练习:
需求:
创建2个线程,都是打印10个数
1个线程最高优先级 , 另一个线程最低优先级
启动
正常逻辑分析: 高优先级的先执行,低优先级的后执行.高优先级的10个数先打印,低优先级的再打印
结论:
执行结果跟我们的预期不符合
原因:
java中的优先级仅仅是1个静态优先级,仅仅是对操作系统的1个"建议"
操作系统中 : 动态 + 静态
java官方: 线程优先级并非完全没有用,我们Thread的优先级,它具有统计意义,总的来说,高优先级的线程
占用的cpu执行时间多一点,低优先级线程,占用cpu执行时间,短一点
package com.cskaoyan._02implone;
/**
* @description: java中的优先级有没有用
**/
/*
需求:
创建2个线程,都是打印10个数
1个线程最高优先级 , 另一个线程最低优先级
启动
正常逻辑分析: 高优先级的先执行,低优先级的后执行.高优先级的10个数先打印,低优先级的再打印
*/
public class Demo6 {
public static void main(String[] args) {
// 创建线程对象2个
MyThread7 t1 = new MyThread7();
MyThread7 t2 = new MyThread7();
t1.setName("牛x");
t2.setName("小垃圾");
// 设置优先级 1个10 1个1
t1.setPriority(10);
t2.setPriority(Thread.MIN_PRIORITY);
// start方法启动
t1.start();
t2.start();
}
}
// 定义类 继承Thread
class MyThread7 extends Thread{
// 重写run方法
@Override
public void run() {
// run方法里面打印数
for (int i = 0; i < 10; i++) {
System.out.println(getName()+"------"+i);
}
}
}
线程的控制API(掌握)
线程休眠sleep
static void | sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响 |
---|---|
package com.cskaoyan._03api;
import java.util.concurrent.TimeUnit;
/**
* @description: 线程休眠
**/
public class SleepDemo {
public static void main(String[] args) {
System.out.println("1");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("2");
// 创建子类对象
ThreadSleep threadSleep = new ThreadSleep();
// start
threadSleep.start();
}
}
class ThreadSleep extends Thread{
// run
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
try {
// 让程序阻塞(等待)
Thread.sleep(2000);
//TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程加入join
void | join() 等待该线程终止 |
---|---|
没有使用join之前 结果:
main start
main------0
main------1
main------2
main end
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
使用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
main------0
main------1
main------2
main end
等待谁?
等待的是子线程.等待的应该是调用join方法的那个线程
谁等待?
主线程在等待.执行join的这行代码在哪个线程上运行,就是那个线程等待
package com.cskaoyan._03api;
/**
* @description: 线程加入
**/
public class JoinDemo {
public static void main(String[] args) {
System.out.println("main start");
// 创建子类对象
ThreadJoin threadJoin = new ThreadJoin();
// start
threadJoin.start();
// join()
try {
threadJoin.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + "------" + i);
}
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);
}
}
}
线程礼让yield
static void | yield() 暂停当前正在执行的线程对象,并执行其他线程。 |
---|---|
练习:
创建2个线程对象,分别打印10个数
A线程打印1 , B线程打印1,A线程打印2 , B线程打印2…
结论:
并不能通过yield做到
原因:虽然当前线程放弃了CPU的执行权,但是仍然参与下轮的CPU竞争.谁抢到谁执行
package com.cskaoyan._03api;
/**
* @description: 线程礼让
**/
/*
创建2个线程对象,分别打印10个数
A线程打印1 , B线程打印1,A线程打印2 , B线程打印2......
*/
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 当中采用的是抢占的线程调度方式
}
}
}
守护线程daemon
void | setDaemon(boolean on) 将该线程标记为守护线程或用户线程。 |
---|---|
线程分类:
- 用户线程
- 守护线程
注意
- 当正在运行的线程都是守护线程时,Java 虚拟机退出。
- 该方法必须在启动线程前调用。(start) java.lang.IllegalThreadStateException(如果放到start之后会报异常)
package com.cskaoyan._03api;
/**
* @description: 守护线程
**/
public class DaemonDemo {
public static void main(String[] args) {
// 创建线程对象
ThreadDaemon t = new ThreadDaemon();
// setDaemon(boolean on)
// 将该线程标记为守护线程或用户线程。
// 将子线程设置为守护线程
// start
t.start();
t.setDaemon(true);
// main 中 打印3个数字 每打印1个 休眠1s
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + "----" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class ThreadDaemon extends Thread{
//run
@Override
public void run() {
for (int i = 0; i < 10; i++) {
// 每打印1个数休眠1s
System.out.println(getName() + " -------" + i);
// sleep
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程中断interrupt/stop
void | interrupt() 中断线程。如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,它还将收到一个 InterruptedException。 |
---|---|
stop() 该方法具有固有的不安全性 |
package com.cskaoyan._03api;
/**
* @description: 中断线程
**/
public class InterruptDemo {
public static void main(String[] args) {
// 创建子类对象
ThreadInterrupt t = new ThreadInterrupt();
// start方法启动
t.start();
// main 打印3个数
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + "----" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 执行中断操作 interrupt()
// 通过异常处理机制 会产生一个异常
//t.interrupt();
// stop 不安全
t.stop();
}
}
class ThreadInterrupt extends Thread{
// run
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+"-----"+i);
// 每打印1个 休眠1s
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 假设这是对数据库的操作
}
}
}
补充 安全终止线程
练习:
创建1个子线程,子线程里面要求打印10个数,每打印1个休眠一s
main线程中打印3个数,每打印1个休眠1s, 要求终止线程
使用boolean flag, 默认为true, 设置为false , 表示中断线程
此时 ,子线程中要求把中断信息保存到日志文件log.txt
格式: 2022-01-20 16:06:00 发生了中断
package com.cskaoyan._03api;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @description: 安全的终止线程
**/
/*
创建1个子线程,子线程里面要求打印10个数,每打印1个休眠一s
main线程中打印3个数,每打印1个休眠1s, 要求终止线程
使用boolean flag, 默认为true, 设置为false , 表示中断线程
此时 ,子线程中要求把中断信息保存到日志文件log.txt
格式: 2022-01-20 16:06:00 发生了中断
*/
public class StopSecurity {
public static void main(String[] args) {
// 创建线程对象
ThreadStop t = new ThreadStop();
// start
t.start();
// main线程中打印3个数,每打印1个休眠1s
for (int i = 0; i < 3; i++) {
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 中断线程 更改flag的值
t.flag = false;
}
}
class ThreadStop extends Thread{
// 定义成员变量
boolean flag = true;
//run
@Override
public void run() {
// 子线程里面要求打印10个数,每打印1个休眠一s
for (int i = 0; i < 10; i++) {
// 进行判断
// 如果为true 正常 打印
if (flag) {
System.out.println(getName() + "-----" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
// 如果为false 发生了中断 信息保存到文件当中
// 此时 ,子线程中要求把中断信息保存到日志文件log.txt
// 创建FIleWriter对象
FileWriter fileWriter = null;
try {
fileWriter = new FileWriter("log.txt");
//格式: 2022-01-20 16:06:00 发生了中断
// 指定格式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date = sdf.format(new Date());
// write
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();
}
}
}
}
}
}
}
线程的生命周期(重点)
线程的5种状态
新建
- 刚new出来的线程对象
就绪
- start方法启动后,等待CPU的执行权
执行
- 抢到了CPU的执行权
阻塞
- 除了没有CPU的执行权,还缺少一些必要的资源,sleep,join
死亡
- run方法执行完了
状态之间的转换
多线程实现方式二:实现Runnable接口(重点)
文档示例
步骤
- 实现Runnable接口
- 重写run方法
- 创建实现了Runnable接口的子类对象
- 创建线程对象,Thread对象, 并且把子类对象作为参数传递
- start方法启动
package com.cskaoyan._04impltwo;
/**
* @description: 多线程的实现方式二
**/
/*
1. 实现Runnable接口
2. 重写run方法
3. 创建实现了Runnable接口的子类对象
4. 创建线程对象,Thread对象, 并且把子类对象作为参数传递
5. start方法启动
*/
public class Demo {
public static void main(String[] args) {
//3. 创建实现了Runnable接口的子类对象
MyRunnable myRunnable = new MyRunnable();
//4. 创建线程对象,Thread对象, 并且把子类对象作为参数传递
Thread thread = new Thread(myRunnable);
//5. start方法启动
thread.start();
}
}
// 1. 实现Runnable接口
class MyRunnable implements Runnable{
//2. 重写run方法
@Override
public void run() {
System.out.println("子线程执行了!!");
}
}
为什么Runnable中的run方法会运行在子线程中?
class Thread {
// 定义成员变量
private Runnable target;
init(){
//...
// 左边的是成员变量 右边的是我们传过来的参数
this.target = target
}
void run(){
if(target != null){
target.run()
}
}
}
方式一 VS 方式二
- 步骤角度:方式一4步, 方式二5步
- 方式一是通过继承的方式,具有单继承的局限性
- 方式二是实现接口的方式,仍然能够继承
- 方式二把执行路径跟在执行路径上要做的事情区别开来(解耦)
- 方式二更加便于数据共享
多线程仿真如下场景:
假设A电影院正在上映某电影,该电影有100张电影票可供出售,现在假设有3个窗口售票。请设计程序模拟窗口售票的场景。
分析:
3个窗口售票,互不影响,同时进行。多线程 3个线程
3个窗口共同出售这100张电影票 3个线程共享这100张票
方式一:
package com.cskaoyan._05datasecurity;
/**
* @description:
**/
/*
分析:
3个窗口售票,互不影响,同时进行。多线程 3个线程
3个窗口共同出售这100张电影票 3个线程共享这100张票
*/
public class Demo {
public static void main(String[] args) {
// 创建3个线程
SellWindow t1 = new SellWindow();
SellWindow t2 = new SellWindow();
SellWindow t3 = new SellWindow();
t1.setName("A窗口");
t2.setName("B窗口");
t3.setName("C窗口");
// start
t1.start();
t2.start();
t3.start();
}
}
class SellWindow extends Thread{
// 成员变量
static int tickets = 100;
// run
@Override
public void run() {
while (true) {
// 模拟卖票
// 判断
if (tickets > 0) {
// 增加网络延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+"卖了第"+ (tickets--) + "票");
}
}
}
}
方式二:
package com.cskaoyan._05datasecurity;
/**
* @description:
**/
/*
分析:
3个窗口售票,互不影响,同时进行。多线程 3个线程
3个窗口共同出售这100张电影票 3个线程共享这100张票
*/
public class Demo2 {
public static void main(String[] args) {
// 创建子类对象
SellWindow2 sellWindow2 = new SellWindow2();
// 创建3个线程
Thread t1 = new Thread(sellWindow2);
Thread t2 = new Thread(sellWindow2);
Thread t3 = new Thread(sellWindow2);
t1.setName("A窗口");
t2.setName("B窗口");
t3.setName("C窗口");
// start
t1.start();
t2.start();
t3.start();
}
}
class SellWindow2 implements Runnable{
// 成员变量
int tickets = 100;
// run
@Override
public void run() {
while (true) {
// 模拟卖票
// 判断
// 分析:为什么出现重复的票
// 假设此时tickets = 100 ,此时A抢到了CPU的执行权
// 假设B抢到了CPU的执行权
if (tickets > 0) {
try {
// 此时A睡觉
// 此时B睡了
// 此时c睡了
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+
"卖了第"+ (tickets--) + "票");
// tickets-- 分3步
// 1.取值 2.-1 3. 重新赋值
// 假设A执行 取值100
// B执行 取值100
// c执行 取值100
}
}
}
}
出现2种情况:
- 不存在的票
- 重复的票
多线程数据安全问题出现的原因(掌握)
解决多线程数据安全问题(重点)
synchronized
同步代码块
同步方法
静态方法
lock
实现
synchronized VS lock
死锁(掌握)
什么是死锁
死锁的场景
如何解决死锁
方式一
方式二
生产者消费者模型(了解)
线程间通信(重点)
wait
notify
notifyAll
常见问题
sleep与wait方法有什么区别?