目录

1 多线程

1.1 并发与并⾏

1.2 线程与进程

1.3 创建线程类

2 多线程详解

2.1 多线程原理

2.2 Thread类

2.3 创建线程⽅式⼆

2.4 Thread 和 Runnable 的区别

2.5 匿名内部类⽅式实现线程的创建

3 线程安全

3.1 线程安全

3.2 线程同步

3.3 同步代码块

3.4 同步⽅法

3.5 Lock锁

4 线程状态

4.1 线程状态概述

4.2 Timed Waiting(计时等待)

4.3 BLOCKED(锁阻塞)

4.4 Waiting(⽆限等待)

4.5 练习

5 小结


1 多线程


我们在之前,学习的程序在没有跳转语句的前提下,都是由上⾄下依次执⾏,那现在想要设计⼀个程序, 边打游戏边听歌,怎么设计?
要解决上述问题,咱们得使⽤多进程或者多线程来解决。

1.1 并发与并⾏

  • 并发:指两个或多个事件在同⼀个时间段内发⽣。
  • 并⾏:指两个或多个事件在同⼀时刻发⽣(同时发⽣)。

在操作系统中,安装了多个程序,并发指的是在⼀段时间内宏观上有多个程序同时运⾏,这在单 CPU 系统中,每⼀时刻只能有⼀道程序执⾏,即微观上这些程序是分时的交替运⾏,只不过是给⼈的感觉是同时运 ⾏,那是因为分时交替运⾏的时间是⾮常短的。
⽽在多个 CPU 系统中,则这些可以并发执⾏的程序便可以分配到多个处理器上( CPU ),实现多任务并⾏ 执⾏,即利⽤每个处理器来处理⼀个可以并发执⾏的程序,这样多个程序便可以同时执⾏。⽬前电脑市场 上说的多核 CPU ,便是多核处理器,核 越多,并⾏处理的程序越多,能⼤⼤的提⾼电脑运⾏的效率。
注意:单核处理器的计算机肯定是不能并⾏的处理多个任务的,只能是多个任务在单个 CPU 上并发运 ⾏。同理 , 线程也是⼀样的,从宏观⻆度上理解线程是并⾏运⾏的,但是从微观⻆度上分析却是串⾏ 运⾏的,即⼀个线程⼀个线程的去运⾏,当系统只有⼀个 CPU 时,线程会以某种顺序执⾏多个线程, 我们把这种情况称之为线程调度。

1.2 线程与进程

  • 进程:是指⼀个内存中运⾏的应⽤程序,每个进程都有⼀个独⽴的内存空间,⼀个应⽤程序可以同时运⾏多个进程;进程也是程序的⼀次执⾏过程,是系统运⾏程序的基本单位;系统运⾏⼀个程序即是 ⼀个进程从创建、运⾏到消亡的过程。
  • 线程:线程是进程中的⼀个执⾏单元,负责当前进程中程序的执⾏,⼀个进程中⾄少有⼀个线程。⼀个进程中是可以有多个线程的,这个应⽤程序也可以称之为多线程程序。
简⽽⾔之:⼀个程序运⾏后⾄少有⼀个进程,⼀个进程中可以包含多个线程
我们可以再电脑底部任务栏,右键 --> 打开任务管理器,可以查看当前任务的进程:
进程

线程调度:
  • 分时调度

所有线程轮流使⽤ CPU 的使⽤权,平均分配每个线程占⽤ CPU 的时间。

  • 抢占式调度

优先让优先级⾼的线程使⽤ CPU,如果线程的优先级相同,那么会随机选择⼀个(线程随机性), Java使⽤的为抢占式调度。

  1. 设置线程的优先级

2.抢占式调度详解

        ⼤部分操作系统都⽀持多进程并发运⾏,现在的操作系统⼏乎都⽀持同时运⾏多个程序。⽐如: 现在我们上课⼀边使⽤编辑器,⼀边使⽤录屏软件,同时还开着画图板, dos 窗⼝等软件。此
时,这些程序是在同时运⾏, “ 感觉这些软件好像在同⼀时刻运⾏着 ” 。
        实际上,CPU (中央处理器)使⽤抢占式调度模式在多个线程间进⾏着⾼速的切换。对于 CPU 的 ⼀个核⽽⾔,某个时刻,只能执⾏⼀个线程,⽽ CPU 的在多个线程间切换速度相对我们的感觉 要快,看上去就是在同⼀时刻运⾏。
        其实,多线程程序并不能提⾼程序的运⾏速度,但能够提⾼程序运⾏效率,让 CPU 的使⽤率更 ⾼。

1.3 创建线程类

Java 使⽤ java.lang.Thread 类代表 线程 ,所有的线程对象都必须是 Thread 类或其⼦类的实例。每个线程的作⽤是完成⼀定的任务,实际上就是执⾏⼀段程序流即⼀段顺序执⾏的代码。 Java 使⽤线程执⾏体来 代表这段程序流。 Java 中通过继承 Thread 类来 创建启动多线程 的步骤如下:
1. 定义 Thread 类的⼦类,并重写该类的 run() ⽅法,该 run() ⽅法的⽅法体就代表了线程需要完成的任务,因此把 run() ⽅法称为线程执⾏体。
2. 创建 Thread ⼦类的实例,即创建了线程对象
3. 调⽤线程对象的 start() ⽅法来启动该线程
代码如下:
测试类:
public class Demo01 {public static void main(String[] args) {// 创建⾃定义线程对象MyThread mt = new MyThread("新的线程!");// 开启新线程mt.start();// 在主⽅法中执⾏for循环for (int i = 0; i < 10; i++) {System.out.println("main线程!" + i);}}
}
⾃定义线程类:
public class MyThread extends Thread {// 定义指定线程名称的构造⽅法public MyThread(String name) {// 调⽤⽗类的String参数的构造⽅法,指定线程的名称super(name);}/*** 重写run⽅法,完成该线程执⾏的逻辑*/@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(getName() + ":正在执⾏!" + i);}}
}

2 多线程详解


2.1 多线程原理

刚刚我们已经写过⼀版多线程的代码,很多同学对原理不是很清楚,那么现在我们先画个多线程执⾏时序 图来体现⼀下多线程程序的执⾏流程。 代码如下:
⾃定义线程类:
public class MyThread extends Thread {/** 利⽤继承中的特点* 将线程名称传递 进⾏设置*/public MyThread(String name) {super(name);}/** 重写run⽅法* 定义线程要执⾏的代码*/public void run() {for (int i = 0; i < 20; i++) {//getName()⽅法 来⾃⽗亲System.out.println(getName() + i);}}
}
测试类:
public class Demo {public static void main(String[] args) {System.out.println("这⾥是main线程");MyThread mt = new MyThread("⼩强");mt.start(); // 开启了⼀个新的线程for (int i = 0; i < 20; i++) {System.out.println("旺财:" + i);}}
}
流程图:
程序启动运⾏ main 时候, java 虚拟机启动⼀个进程,主线程 main 在 main() 调⽤时候被创建。随着调⽤mt 的对象的 start ⽅法,另外⼀个新的线程也启动了,这样,整个应⽤就在多线程下运⾏。
通过这张图我们可以很清晰的看到多线程的执⾏流程,那么为什么可以完成并发执⾏呢?我们再来讲⼀讲原理。
多线程执⾏时,到底在内存中是如何运⾏的呢?以上个程序为例,进⾏图解说明:
多线程执⾏时,在栈内存中,其实 每⼀个执⾏线程都有⼀⽚⾃⼰所属的栈内存空间。 进⾏⽅法的压栈和弹栈。
当执⾏线程的任务结束了,线程⾃动在栈内存中释放了。但是当 所有的执⾏线程 都结束了(“前置”线程结束了),进程才结束了。

2.2 Thread

在上⼀章内容中我们已经可以完成最基本的线程开启,那么在我们完成操作过程中⽤到了 java.lang.Thread 类, API 中该类中定义了有关线程的⼀些⽅法,具体如下:
构造⽅法:
  • public Thread() :分配⼀个新的线程对象。
  • public Thread(String name) :分配⼀个指定名字的新的线程对象。
  • public Thread(Runnable target) :分配⼀个带有指定⽬标新的线程对象。
  • public Thread(Runnable target, String name) :分配⼀个带有指定⽬标新的线程对象并指定名字。
常⽤⽅法:
  • public String getName() :获取当前线程名称。
  • public void start() :导致此线程开始执⾏;Java虚拟机调⽤此线程的run⽅法。
  • public void run() :此线程要执⾏的任务在此处定义代码。
  • public static void sleep(long millis) :使当前正在执⾏的线程以指定的毫秒数暂停(暂时停⽌执⾏)。
  • public static Thread currentThread() :返回对当前正在执⾏的线程对象的引⽤。
翻阅 API 后得知创建线程的⽅式总共有两种,⼀种是继承 Thread 类⽅式,⼀种是实现 Runnable 接⼝⽅式,⽅式⼀我们上⼀章已经完成,接下来讲解⽅式⼆实现的⽅式。

2.3 创建线程⽅式⼆

采⽤ java.lang.Runnable 也是⾮常常⻅的⼀种,我们只需要重写 run ⽅法即可。
步骤如下:
1. 定义 Runnable 接⼝的实现类,并重写该接⼝的 run() ⽅法,该 run() ⽅法的⽅法体同样是该线程的线程执⾏体。
2. 创建 Runnable 实现类的实例,并以此实例作为 Thread 的 target 来创建 Thread 对象,该 Thread 对象才是 真正的线程对象。
3. 调⽤线程对象的 start() ⽅法来启动线程。
代码如下:
public class MyRunnable implements Runnable {@Overridepublic void run() {for (int i = 0; i < 20; i++) {System.out.println(Thread.currentThread().getName() + " " + i);}}
}
public class Demo {public static void main(String[] args) {// 创建⾃定义类对象 线程任务对象MyRunnable mr = new MyRunnable();// 创建线程对象Thread t = new Thread(mr, "⼩强");t.start();for (int i = 0; i < 20; i++) {System.out.println("旺财 " + i);}}
}
通过实现 Runnable 接⼝,使得该类有了多线程类的特征。 run() ⽅法是多线程程序的⼀个执⾏⽬标。所有的多线程代码都在 run ⽅法⾥⾯。 Thread 类实际上也是实现了 Runnable 接⼝的类。
在启动的多线程的时候,需要先通过 Thread 类的构造⽅法 Thread(Runnable target) 构造出对象,然后调⽤Thread 对象的 start() ⽅法来运⾏多线程代码。
实际上所有的多线程代码都是通过运⾏ Thread 的 start() ⽅法来运⾏的。因此,不管是继承 Thread 类还是实现 Runnable 接⼝来实现多线程,最终还是通过 Thread 的对象的 API 来控制线程的,熟悉 Thread 类的 API 是进⾏多线程编程的基础。
Tips : Runnable 对象仅仅作为 Thread 对象的 target , Runnable 实现类⾥包含的 run() ⽅法仅作为线程 执⾏体。⽽实际的线程对象依然是 Thread 实例,只是该 Thread 线程负责执⾏其 target 的 run() ⽅法。

2.4 Thread Runnable 的区别

如果⼀个类继承 Thread ,则不适合资源共享。但是如果实现了 Runable 接⼝的话,则很容易的实现资源共享。
总结:
实现 Runnable 接⼝⽐继承 Thread 类所具有的优势:
1. 适合多个相同的程序代码的线程去共享同⼀个资源。
2. 可以避免 java 中的单继承的局限性。
3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独⽴。
4. 线程池只能放⼊实现 Runable 或 Callable 类线程,不能直接放⼊继承 Thread 的类。
扩充:在 java 中,每次程序运⾏⾄少启动 2 个线程。⼀个是 main 线程,⼀个是垃圾收集(GC)线程。因为 每当使⽤ java 命令执⾏⼀个类的时候,实际上都会启动⼀个 JVM ,每⼀个 JVM 其实在就是在操作系 统中启动了⼀个进程。

2.5 匿名内部类⽅式实现线程的创建

使⽤线程的匿名内部类⽅式,可以⽅便的实现每个线程执⾏不同的线程任务操作。
使⽤匿名内部类的⽅式实现 Runnable 接⼝,重写 Runnable 接⼝中的 run ⽅法:
public class NoNameInnerClassThread {public static void main(String[] args) {
// new Runnable() {
// public void run() {
// for (int i = 0; i < 20; i++) {
// System.out.println("张宇:" + i);
// }
// }
// }; //---这个整体 相当于new MyRunnable()Runnable r = new Runnable() {public void run() {for (int i = 0; i < 20; i++) {System.out.println("张宇:" + i);}}};new Thread(r).start();for (int i = 0; i < 20; i++) {System.out.println("费⽟清:" + i);}}
}

3 线程安全


3.1 线程安全

如果有多个线程在同时运⾏,⽽这些线程可能会同时运⾏这段代码。程序每次运⾏结果和单线程运⾏的结果是⼀样的,⽽且其他的变量的值也和预期的是⼀样的,就是线程安全的。
我们通过⼀个案例,演示线程的安全问题:
电影院要卖票,我们模拟电影院的卖票过程。假设要播放的电影是 “ 葫芦娃⼤战奥特曼 ” ,本次电影的座位 共 100 个(本场电影只能卖 100 张票)。
我们来模拟电影院的售票窗⼝,实现多个窗⼝同时卖 “ 葫芦娃⼤战奥特曼 ” 这场电影票(多个窗⼝⼀起卖这100 张票)
需要窗⼝,采⽤线程对象来模拟;需要票, Runnable 接⼝⼦类来模拟
模拟票:
public class Ticket implements Runnable {private int ticket = 100;/** 执⾏卖票操作*/@Overridepublic void run() {// 每个窗⼝卖票的操作// 窗⼝ 永远开启while (true) {if (ticket > 0) {// 有票 可以卖// 出票操作// 使⽤sleep模拟⼀下出票时间try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}// 获取当前线程对象的名字String name = Thread.currentThread().getName();System.out.println(name + "正在卖:" + ticket--);}}}
}
测试类:

public class Demo {public static void main(String[] args) {// 创建线程任务对象Ticket ticket = new Ticket();// 创建三个窗⼝对象Thread t1 = new Thread(ticket, "窗⼝1");Thread t2 = new Thread(ticket, "窗⼝2");Thread t3 = new Thread(ticket, "窗⼝3");// 同时卖票t1.start();t2.start();t3.start();}
}

结果中有⼀部分这样现象:

发现程序出现了两个问题:
1. 相同的票数,⽐如 2 这张票被卖了两回。
2. 不存在的票,⽐如 0 票与 -1 票,是不存在的。
这种问题,⼏个窗⼝(线程)票数不同步了,这种问题称为线程安全性问题。
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,⽽⽆写操作,⼀般来说,这个全局变量是线程安全的;若有多个线程同时执⾏写操作,⼀般都需要考 虑线程同步,否则的话就可能影响线程安全。

3.2 线程同步

当我们使⽤多个线程访问同⼀资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。
要解决上述多线程并发访问⼀个资源的安全性问题:也就是解决重复票与不存在票问题, Java 中提供了同 步机制 synchronized 来解决。
根据案例简述:
窗⼝1线程进⼊操作的时候,窗⼝2和窗⼝3线程只能在外等着,窗⼝1操作结束,窗⼝1和窗⼝2和窗⼝3才有机会进⼊代码去执⾏。也就是说在某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢夺CPU资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象。
为了保证每个线程都能正常执⾏原⼦操作, Java 引⼊了线程同步机制。
那么怎么去使⽤呢?有三种⽅式完成同步操作:
1. 同步代码块。
2. 同步⽅法。
3. 锁机制。

3.3 同步代码块

  • 同步代码块: synchronized 关键字可以⽤于⽅法中的某个区块中,表示只对这个区块的资源实⾏互斥访问。
格式:
synchronized(同步锁) {需要同步操作的代码
}
同步锁:
对象的同步锁只是⼀个概念,可以想象为在对象上标记了⼀个锁。
1. 锁对象,可以是任意类型。
2. 多个线程对象,要使⽤同⼀把锁。
注意:在任何时候,最多允许⼀个线程拥有同步锁,谁拿到锁就进⼊代码块,其他的线程只能在外等 着( BLOCKED )。
使⽤同步代码块解决代码:
public class Ticket implements Runnable {private int ticket = 100;Object lock = new Object();/** 执⾏卖票操作*/@Overridepublic void run() {// 每个窗⼝卖票的操作// 窗⼝ 永远开启while (true) {synchronized (lock) {if (ticket > 0) { // 有票 可以卖// 出票操作// 使⽤sleep模拟⼀下出票时间try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}// 获取当前线程对象的名字String name = Thread.currentThread().getName();System.out.println(name + "正在卖: " + ticket--);}}}}
}
当使⽤了同步代码块后,上述的线程的安全问题,解决了。

3.4 同步⽅法

  • 同步⽅法:使⽤ synchronized 修饰的⽅法,就叫做同步⽅法,保证A线程执⾏该⽅法的时候,其他线程只能在⽅法外等着。
格式:
public synchronized void method() {可能会产⽣线程安全问题的代码
}
同步锁是谁?
对于⾮ static ⽅法,同步锁就是 this 。
对于 static ⽅法,我们使⽤当前⽅法所在类的字节码对象(类名 .class )。

使⽤同步⽅法代码如下:

public class Ticket implements Runnable {private int ticket = 100;/** 执⾏卖票操作*/@Overridepublic void run() {// 每个窗⼝卖票的操作// 窗⼝ 永远开启while (true) {sellTicket();}}/** 锁对象 是 谁调⽤这个⽅法 就是谁* 隐含 锁对象 就是 this*/public synchronized void sellTicket() {if (ticket > 0) { // 有票 可以卖// 出票操作// 使⽤sleep模拟⼀下出票时间
try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}// 获取当前线程对象的名字String name = Thread.currentThread().getName();System.out.println(name + "正在卖:" + ticket--);}}
}

3.5 Lock

java.util.concurrent.locks.Lock 机制提供了⽐ synchronized 代码块和 synchronized ⽅法更⼴泛的
锁定操作,同步代码块 / 同步⽅法具有的功能 Lock 都有,除此之外更强⼤,更体现⾯向对象。
Lock 锁也称同步锁,创建对象 Lock lock = new ReentrantLock() ,加锁与释放锁⽅法如下:
  • public void lock() :加同步锁。
  • public void unlock() :释放同步锁。
使⽤如下:
public class Ticket implements Runnable {private int ticket = 100;Lock lock = new ReentrantLock();/** 执⾏卖票操作*/@Overridepublic void run() {// 每个窗⼝卖票的操作// 窗⼝ 永远开启while (true) {lock.lock();if (ticket > 0) { // 有票 可以卖// 出票操作// 使⽤sleep模拟⼀下出票时间try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}// 获取当前线程对象的名字String name = Thread.currentThread().getName();
System.out.println(name + "正在卖:" + ticket--);}lock.unlock();}}
}

4 线程状态


4.1 线程状态概述

当线程被创建并启动以后,它既不是⼀启动就进⼊了执⾏状态,也不是⼀直处于执⾏状态。在线程的⽣命 周期中,有⼏种状态呢?在 API 中 java.lang.Thread.State 这个枚举中给出了六种线程状态:
这⾥先列出各个线程状态发⽣的条件,下⾯将会对每种状态进⾏详细解析
我们不需要去研究这⼏种状态的实现原理,我们只需知道在做线程操作中存在这样的状态。那我们怎么去理解这⼏个状态呢,新建与被终⽌还是很容易理解的,我们就研究⼀下线程从 Runnable (可运⾏)状态与 ⾮运⾏状态之间的转换问题。

4.2 Timed Waiting(计时等待)

Timed Waiting 在 API 中的描述为:⼀个正在限时等待另⼀个线程执⾏⼀个(唤醒)动作的线程处于这⼀状态。单独的去理解这句话,真是⽞之⼜⽞,其实我们在之前的操作中已经接触过这个状态了,在哪⾥呢?
在我们写卖票的案例中,为了减少线程执⾏太快,现象不明显等问题,我们在 run ⽅法中添加了 sleep 语句,这样就强制当前正在执⾏的线程休眠 (暂停执⾏) ,以 “ 减慢线程 ” 。
其实当我们调⽤了 sleep ⽅法之后,当前执⾏的线程就进⼊到 “ 休眠状态 ” ,其实就是所谓的 Timed Waiting(计时等待),那么我们通过⼀个案例加深对该状态的⼀个理解。
实现⼀个计数器,计数到 100 ,在每个数字之间暂停 1 秒,每隔 10 个数字输出⼀个字符串
代码:
public class MyThread extends Thread {public void run() {for (int i = 0; i < 100; i++) {if ((i) % 10 == 0) {System.out.println("-------" + i);}System.out.print(i);try {Thread.sleep(1000);System.out.print(" 线程睡眠1秒!\n");} catch (InterruptedException e) {e.printStackTrace(); }}}public static void main(String[] args) {new MyThread().start();}
}
通过案例可以发现, sleep ⽅法的使⽤还是很简单的。我们需要记住下⾯⼏点:
1. 进⼊ TIMED_WAITING 状态的⼀种常⻅情形是调⽤的 sleep ⽅法,单独的线程也可以调⽤,不⼀定⾮要有协作关系。
2. 为了让其他线程有机会执⾏,可以将 Thread.sleep() 的调⽤ 放线程 run() 之内 。这样才能保证该线程执⾏过程中会睡眠。
3. sleep 与锁⽆关,线程睡眠到期⾃动苏醒,并返回到 Runnable (可运⾏)状态。
⼩提示: sleep() 中指定的时间是线程不会运⾏的最短时间。因此, sleep() ⽅法不能保证该线程睡眠到期后就开始⽴刻执⾏。

Timed Waiting 线程状态图:

4.3 BLOCKED(锁阻塞)

Blocked 状态在 API 中的介绍为:⼀个正在阻塞等待⼀个监视器锁(锁对象)的线程处于这⼀状态。
我们已经学完同步机制,那么这个状态是⾮常好理解的了。⽐如,线程 A 与线程 B 代码中使⽤同⼀锁,如果 线程 A 获取到锁,线程 A 进⼊到 Runnable 状态,那么线程 B 就进⼊到 Blocked 锁阻塞状态。
这是由 Runnable 状态进⼊ Blocked 状态。除此 Waiting 以及 Time Waiting 状态也会在某种情况下进⼊阻塞状态,⽽这部分内容作为扩充知识点带领⼤家了解⼀下。
Blocked 线程状态图:

4.4 Waiting(⽆限等待)

Wating 状态在 API 中介绍为:⼀个正在⽆限期等待另⼀个线程执⾏⼀个特别的(唤醒)动作的线程处于这⼀状态。
那么我们之前遇到过这种状态吗?答案是并没有,但并不妨碍我们进⾏⼀个简单深⼊的了解。我们通过⼀段代码来学习⼀下:
public class WaitingTest {public static Object obj = new Object();public static void main(String[] args) {// 演示waitingnew Thread(new Runnable() {@Overridepublic void run() {while (true) {synchronized (obj) {try {System.out.println(Thread.currentThread().getName() + "=== 获取到锁对
象,调⽤wait⽅法,进⼊waiting状态,释放锁对象");obj.wait(); // ⽆限等待// obj.wait(5000); // 计时等待, 5秒 时间到,⾃动醒来} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "=== 从waiting状态
醒来,获取到锁对象,继续执⾏了");}}}}, "等待线程").start();new Thread(new Runnable() {@Overridepublic void run() {
// while (true) { // 每隔3秒 唤醒⼀次try {System.out.println(Thread.currentThread().getName() + "----- 等待3秒 钟");Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (obj) {System.out.println(Thread.currentThread().getName() + "----- 获取到锁对
象,调⽤notify⽅法,释放锁对象");obj.notify();}}
// }}, "唤醒线程").start();}
}
通过上述案例我们会发现,⼀个调⽤了某个对象的 Object.wait ⽅法的线程会等待另⼀个线程调⽤此对象的Object.notify() ⽅法 或 Object.notifyAll() ⽅法。
其实 waiting 状态并不是⼀个线程的操作,它体现的是多个线程间的通信,可以理解为多个线程之间的协作关系,多个线程会争取锁,同时相互之间⼜存在协作关系。就好⽐在公司⾥你和你的同事们,你们可能 存在晋升时的竞争,但更多时候你们更多是⼀起合作以完成某些任务。
当多个线程协作时,⽐如 A , B 线程,如果 A 线程在 Runnable (可运⾏)状态中调⽤了 wait() ⽅法那么 A 线程 就进⼊了 Waiting (⽆限等待)状态,同时失去了同步锁。假如这个时候 B 线程获取到了同步锁,在运⾏状 态中调⽤了 notify() ⽅法,那么就会将⽆限等待的 A 线程唤醒。注意是唤醒,如果获取到锁对象,那么 A 线 程唤醒后就进⼊ Runnable (可运⾏)状态;如果没有获取锁对象,那么就进⼊到 Blocked (锁阻塞状 态)。
Waiting 线程状态图:
⼀条有意思的 Tips :
我们在翻阅 API 的时候会发现 Timed Waiting (计时等待)与 Waiting (⽆限等待)状态联系还是很紧密的,⽐如 Waiting (⽆限等待)状态中 wait ⽅法是空参的,⽽ Timed Waiting (计时等待)中 wait ⽅ 法是带参的。这种带参的⽅法,其实是⼀种倒计时操作,相当于我们⽣活中的⼩闹钟,我们设定好时 间,到时通知,可是如果提前得到(唤醒)通知,那么设定好时间再通知也就显得多此⼀举了,那么 这种设计⽅案其实是⼀举两得。如果没有得到(唤醒)通知,那么线程就处于 Timed Waiting 状态, 直到倒计时完毕⾃动醒来;如果在倒计时期间得到(唤醒)通知,那么线程从 Timed Waiting 状态⽴
刻唤醒。

4.5 练习

线程通信:练习1

需求:两个线程

线程1:图片的加载  1%~100%

线程2:图片的显示  显示

同时开启线程

线程2 等待 线程1 结束后在执行,

在线程2中使用 线程1.join() -> 线程2 进入阻塞状态

采用匿名内部类的方式

public class ThreadDemo3 {public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(){public void run(){System.out.println("开始加载...");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}for (int i = 1; i <= 100; i++) {System.out.println("已加载" + i + "%");try {Thread.sleep((long) (Math.random() * 1000));} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("结束加载");}};Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {try {t1.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("等待加载中。。。");System.out.println("显示图片");}});t1.start();t2.start();}
}

线程通信:练习2

需求: 两个线程
       线程1: 图片的加载   1% ~ 100% (等待图片显示)  图片的下载 1%~100%
       线程2: 图片的显示
       同时开启线程: 显示之前 需要等待 加载完成
                   显示后 才能开始下载

wait(long): 等待指定的时间毫秒值
wait(): 无限等待, 可以被唤醒 notify()  notifyAll()

public class TreadDemo4 {public static void main(String[] args) {Object obj = new Object();//这是用来做加锁的工具的, 此案例中什么意义都没有LoadThread load = new LoadThread(obj);ShowThread showT = new ShowThread(obj);Thread show = new Thread(showT);load.start();show.start();}
}class LoadThread extends Thread {private Object obj;public LoadThread(Object obj) {this.obj = obj;}public void run() {System.out.println("开始加载...");try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}for (int i = 1; i <= 100; i++) {System.out.println("已加载" + i + "%");try {Thread.sleep((long) (Math.random() * 300));} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("结束加载");synchronized (obj){obj.notify();}synchronized (obj){try {obj.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("开始下载...");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}for (int i = 1; i <= 100; i++) {System.out.println("已下载" + i + "%");try {Thread.sleep((long) (Math.random() * 300));} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("结束下载");}
}class ShowThread implements Runnable {private Object obj;public ShowThread(Object obj) {this.obj = obj;}@Overridepublic void run() {
//                try {
//                    t1.join();
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }System.out.println("等待加载中。。。");synchronized (obj) {try {obj.wait();} catch (InterruptedException e) {e.printStackTrace();}}try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("显示图片");synchronized (obj){obj.notify();}}
}

5 小结

到此为⽌我们已经对线程状态有了基本的认识,想要有更多的了解,详情可以⻅下图:

多线程: 父类 Thread
  程序: 软件, 工程
  进程: 正在运行的程序
  线程: 进程中的任务单位

CPU 可以"同时"处理多个线程
  并行: 同一时刻, 同时运行, 通常需要多核处理器
  并发: 多线程, 交替执行(交替速度足够快, 看起来是同时)

实现多线程:

main -> 一个线程
  执行多线程: 随机性

  方式一: 只能继承一个类, 功能性单一
    1.自定义类, 继承Thread
    2.重写run方法
    3.在主程序中创建线程对象
    4.开启线程 start()

  方式二: 实现接口
    1.自定义类, 实现Runnable接口
    2.实现run方法
    3.创建线程对象  ※  使用Runnable对象来构造
    4.开启线程 start

  方式三: 匿名内部类

Thread 基础的API:
  String getName(): Thread 属性 name
  static Thread currentThread(): 获得当前线程对象
  static void sleep(long time): 当前线程的阻塞时间

线程的状态:
    见图

线程安全: 多个线程共享资源
解决安全: 实现线程同步
         加锁: 同步锁  synchronized, 需要借助一个对象
              Lock锁  接口  实现类 ReentrantLock()
                 上锁 lock()  解锁 unlock()

线程其他属性和方法:
    setPriority(1-10越来越大): 设置优先级, 提升了这个线程的执行概率
    setDaemon(true): 设置守护线程, 所有的"前置"线程结束, 守护线程也将自动结束
                     GC -> 垃圾回收(守护线程)
                     System.gc() -> 手动清理

什么是线程?线程概念及方法详细讲解相关推荐

  1. Require使用方法详细讲解

    Require使用方法详细讲解 文章目录 Require使用方法详细讲解 一.AMD 规范 1,AMD 基本介绍 2,AMD 模块规范 二.RequireJS 介绍 1,什么是 RequireJS 2 ...

  2. Python海龟Turtle的使用画中秋画的方法详细讲解

    最近发现很多博主使用Turtle库进行画作,今天来详细讲解海龟库方法 一.定义: Python的turtle库是一个用于绘制图形的库,它来自 Wally Feurzeig, Seymour Paper ...

  3. String类中方法详细讲解

    学习目标: String类方法详细讲解 学习内容: 1.value属性 了解String类的value属性: private final char value[] ; //String类的不可变特性就 ...

  4. 队列使用方法详细讲解

    队列详细讲解 队列是一种先进先出的线性表,它只允许在表的一段进行插入元素,在表的另一端删除元素,先进先出,插入的一端叫做队尾(back),删除的一段叫做对头(front) 队列定义在<queun ...

  5. Java split方法详细讲解

    今天是圣诞节,我是中国人,无视圣诞节. 文章可能有点长,看下来必定有所收获. 没有学过正则表达式的去b站看,一个半小时应该可以看完,要看请点这里 这是必备的前置技能,不懂得话没法真正明白split用法 ...

  6. Android --- PagerAdapter的使用方法详细讲解

    PagerAdapter简介 PagerAdapter是android.support.v4包中的类,它的子类有FragmentPagerAdapter, FragmentStatePagerAdap ...

  7. matlab蒙特卡罗方法求体积_蒙特卡罗方法详细讲解与MATLAB实现.ppt

    下面给出几个常用的α与的数值: ? 关于蒙特卡罗方法的误差需说明两点:第一,蒙特卡罗方法的误差为概率误差,这与其他数值计算方法是有区别的.第二,误差中的均方差σ是未知的,必须使用其估计值 来代替,在计 ...

  8. 泰拉瑞亚修改器服务器能用吗,泰拉瑞亚修改器使用方法详细讲解

    泰拉瑞亚修改器的使用方法一直是小伙伴们所关心的问题,这款游戏走的是像素风,依旧是沙盒内游戏专用的风格,自由度非常的高,而且这款游戏目前在PC端和移动端上都可以使用了,在PC上玩家可以使用修改器,而在移 ...

  9. javascript中常用数组方法详细讲解

    javascript中数组常用方法总结 1.join()方法: Array.join()方法将数组中所以元素都转化为字符串链接在一起,返回最后生成的字符串.也可以指定可选的字符串在生成的字符串中来分隔 ...

最新文章

  1. ASP.NET配置文件Web.config
  2. python opencv生成 html5 支持的mp4
  3. C#windows服务开发
  4. RHEL 5.4 安装Oracle 11gR2, 准备篇...
  5. 室内使用酒精消毒的时候一定要注意开窗!!!
  6. 用友政务知识管理平台_云创数字政务大数据平台,助力政务工作高效管理
  7. python调用摄像头录制视频_Python OpenCV使用摄像头捕获视频
  8. Windows中下载并安装RabbitMQ
  9. 对称密钥、非对称密钥、数字签名、数字证书
  10. HTML|按钮和多选框
  11. 2021-11-13 变电站综合自动化 二次系统安全
  12. (附源码)springboot嘉应房地产公司质量管理系统 毕业设计 453100
  13. 电子设计(1)二极管防电源反接电路
  14. 阿里云 杭州 ARM 云服务器性能评测
  15. ch341a i2c 安卓_CH341A实现USB转I2C的问题
  16. 思科ccie和华为hcie中交换机环路的产生原因和解决方法
  17. 使用Fiddler对手机App抓包
  18. 新媒体工作者必备常识
  19. css:html元素的定位
  20. shell脚本 查看还有多少天过生日

热门文章

  1. vue中findIndex()
  2. 安全帽识别的行业应用
  3. 常见分布期望和方差的推导
  4. 电子设计教程5:稳压管与保险丝防反接电路
  5. sourcetree 卡顿_快手与直播延迟卡顿较劲的这些年
  6. 小波变换、小波分解[python实现]//未完待续
  7. 情人节,我跟对战平台谈恋爱,妖妖,vs,浩方
  8. APT持续性渗透攻击的九份报告
  9. openocd目录_OpenOCD介绍以及上手
  10. string类型转LPCWSTR