1.基本概念:程序,进程,线程
1.程序
程序(program) 是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
2.进程
进程(process) 是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期
- 如:运行中的QQ,运行中的MP3播放器
- 程序是静态的,进程是动态的
进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
3.线程
线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。
若一个进程同一时间并行执行多个线程,就是支持多线程的
- 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
- 一个进程中的多个线程共享相同的内存单元/内存地址空间它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。
4.单核CPU和多核CPU的理解
- 单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。例如:虽然有多车道,但是收费站只有一个工作人员在收费,只有收了费才能通过,那么CPU就好比收费人员。如果有某个人不想交钱,那么收费人员可以把他“挂起”(晾着他,等他想通了,准备好了钱,再去收费)。但是因为CPU时间单元特别短,因此感觉不出来。
- 如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)
- 一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
5.并行与并发
并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。6.多线程的优点
背景:以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方法),肯定比用多个线程来完成用的时间更短,为何仍需多线程呢?
多线程程序的优点:
1. 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
2. 提高计算机系统CPU的利用率
3. 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
7.需要使用多线程的场景
- 程序需要同时执行两个或多个任务。
- 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
-
2.线程的创建与使用
1.线程的创建与启动
Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread类来体现。
- Thread类的特性
- 每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体
- 通过该Thread对象的start()方法来启动这个线程,而非直接调用run()
2.API创建线程的两种方式
1.继承于Thread类
1.步骤
1.创建一个继承于Thread类的子类
2.重写Thread类的run()——>将此线程执行的操作声明在run()中;
3.创建Thread类的子类的对象;
4.通过过此对象调用start():①启动当前线程;②调用当前线程的run()
package Thread;
/**
* @Author 戴尔
* @Date 2021/12/21 9:16
* @Version 1.0
*/
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i%2==0){
System.out.println(Thread.currentThread().getName() +">>>" + i);
}
}
}
}
public class ThreadTest{
public static void main(String[] args) {
MyThread t1 = new MyThread("t1");
MyThread t2 = new MyThread("t2");
MyThread t3 = new MyThread("t3");
t1.start();
t2.start();
t3.start();
}
}
运行结果
Thread-0>>>0
Thread-1>>>0
Thread-2>>>0
Thread-1>>>2
Thread-0>>>2
Thread-1>>>4
Thread-2>>>2
Thread-1>>>6
Thread-0>>>4
Thread-1>>>8
Thread-2>>>4
Thread-1>>>10
Thread-0>>>6
Thread-1>>>12
Thread-2>>>6
Thread-1>>>14
Thread-0>>>8
Thread-1>>>16
Thread-2>>>8
Thread-1>>>18
Thread-0>>>10
Thread-2>>>10
Thread-0>>>12
Thread-2>>>12
Thread-0>>>14
Thread-2>>>14
Thread-0>>>16
Thread-2>>>16
Thread-0>>>18
Thread-2>>>18
2.使用注意
- 如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。
2. run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU调度决定。
3. 想要启动多线程,必须调用start方法。
4. 一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上的异常“IllegalThreadStateException”。3.另一种实现方法
若该线程仅使用一次,可使用创建Thread类的匿名子类创建线程new Thread(){
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
if (i%2==0){
System.out.println(Thread.currentThread().getName()+">>"+i);
}
}
}
}.start();
2.实现Runnable接口
1.实现步骤
1) 定义子类,实现Runnable接口。
2) 子类中重写Runnable接口中的run方法。
3) 通过Thread类含参构造器创建线程对象。
4) 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
5) 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。 ```java package Thread;
/**
- @Author 戴尔
- @Date 2021/12/25 17:29
- @Version 1.0 */
//1.创建一个实现了Runnable接口的类 class MThread implements Runnable{
//2.实现类去实现Runnable中的抽象方法:run()
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
if (i%2==0){
System.out.println(Thread.currentThread().getName()+">>"+i);
}
}
}
}
public class ThreadTest1 { public static void main(String[] args) { //3.创建实现类的对象 MThread m1=new MThread(); //4.将此对象作为参数传递到Thread类的构造器中,创建Thread的对象 Thread t1=new Thread(m1); t1.setName(“线程一”); //5.通过Thread类的对象调用start(); t1.start();
Thread t2=new Thread(m1);
t2.setName("主线程!");
t2.start();
System.out.println(Thread.currentThread().getName());
}
}
**执行结果**
```java
C:\Users\戴尔\.jdks\openjdk-15.0.1-1\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2020.1.1.1\lib\idea_rt.jar=58734:C:\Program Files\JetBrains\IntelliJ IDEA 2020.1.1.1\bin" -Dfile.encoding=UTF-8 -classpath C:\Users\戴尔\IdeaProjects\untitled\out\production\JavaSE Thread.ThreadTest1
main
线程一>>0
主线程!>>0
线程一>>2
线程一>>4
主线程!>>2
线程一>>6
线程一>>8
主线程!>>4
线程一>>10
主线程!>>6
线程一>>12
线程一>>14
线程一>>16
主线程!>>8
线程一>>18
主线程!>>10
线程一>>20
线程一>>22
线程一>>24
主线程!>>12
线程一>>26
线程一>>28
线程一>>30
主线程!>>14
线程一>>32
主线程!>>16
线程一>>34
线程一>>36
线程一>>38
主线程!>>18
线程一>>40
主线程!>>20
线程一>>42
线程一>>44
线程一>>46
主线程!>>22
线程一>>48
主线程!>>24
线程一>>50
主线程!>>26
线程一>>52
线程一>>54
线程一>>56
主线程!>>28
线程一>>58
主线程!>>30
线程一>>60
线程一>>62
主线程!>>32
线程一>>64
主线程!>>34
线程一>>66
线程一>>68
线程一>>70
线程一>>72
线程一>>74
主线程!>>36
线程一>>76
主线程!>>38
线程一>>78
主线程!>>40
线程一>>80
主线程!>>42
线程一>>82
线程一>>84
线程一>>86
主线程!>>44
线程一>>88
线程一>>90
线程一>>92
线程一>>94
线程一>>96
主线程!>>46
线程一>>98
主线程!>>48
主线程!>>50
主线程!>>52
主线程!>>54
主线程!>>56
主线程!>>58
主线程!>>60
主线程!>>62
主线程!>>64
主线程!>>66
主线程!>>68
主线程!>>70
主线程!>>72
主线程!>>74
主线程!>>76
主线程!>>78
主线程!>>80
主线程!>>82
主线程!>>84
主线程!>>86
主线程!>>88
主线程!>>90
主线程!>>92
主线程!>>94
主线程!>>96
主线程!>>98
Process finished with exit code 0
3.继承方式和实现方式的联系与区别
- 比较创建线程的两种方式。
- 开发中:优先选择:实现Runnable接口的方式
- 原因:
- 1.实现的方式没有类的单继承性的局限性
- 2.实现的方式更适合来处理多个线程有共享数据的情况。
- 联系: public class Thread implements Runnable
- 相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
4.线程的分类
Java中的线程分为两类:一种是守护线程,一种是用户线程。
它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
守护线程是用来服务用户线程的,通过在start()方法前调用
thread.setDaemon(true)可以把一个用户线程变成一个守护线程。
Java垃圾回收就是一个典型的守护线程。
若JVM中都是守护线程,当前JVM将退出。
形象理解:兔(用户线程)死狗(守护线程)烹,鸟尽弓藏
一个Java应用程序java.exe,其实至少有三个线程:main()主线程()(用户线程),gc()垃圾回收线程(守护线程),异常处理线程。当然如果发生异常,会影响主线程。
3.Thread相关方法
void start(): 启动线程,并执行对象的run()方法
run(): 线程在被调度时执行的操作
String getName(): 返回线程的名称
void setName(String name):设置该线程名称
public HelloThread(String name){
super(name);
}
HelloThread h1=new HelloThread(Thread:1)
static Thread currentThread(): 返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类
static void yield():线程让步 (释放当前CPU的执行权)
- 暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
- 若队列中没有同优先级的线程,忽略此方法
join() :当某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止
- 低优先级的线程也可以获得执行
在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。
yield:争球(本轮礼让后,再公平竞争);join:球权转换(强行插队)
static void sleep(long millis):(指定时间:毫秒)
- 令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。
- 抛出InterruptedException异常
stop(): 强制线程生命期结束,不推荐使用
boolean isAlive():返回boolean,判断线程是否还活着。
package Thread;
/**
* @Author 戴尔
* @Date 2021/12/21 11:15
* @Version 1.0
*/
class HelloThread extends Thread{
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
if (i%2==0){
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public HelloThread(String name){
super(name);
}
}
public class ThreadMethodTest {
public static void main(String[] args) {
HelloThread h1=new HelloThread("Thread:1");
h1.setName("线程一!");
h1.start();
Thread.currentThread().setName("主线程");
for (int i = 0; i <100 ; i++) {
if (i%2==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
if (i%20==0){
Thread.yield();
}
if (i==20){
try {
h1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
4.线程优先级的设置
1.线程的调度
1.调度策略
2.Java的调度方法
同优先级线程组成先进先出队列(先到先服务),使用时间片策略
对高优先级,使用优先调度的抢占式策略
2.线程的优先级
1.线程的优先等级
MAX_PRIORITY:10
MIN _PRIORITY:1
NORM_PRIORITY:5
2.涉及的方法
getPriority() :返回线程优先值
setPriority(int newPriority) :改变线程的优先级
3.说明
线程创建时继承父线程的优先级
低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用
package Thread;
/**
* @Author 戴尔
* @Date 2021/12/25 15:10
* @Version 1.0
*/
class ThreadPriorityTest extends Thread{
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
if (i%2==0){
System.out.println(Thread.currentThread().getName()+">>"+i);
}
}
}
}
public class ThreadPriority {
public static void main(String[] args) {
ThreadPriorityTest p1=new ThreadPriorityTest();
p1.setName("线程一");
p1.start();
p1.setPriority(Thread.MAX_PRIORITY);
for (int i = 0; i <100; i++) {
if (i%5==0){
Thread.currentThread().setName("主线程");
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
System.out.println(Thread.currentThread().getName()+">>"+i);
}
}
}
}
执行结果
C:\Users\戴尔\.jdks\openjdk-15.0.1-1\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2020.1.1.1\lib\idea_rt.jar=61331:C:\Program Files\JetBrains\IntelliJ IDEA 2020.1.1.1\bin" -Dfile.encoding=UTF-8 -classpath C:\Users\戴尔\IdeaProjects\untitled\out\production\JavaSE Thread.ThreadPriority
线程一>>0
线程一>>2
线程一>>4
线程一>>6
主线程>>0
线程一>>8
线程一>>10
线程一>>12
主线程>>5
线程一>>14
线程一>>16
线程一>>18
主线程>>10
线程一>>20
线程一>>22
线程一>>24
线程一>>26
线程一>>28
线程一>>30
主线程>>15
线程一>>32
线程一>>34
线程一>>36
主线程>>20
线程一>>38
线程一>>40
线程一>>42
线程一>>44
线程一>>46
线程一>>48
线程一>>50
线程一>>52
线程一>>54
线程一>>56
线程一>>58
线程一>>60
线程一>>62
线程一>>64
线程一>>66
线程一>>68
线程一>>70
线程一>>72
线程一>>74
线程一>>76
线程一>>78
主线程>>25
主线程>>30
线程一>>80
线程一>>82
主线程>>35
线程一>>84
主线程>>40
线程一>>86
主线程>>45
线程一>>88
主线程>>50
线程一>>90
主线程>>55
线程一>>92
主线程>>60
线程一>>94
主线程>>65
线程一>>96
主线程>>70
线程一>>98
主线程>>75
主线程>>80
主线程>>85
主线程>>90
主线程>>95
Process finished with exit code 0
5.多窗口卖票Demo——多线程的应用
1.继承于Thread类方式实现
package Thread;
/**
* @Author 戴尔
* @Date 2021/12/25 15:29
* @Version 1.0
*/
class Window extends Thread{
private static int ticket = 100;
@Override
public void run() {
while (true){
if (ticket>0){
System.out.println(getName()+":卖票,票号为:"+ticket);
ticket--;
}else{
break;
}
}
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
Window w1=new Window();
Window w2=new Window();
Window w3=new Window();
w1.setName("窗口一");
w2.setName("窗口二");
w3.setName("窗口三");
w1.start();
w2.start();
w3.start();
}
}
执行结果
C:\Users\戴尔\.jdks\openjdk-15.0.1-1\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2020.1.1.1\lib\idea_rt.jar=65300:C:\Program Files\JetBrains\IntelliJ IDEA 2020.1.1.1\bin" -Dfile.encoding=UTF-8 -classpath C:\Users\戴尔\IdeaProjects\untitled\out\production\JavaSE Thread.ThreadDemo2
窗口三:卖票,票号为:100
窗口二:卖票,票号为:100
窗口一:卖票,票号为:100
窗口二:卖票,票号为:98
窗口三:卖票,票号为:99
窗口二:卖票,票号为:96
窗口二:卖票,票号为:94
窗口一:卖票,票号为:97
窗口二:卖票,票号为:93
窗口三:卖票,票号为:95
窗口二:卖票,票号为:91
窗口一:卖票,票号为:92
窗口二:卖票,票号为:89
窗口三:卖票,票号为:90
窗口二:卖票,票号为:87
窗口一:卖票,票号为:88
窗口二:卖票,票号为:85
窗口二:卖票,票号为:83
窗口二:卖票,票号为:82
窗口三:卖票,票号为:86
窗口二:卖票,票号为:81
窗口一:卖票,票号为:84
窗口二:卖票,票号为:79
窗口三:卖票,票号为:80
窗口二:卖票,票号为:77
窗口一:卖票,票号为:78
窗口二:卖票,票号为:75
窗口三:卖票,票号为:76
窗口二:卖票,票号为:73
窗口一:卖票,票号为:74
窗口二:卖票,票号为:71
窗口三:卖票,票号为:72
窗口二:卖票,票号为:69
窗口一:卖票,票号为:70
窗口二:卖票,票号为:67
窗口三:卖票,票号为:68
窗口二:卖票,票号为:65
窗口二:卖票,票号为:63
窗口二:卖票,票号为:62
窗口一:卖票,票号为:66
窗口二:卖票,票号为:61
窗口三:卖票,票号为:64
窗口二:卖票,票号为:59
窗口一:卖票,票号为:60
窗口一:卖票,票号为:56
窗口一:卖票,票号为:55
窗口一:卖票,票号为:54
窗口一:卖票,票号为:53
窗口一:卖票,票号为:52
窗口一:卖票,票号为:51
窗口一:卖票,票号为:50
窗口一:卖票,票号为:49
窗口一:卖票,票号为:48
窗口一:卖票,票号为:47
窗口一:卖票,票号为:46
窗口二:卖票,票号为:57
窗口一:卖票,票号为:45
窗口一:卖票,票号为:43
窗口三:卖票,票号为:58
窗口一:卖票,票号为:42
窗口二:卖票,票号为:44
窗口一:卖票,票号为:40
窗口三:卖票,票号为:41
窗口一:卖票,票号为:38
窗口二:卖票,票号为:39
窗口一:卖票,票号为:36
窗口三:卖票,票号为:37
窗口一:卖票,票号为:34
窗口二:卖票,票号为:35
窗口一:卖票,票号为:32
窗口三:卖票,票号为:33
窗口一:卖票,票号为:30
窗口二:卖票,票号为:31
窗口二:卖票,票号为:27
窗口一:卖票,票号为:28
窗口三:卖票,票号为:29
窗口一:卖票,票号为:25
窗口二:卖票,票号为:26
窗口一:卖票,票号为:23
窗口三:卖票,票号为:24
窗口一:卖票,票号为:21
窗口二:卖票,票号为:22
窗口一:卖票,票号为:19
窗口三:卖票,票号为:20
窗口一:卖票,票号为:17
窗口二:卖票,票号为:18
窗口二:卖票,票号为:14
窗口一:卖票,票号为:15
窗口三:卖票,票号为:16
窗口一:卖票,票号为:12
窗口一:卖票,票号为:10
窗口二:卖票,票号为:13
窗口一:卖票,票号为:9
窗口一:卖票,票号为:7
窗口三:卖票,票号为:11
窗口一:卖票,票号为:6
窗口一:卖票,票号为:4
窗口一:卖票,票号为:3
窗口一:卖票,票号为:2
窗口一:卖票,票号为:1
窗口二:卖票,票号为:8
窗口三:卖票,票号为:5
Process finished with exit code 0
2.实现Runnable接口方式实现
package Thread;
/**
* @Author 戴尔
* @Date 2021/12/26 23:34
* @Version 1.0
*/
class window1 implements Runnable{
private int ticket=100;
@Override
public void run() {
while (true){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+":卖票:票号为:"+ticket);
ticket--;
}
else {
break;
}
}
}
}
public class ThreadDemo3 {
public static void main(String[] args) {
window1 w=new window1();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
执行结果
C:\Users\戴尔\.jdks\openjdk-15.0.1-1\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2020.1.1.1\lib\idea_rt.jar=52285:C:\Program Files\JetBrains\IntelliJ IDEA 2020.1.1.1\bin" -Dfile.encoding=UTF-8 -classpath C:\Users\戴尔\IdeaProjects\untitled\out\production\JavaSE Thread.ThreadDemo3
窗口2:卖票:票号为:100
窗口1:卖票:票号为:100
窗口1:卖票:票号为:98
窗口1:卖票:票号为:97
窗口3:卖票:票号为:100
窗口1:卖票:票号为:96
窗口1:卖票:票号为:94
窗口2:卖票:票号为:99
窗口1:卖票:票号为:93
窗口1:卖票:票号为:91
窗口3:卖票:票号为:95
窗口1:卖票:票号为:90
窗口2:卖票:票号为:92
窗口1:卖票:票号为:88
窗口1:卖票:票号为:86
窗口3:卖票:票号为:89
窗口1:卖票:票号为:85
窗口1:卖票:票号为:83
窗口2:卖票:票号为:87
窗口1:卖票:票号为:82
窗口1:卖票:票号为:80
窗口3:卖票:票号为:84
窗口1:卖票:票号为:79
窗口2:卖票:票号为:81
窗口2:卖票:票号为:76
窗口1:卖票:票号为:77
窗口3:卖票:票号为:78
窗口3:卖票:票号为:73
窗口1:卖票:票号为:74
窗口1:卖票:票号为:71
窗口2:卖票:票号为:75
窗口2:卖票:票号为:69
窗口1:卖票:票号为:70
窗口3:卖票:票号为:72
窗口1:卖票:票号为:67
窗口1:卖票:票号为:65
窗口2:卖票:票号为:68
窗口1:卖票:票号为:64
窗口3:卖票:票号为:66
窗口1:卖票:票号为:62
窗口2:卖票:票号为:63
窗口1:卖票:票号为:60
窗口1:卖票:票号为:58
窗口3:卖票:票号为:61
窗口1:卖票:票号为:57
窗口2:卖票:票号为:59
窗口1:卖票:票号为:55
窗口1:卖票:票号为:53
窗口3:卖票:票号为:56
窗口1:卖票:票号为:52
窗口2:卖票:票号为:54
窗口1:卖票:票号为:50
窗口3:卖票:票号为:51
窗口1:卖票:票号为:48
窗口2:卖票:票号为:49
窗口3:卖票:票号为:47
窗口1:卖票:票号为:46
窗口3:卖票:票号为:44
窗口2:卖票:票号为:45
窗口3:卖票:票号为:42
窗口1:卖票:票号为:43
窗口3:卖票:票号为:40
窗口2:卖票:票号为:41
窗口3:卖票:票号为:38
窗口1:卖票:票号为:39
窗口3:卖票:票号为:36
窗口2:卖票:票号为:37
窗口3:卖票:票号为:34
窗口1:卖票:票号为:35
窗口1:卖票:票号为:31
窗口3:卖票:票号为:32
窗口2:卖票:票号为:33
窗口3:卖票:票号为:29
窗口3:卖票:票号为:27
窗口3:卖票:票号为:26
窗口1:卖票:票号为:30
窗口3:卖票:票号为:25
窗口3:卖票:票号为:23
窗口2:卖票:票号为:28
窗口3:卖票:票号为:22
窗口1:卖票:票号为:24
窗口3:卖票:票号为:20
窗口2:卖票:票号为:21
窗口3:卖票:票号为:18
窗口1:卖票:票号为:19
窗口1:卖票:票号为:15
窗口1:卖票:票号为:14
窗口1:卖票:票号为:13
窗口3:卖票:票号为:16
窗口2:卖票:票号为:17
窗口3:卖票:票号为:11
窗口1:卖票:票号为:12
窗口3:卖票:票号为:9
窗口2:卖票:票号为:10
窗口3:卖票:票号为:7
窗口1:卖票:票号为:8
窗口3:卖票:票号为:5
窗口3:卖票:票号为:3
窗口2:卖票:票号为:6
窗口2:卖票:票号为:1
窗口3:卖票:票号为:2
窗口1:卖票:票号为:4
Process finished with exit code 0
3.线程的生命周期
1.线程的状态
JDK中用Thread.State类定义了线程的几种状态
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:
- 新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
- 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
- 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线程的操作和功能
- 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
- 死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
4.线程的同步
1.问题的提出
- 多个线程执行的不确定性引起执行结果的不稳定
- 多个线程对账本的共享,会造成操作的不完整性,会破坏数据。
2.例题引入
1.题目描述
2.例题代码
package Thread;
/**
* @Author 戴尔
* @Date 2021/12/26 23:34
* @Version 1.0
*/
class window1 implements Runnable{
private int ticket=100;
@Override
public void run() {
while (true){
if (ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":卖票:票号为:"+ticket);
ticket--;
}
else {
break;
}
}
}
}
public class ThreadDemo3 {
public static void main(String[] args) {
window1 w=new window1();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
执行结果
C:\Users\戴尔\.jdks\openjdk-15.0.1-1\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2020.1.1.1\lib\idea_rt.jar=62966:C:\Program Files\JetBrains\IntelliJ IDEA 2020.1.1.1\bin" -Dfile.encoding=UTF-8 -classpath C:\Users\戴尔\IdeaProjects\untitled\out\production\JavaSE Thread.ThreadDemo3
窗口2:卖票:票号为:100
窗口3:卖票:票号为:100
窗口1:卖票:票号为:100
窗口3:卖票:票号为:97
窗口2:卖票:票号为:97
窗口1:卖票:票号为:95
窗口3:卖票:票号为:94
窗口2:卖票:票号为:93
窗口1:卖票:票号为:92
窗口3:卖票:票号为:91
窗口2:卖票:票号为:91
窗口1:卖票:票号为:89
窗口3:卖票:票号为:88
窗口2:卖票:票号为:87
窗口1:卖票:票号为:86
窗口3:卖票:票号为:85
窗口2:卖票:票号为:84
窗口1:卖票:票号为:83
窗口3:卖票:票号为:82
窗口2:卖票:票号为:81
窗口1:卖票:票号为:80
窗口3:卖票:票号为:79
窗口2:卖票:票号为:78
窗口1:卖票:票号为:77
窗口3:卖票:票号为:76
窗口2:卖票:票号为:75
窗口1:卖票:票号为:74
窗口3:卖票:票号为:73
窗口2:卖票:票号为:72
窗口1:卖票:票号为:71
窗口3:卖票:票号为:70
窗口2:卖票:票号为:69
窗口1:卖票:票号为:68
窗口3:卖票:票号为:67
窗口2:卖票:票号为:66
窗口1:卖票:票号为:65
窗口3:卖票:票号为:64
窗口2:卖票:票号为:63
窗口1:卖票:票号为:62
窗口3:卖票:票号为:61
窗口2:卖票:票号为:60
窗口1:卖票:票号为:59
窗口3:卖票:票号为:58
窗口2:卖票:票号为:57
窗口1:卖票:票号为:56
窗口3:卖票:票号为:55
窗口2:卖票:票号为:54
窗口1:卖票:票号为:53
窗口3:卖票:票号为:52
窗口2:卖票:票号为:51
窗口1:卖票:票号为:50
窗口3:卖票:票号为:49
窗口2:卖票:票号为:48
窗口1:卖票:票号为:47
窗口3:卖票:票号为:46
窗口2:卖票:票号为:45
窗口1:卖票:票号为:44
窗口3:卖票:票号为:43
窗口2:卖票:票号为:42
窗口1:卖票:票号为:41
窗口3:卖票:票号为:40
窗口2:卖票:票号为:39
窗口1:卖票:票号为:38
窗口3:卖票:票号为:37
窗口2:卖票:票号为:36
窗口1:卖票:票号为:35
窗口3:卖票:票号为:34
窗口2:卖票:票号为:33
窗口1:卖票:票号为:32
窗口3:卖票:票号为:31
窗口2:卖票:票号为:30
窗口1:卖票:票号为:29
窗口3:卖票:票号为:28
窗口2:卖票:票号为:27
窗口1:卖票:票号为:26
窗口3:卖票:票号为:25
窗口2:卖票:票号为:24
窗口1:卖票:票号为:23
窗口3:卖票:票号为:22
窗口2:卖票:票号为:21
窗口1:卖票:票号为:20
窗口3:卖票:票号为:19
窗口2:卖票:票号为:18
窗口1:卖票:票号为:17
窗口3:卖票:票号为:16
窗口2:卖票:票号为:15
窗口1:卖票:票号为:14
窗口3:卖票:票号为:13
窗口2:卖票:票号为:12
窗口1:卖票:票号为:11
窗口3:卖票:票号为:10
窗口2:卖票:票号为:9
窗口1:卖票:票号为:8
窗口3:卖票:票号为:7
窗口2:卖票:票号为:6
窗口1:卖票:票号为:5
窗口3:卖票:票号为:4
窗口2:卖票:票号为:3
窗口1:卖票:票号为:2
窗口3:卖票:票号为:1
窗口2:卖票:票号为:0
窗口1:卖票:票号为:-1
Process finished with exit code 0
3.程序运行图解
4.出现的问题及解决措施
1.问题:卖票过程中,出现了重票、错票—>出现了线程的安全问题。
2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
3.如何解决:当一个线程a在操作ticket的时候, 其他线程不能参与进来。直到线程a操作完ticket时,其他线程才可以参与进来开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。
5.同步机制解决线程的安全问题
1.同步代码块
synchronized (对象){
// 需要被同步的代码;
}
说明:1.操作共享数据的代码,即为需要被同步的代码。——>不能包多了,也不能包少了。 2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。 3.同步监视器,俗称:锁(任何一个类的对象,都可以充当锁)。 要求:多个线程必须要共用同一把锁。 补充:在实现Runnable 接口创建多线程的方式中,我们可以考居使用this充当同步监视器。 在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。
package Thread;
/**
* @Author 戴尔
* @Date 2021/12/29 0:57
* @Version 1.0
*/
class window4 implements Runnable{
private int ticket=1000000;
Object obj=new Object();
@Override
public void run() {
while (true){
synchronized (obj) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票:票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class ThreadDemo4 {
public static void main(String[] args) {
window4 x=new window4();
Thread x1=new Thread(x);
Thread x2=new Thread(x);
Thread x3=new Thread(x);
x1.setName("窗口一");
x2.setName("窗口二");
x3.setName("窗口三");
x1.start();
x2.start();
x3.start();
}
}
package Thread;
/**
* @Author 戴尔
* @Date 2021/12/25 15:29
* @Version 1.0
*/
class Window1 extends Thread{
private static int ticket = 100;
private static Object obj=new Object();
@Override
public void run() {
while (true) {
// synchronized (obj) { right
// synchronized (this){ wrong t1,t2,t3三个对象三把锁
synchronized (Window1.class){//Class class=Window2.class
if (ticket > 0) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + ":卖票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class ThreadDemo5 {
public static void main(String[] args) {
Window1 w1=new Window1();
Window1 w2=new Window1();
Window1 w3=new Window1();
w1.setName("窗口一");
w2.setName("窗口二");
w3.setName("窗口三");
w1.start();
w2.start();
w3.start();
}
}
2.同步方法
public synchronized void show (String name){
......
}
synchronized还可以放在方法声明中,表示整个方法为同步方法。 关于同步方法的总结: 1.同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。 2.非静态的同步方法,同步监视器是: this 静态的同步方法,同步监视器是:当前类本身
package Thread;
/**
* @Author 戴尔
* @Date 2021/12/29 0:57
* @Version 1.0
*/
class Window2 implements Runnable{
private int ticket=100;
// Object obj=new Object();
@Override
public void run() {
while (true){
show();
if (ticket==0){
break;
}
}
}
private synchronized void show() {
// synchronized (this) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票:票号为:" + ticket);
ticket--;
}
}
}
public class ThreadDemo6{
public static void main(String[] args) {
Window2 x= new Window2();
Thread x1=new Thread(x);
Thread x2=new Thread(x);
Thread x3=new Thread(x);
x1.setName("窗口一");
x2.setName("窗口二");
x3.setName("窗口三");
x1.start();
x2.start();
x3.start();
}
}
package Thread;
/**
* @Author 戴尔
* @Date 2021/12/25 15:29
* @Version 1.0
*/
class Window3 extends Thread{
private static int ticket = 100;
@Override
public void run() {
while (true) {
show();
if (ticket==0){
break;
}
}
}
private static synchronized void show() {
// private synchronized void show() {
if (ticket > 0) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
}
}
}
public class ThreadDemo7 {
public static void main(String[] args) {
Window3 w1=new Window3();
Window3 w2=new Window3();
Window3 w3=new Window3();
w1.setName("窗口一");
w2.setName("窗口二");
w3.setName("窗口三");
w1.start();
w2.start();
w3.start();
}
}
3.同步的优缺点
- 同步的方式,解决了线程的安全问题。——好处
- 操作同步代码时,只能有一个线程参与, 其他线程等待。相当于是一个单线程的过程,效率低。——缺点
3.线程安全的单例模式之懒汉式
单例模式:懒汉式——> P437->P324 设计模式