1. 基本概念
- 程序
程序 是指令和数据组成的有序集合 - 进程
执行一次程序的过程
进程是系统资源分配的单元 - 线程
一个进程包括一个或多个线程
线程是 CPU 调度与执行的单位
线程是独立执行的
注意:
多线程是指多个
CPU
(即多核) 同时进行不同的任务。一个
CPU
在同一个节点只能执行一个线程
2. 创建进程的方式
- 继承
Thread class
- 实现
Runnable interface
- 实现
Callable interface
Thread class
实现了Runnable interface
JVM 允许应用程序同时执行多个执行线程
2.1 线程的优先级
每个线程都有一个优先级
较高优先级的线程优先于优先级较低的线程执行
新线程的优先级最初设置为创建线程的优先级
当在某个线程中创建一个新的线程时,并且当且仅当创建线程是守护进程时新线程才是守护线程。
2.2 main 线程停止
JVM 启动时,通常有一个非守护线程(通常调用某些指定类的名为 main
的方法)。 JVM 将继续执行线程,直到发生以下任一情况:
- 已经调用了
Runtime
类的exit
方法,并且安全管理器已经允许进行退出操作 - 所有非守护进程线程的全部死亡
2.3 创建新线程
2.3.1 Thread
- 继承
Thread
类 - 重写
run()
main()
方法new
这个类并调用start()
/**
* description: 线程学习
* @author: Leet
* @date: 2020-11-10 23:25
**/
public class TestThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 2000; i++) {
System.out.println(String.format("%s%d", "我在看代码---", i));
}
}
public static void main(String[] args) {
new TestThread1().start();
for (int i = 0; i < 2000; i++) {
System.out.println(String.format("%s%d", "我在学习多线程---", i));
}
}
}
2.3.2 Runnable
- 实现
Runnable
- 重写
run()
- 在
main()
中new
一个这个类作为参数传给Thread
对象,调用start()
/**
* description: 多线程学习2
*
* @author: Leet
* @date: 2020-11-10 23:39
**/
public class TestThread2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 2000; i++) {
System.out.println(String.format("%s%d", "我是天才-----", i));
}
}
public static void main(String[] args) {
new Thread(new TestThread2()).start();
for (int i = 0; i < 2000; i++) {
System.out.println(String.format("%s%d", "不,我才是天才-----", i));
}
}
}
2.3.3 Callable
- 实现
Callable interface
- 重写
call()
方法,需抛出异常 - 创建目标对象 XXX
- 创建执行服务:
ExecutorService es = Executors.newFixedThreadPool(1);
- 提交执行:
Future<Boolean> result = es.submit(XXX);
- 获取结果:
boolean r = result.get();
- 关闭服务:
es.shutdown();
3. 静态代理模式
真实对象与代理对象都实现同一个接口
代理对象代理真实角色
package proxy;
/**
* description:静态代理学习
* 通过“结婚”这个场景带入
* @author Leet
* @date 2020/11/11 9:31
*/
public interface Marry {
void marry();
}
class You implements Marry {
@Override
public void marry() {
System.out.println("今天我要结婚了,我很高兴");
}
}
class WeddingCompany implements Marry {
public WeddingCompany(Marry marrier) {
this.marrier = marrier;
}
private Marry marrier;
@Override
public void marry() {
before();
marrier.marry();
after();
}
private void before() {
System.out.println("结婚前,婚庆公司布置现场");
}
private void after() {
System.out.println("结婚后,婚庆公司打扫现场");
}
}
class TestStaticProxy {
public static void main(String[] args) {
WeddingCompany weddingCompany = new WeddingCompany(new You());
weddingCompany.marry();
}
}
3.1 Thread class 中的代理
Thread 类
代理 Runnable interface
的实现类
4. lambda
- 函数式接口
该接口只有一个方法
在使用该接口实现类的时候,就可以使用 lambda
public class Main {
public static void main(String[] args) {
new Thread(() -> System.out.println("lambda 创建线程")).start();
}
}
lambda 表达式在容器的流式编程中用着很舒服
5. 线程状态
5.1 线程方法
setPriority(int newPriority) | 更改线程优先级 |
static void sleep(long milis) | 让该线程休眠 milis ms |
void join() | 等待该线程终止 |
static void yield() | 暂停该线程,执行其他线程(让贤) |
isAlive() | 该线程是否存活 |
5.1.1 线程停止
- 利用有限的次数让线程停止
- 使用一个
flag
(终止循环)让线程停止 - 不要使用
stop()
、destory()
(被舍弃)
5.1.2 线程休眠
sleep()
需要捕获异常:InterruptedException
sleep 不会释放锁
达到失眠时间后,该线程进入就绪状态
5.1.3 线程礼让
yield()
线程礼让:让当前线程停止,但不阻塞,变为就绪状态
礼让不一定成功
5.1.4 join()
合并线程,待此线程执行完,再执行其他线程(其他线程此时阻塞)
/**
* description: join() 方法测试
* @author: Leet
* @date: 2020-11-11 11:06
**/
public class TestJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.printf("%s%d\n", "线程 VIP 来了 ---》 ", i);
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new TestJoin());
thread.start();
for (int i = 0; i < 500; i++) {
if (i == 200) {
thread.join();
}
System.out.printf("%s%d\n", "线程 main ---》 ", i);
}
// main :VIP 线程属实过分了,它自己执行那么多次,不老实排队,竟然利用自己的特权插队,这是可恶。
}
}
5.2 观测线程状态:Thread.State
5.3 线程优先级
Java 为每个线程提供了优先级
线程调度器按照优先级决定调度哪个线程
- 优先级只是意味着被调度概率的高低
- 线程优先级:1~10
- 获取线程优先级
getPriority()
- 设置线程优先级
setPriority(int priority)
建议:优先级设定在 start()
前
/**
* description: 线程优先级测试
*
* @author: Leet
* @date: 2020-11-11 11:26
**/
public class TestPriority implements Runnable {
@Override
public void run() {
System.out.printf("%S 的 priority : %d\n", Thread.currentThread().getName(), Thread.currentThread().getPriority());
}
public static void main(String[] args) {
System.out.printf("%S 的 priority : %d\n", Thread.currentThread().getName(), Thread.currentThread().getPriority());
Thread thread1 = new Thread(new TestPriority(), "thread1");
Thread thread2 = new Thread(new TestPriority(), "thread2");
Thread thread3 = new Thread(new TestPriority(), "thread3");
Thread thread4 = new Thread(new TestPriority(), "thread4");
Thread thread5 = new Thread(new TestPriority(), "thread5");
thread1.setPriority(1);
thread2.setPriority(5);
thread2.setPriority(9);
thread4.setPriority(2);
thread5.setPriority(10);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread5.start();
}
}
5.4 守护线程
setDaemon(true)
将该线程设为守护线程- 用户线程结束,不管守护线程如何,JVM(程序)停止
/**
* description: 守护线程
* @author: Leet
* @date: 2020-11-11 14:15
**/
public class TestDaemon {
public static void main(String[] args) {
Thread thread = new Thread(new God());
thread.setDaemon(true);
thread.start();
new Thread(new You()).start();
}
}
class God implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("GOD BLESS YOU");
}
// 你到天堂,见到 GOD
// GOD: 我还没反应过来,你咋就死了????!!!!!
}
}
class You implements Runnable {
@Override
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("You are happy every day");
}
System.out.println("----------Goodbye! The world----------");
}
}
6. 线程同步
6.1 队列与锁
- 对于统一资源访问人数较多时,可以采用队列
- 为解决访问冲突,在访问时,加入锁机制 synchronized
一个线程获得对象的排他锁。该线程独占资源,其他线程必须等待至该线程释放锁。- 存在的问题
- 未拿到锁的线程被挂起
- 性能损失:
- 调度
- 优先级
- 存在的问题
6.2 不安全的例子
- 买票
- 取钱
- 容器
6.3 同步方法
通过 private
保证数据只能被对象访问,针对方法提出 synchronized
机制(作用在方法或块上)保证同步。
对于普通同步方法,锁是当前实例对象,如果有多个实例,那么锁对象必然不同,无法完成同步
对于静态同步方法,锁是当前类的 Class 对象
。有多个实例,但是锁对象是相同的,可以完成同步
对于同步方法块,锁是 synchronized
括号中的配置对象。
6.4 死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,导致两个或多个线程都在等待对方释放资源,都停止的情形
一个同步块同时拥有 两个以上对象的锁 时,就可能发生“死锁”问题
package synchronizedtest.deadsynchronized;
/**
* description: 验证死锁
* 死锁:多个线程互相持有各自需要的资源,而僵持
* synchronized 块同时持有两个以上对象的锁,就可能造成死锁
*
* @author: Leet
* @date: 2020-11-11 15:25
**/
public class DeadLock {
public static void main(String[] args) {
// 灰姑娘想化妆,她拿起了口红
new Thread(new Makeup(0, "灰姑娘")).start();
// 白雪公主想化妆,她拿起了镜子
new Thread(new Makeup(1, "白雪公主")).start();
}
}
/**
* 口红
*/
class Lipstick {
}
/**
* 镜子
*/
class Mirror {
}
/**
* 化妆
*/
class Makeup implements Runnable {
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choice;
String girlName;
public Makeup(int choice, String girlName) {
this.choice = choice;
this.girlName = girlName;
}
@Override
public void run() {
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* lipstick 与 mirror 是 Makeup 类的静态属性,为 Makeup 类对象持有;
* lipstick 与 mirror只有一个,
* 所以说,下方同步块中的代码,锁共享资源的方法正确
*/
private void makeup() throws InterruptedException {
if (choice == 0) {
synchronized (lipstick) {
System.out.printf("%s 获得口红\n", girlName);
Thread.sleep(1000);
}
synchronized (mirror) {
System.out.printf("%s 获得镜子\n", girlName);
}
} else {
synchronized (mirror) {
System.out.printf("%s 获得镜子\n", girlName);
Thread.sleep(2000);
}
synchronized (lipstick) {
System.out.printf("%s 获得口红\n", girlName);
}
}
}
}
7. 线程协作
之前学的都是每个线程各自进行自己的工作,彼此间没有关系、互不干扰。
现在要让线程配合起来工作!
7.1 生产者与消费者模式
生产者与消费者的问题
假设仓库中只能存放一件产品,生产者将生产的东西放入仓库,消费者将仓库中的东西取走消费
如果仓库中没有产品,那么生产者将产品放入仓库,否则停止生产并等待,直至仓库中的东西被消费者取走
如果仓库中放有产品,那么消费者将产品取走消费,否则停止消费并等待,直至仓库中再次放有产品
这个是一个线程同步的问题,二者共享同一个资源,彼此相互依赖、互为条件.
显而易见,仅使用 synchronized
是不够的:synchronized
不能实现不同线程间消息的传递。
7.2 线程通信
wait() | 让线程一直等待,直至其他线程通知,期间不持有(释放)锁 |
wait(long timeout) | 让线程一直等待指定的毫秒数 |
notify() | 唤醒一个处于等待状态的线程 |
notifyAll() | 唤醒同一个类上所有调用 wait() 方法的线程,优先级高的线程优先调度 |
注意:他们都是 Object 类
中的方法,必须在同步块中调用,否则抛出 IllegalMonitorStateException
7.3 解决方法
7.3.1 管程法
设置一个具有一定容量的缓冲区,生产者生产的东西放入缓冲区,消费者从缓冲区取东西消费
根据缓冲区容量的状态判断唤醒哪个线程、让哪个线程等待
package thread.synchronize;
/**
* description: 生产者与消费者问题:管程法解决
*
* @author: Leet
* @date: 2020-11-12 19:58
**/
public class TestPC {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Productor(container).start();
new Consumer(container).start();
}
}
class Productor extends Thread {
SynContainer container;
public Productor(SynContainer container) {
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
container.push(new Chicken(i));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("生产了 %d 只鸡\n", i);
}
}
}
class Consumer extends Thread {
SynContainer container;
public Consumer(SynContainer container) {
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
System.out.printf("消费了---> %d 只鸡\n", container.pop().id);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Chicken {
int id;
public Chicken(int id) {
this.id = id;
}
}
class SynContainer {
Chicken[] chickens = new Chicken[10];
int count = 0;
/**
* 生产者放入产品
* @param chicken 产品
* @throws InterruptedException InterruptedException
*/
public synchronized void push(Chicken chicken) throws InterruptedException {
// 如果容器满了,需要等待消费者消费
if (count == chickens.length) {
wait();
} else {
// 容器没满,生产者直接放入产品
chickens[count++] = chicken;
// 通知消费者消费
notifyAll();
}
}
/**
* 消费者消费
* @return Chicken Chicken
* @throws InterruptedException InterruptedException
*/
public synchronized Chicken pop() throws InterruptedException {
// 如果容器空了,消费者等待(等待生产者向容器中放入产品)
if (count == 0) {
this.wait();
} {
// 容器中有产品,消费者直接消费
Chicken chicken = chickens[--count];
// 告知生产者生产东西
notifyAll();
return chicken;
}
}
}
7.3.2 信号灯法
设置一个标识符,根据标识符的状态判断唤醒哪个线程、让哪个线程等待
package thread.synchronize;
/**
* description: 生产者与消费者问题:信号灯法解决
*
* @author: Leet
* @date: 2020-11-12 20:32
**/
public class TestPC2 {
public static void main(String[] args) {
TV tv = new TV();
new Player(tv).start();
new Watcher(tv).start();
}
}
class Player extends Thread {
TV tv;
public Player(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i % 2 == 0) {
this.tv.play("Hello World 播放中");
} else {
this.tv.play("抖音:记录记录美好生活");
}
}
}
}
class Watcher extends Thread {
TV tv;
public Watcher(TV tv) {
this.tv = tv;
}
@Override
public void run() {
tv.watch();
}
}
class TV {
String voice;
/**
* true: 演员表演,观众等待
* false: 观众观看,演员等待
*/
boolean flag = true;
public synchronized void play(String voice) {
if (!flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.printf("演员表演了 %s\n", voice);
notifyAll();
this.voice = voice;
this.flag = !this.flag;
}
public synchronized void watch() {
if (flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.printf("观众观看了 %s\n", voice);
notifyAll();
this.flag = !this.flag;
}
}
这个例子有点缺陷:会死锁。
那请聪明的你改一改吧
8. 线程池
ExecutorService
线程池接口void execute(Runnable command)
执行Runnable 接口
的实现类<T> Future <T> submit(Callable<T> task)
执行Callable 接口
的实现类void shutdown()
关闭线程池
Executor
工具类、线程池的工厂类
用于创建并返回不同类型的线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* description: 线程池
*
* @author: Leet
* @date: 2020-11-12 20:57
**/
public class TestThreadPool implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(10);
es.execute(new TestThreadPool());
es.execute(new TestThreadPool());
es.execute(new TestThreadPool());
es.execute(new TestThreadPool());
es.shutdown();
}
}