一、引言
今天来学习一下多线程的入门的一些小知识!
虽然呀,你工作项目中不一定用得到。但是吧,面试打嘴炮的时候说不定就能派上用场了~
hahaha~
二、目录
目录
三、什么是多线程?
多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理或同时多线程处理器。在一个程序中,这些独立运行的程序片段叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理“。
1.为了了解多线程,我们还需要理解两个术语
进程:进程是执行程序中的一次执行过程,它是一个动态的概念。是系统资源分配的单位。
线程:在系统当中,一个程序有一个进程,一个进程中至少有一个线程。线程就是CPU调度和执行的单位。
四、多线程的实现方式
1.1 继承Thread类
/**
* @Author WangYan
* @Date 2022/11/7 19:16
* @Version 1.0
* 继承Thread 类
* 注意: 线程开启不一定立即执行,由CPU调度执行
*/
public class TestThread extends Thread{
@Override
public void run() {
// run的方法体
for (int i = 0; i < 2000; i++) {
System.out.println("我在刷抖音!" + i);
}
}
public static void main(String[] args) {
TestThread thread = new TestThread();
thread.start();
for (int i = 0; i < 2000; i++) {
System.out.println("我在上厕所!" + i);
}
}
}
1.2 实现Runnable接口
/**
* @Author WangYan
* @Date 2022/11/7 19:48
* @Version 1.0
* 实现Runnable 接口
*/
public class TestRunnable implements Runnable {
@Override
public void run() {
// run的方法体
for (int i = 0; i < 2000; i++) {
System.out.println("我在刷抖音!" + i);
}
}
public static void main(String[] args) {
Thread thread = new Thread(new TestRunnable());
thread.start();
for (int i = 0; i < 2000; i++) {
System.out.println("我在上厕所!" + i);
}
}
}
1.3 实现Callable 接口
/**
* @Author WangYan
* @Date 2022/11/8 16:33
* @Version 1.0
* 实现Callable 接口
*
* 好处: 1.可以定义返回值类型 2、可以抛出异常
*/
public class TestCallable implements Callable<Boolean> {
String name = null;
public TestCallable(String name){
this.name = name;
}
@Override
public Boolean call() throws Exception {
// run的方法体
for (int i = 1; i <= 20; i++) {
System.out.println(name + i);
}
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1.创建线程
TestCallable t1 = new TestCallable("睡觉!");
TestCallable t2 = new TestCallable("水饺");
// 2.创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(2);
// 3.提交执行
Future<Boolean> r1 = ser.submit(t1);
Future<Boolean> r2 = ser.submit(t2);
// 4.获取结果
Boolean res1 = r1.get();
Boolean res2 = r2.get();
System.out.println(res1);
System.out.println(res2);
// 5.关闭服务
ser.shutdown();
}
}
案例
想必龟兔赛跑的故事大家都听过吧,兔子跑得比较快,认为乌龟追不上,就跑了一点路程,睡了一觉。乌龟跑得比较慢,但是乌龟没有停止自己脚步,经过坚持不懈的 努力,最终乌龟赢得了比赛。
/**
* @Author WangYan
* @Date 2022/11/8 15:27
* @Version 1.0
* 龟兔赛跑
*/
public class Race implements Runnable{
public static String winner;
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
String name = Thread.currentThread().getName();
// 兔子线程睡眠,保证每次赢得都是乌龟
if (name.equals("兔子") && i%10 == 0){
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Boolean over = this.isOver(i);
if (over){
break;
}
System.out.println(name + "跑了第" + i + "步");
}
}
/**
* 判断游戏是否结束
* @param steps
* @return
*/
public Boolean isOver(int steps) {
if (winner != null){
return true;
}else {
if (steps >= 100){
winner = Thread.currentThread().getName();
System.out.println("winner is " + winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}
结果
五、多线程方法
2.1守护线程
那就有小伙伴好奇了什么是守护线程呢 ?
线程分为守护线程和用户线程,守护线程的作用是为其他线程运行提供便利服务,用户线程如果全部退出执行,守护线程也会随之关闭,守护线程最经典的 例子GC(垃圾回收器)。
案例
光说概念可能大家也不太能直观的理解,看看下面的案例,你就明白了~
概述:妈妈设置为守护线程,监视我完成作业,等我作业写完,守护线程就会停止。但是不会立即停止,因为虚拟机的停止是需要时间的!
/**
* @Author WangYan
* @Date 2022/11/12 23:02
* @Version 1.0
* 守护线程
* 线程分为守护线程和用户线程
* 守护线程的作用是为其他线程运行提供便利服务,用户线程如果全部退出执行,守护线程也会随之关闭,守护线程最经典的例子GC(垃圾回收器)。
*/
public class TestDaemon {
public static void main(String[] args) {
Mom mom = new Mom();
Thread thread = new Thread(mom);
thread.setDaemon(true); // 开启守护线程
thread.start();
new Thread(new Me()).start();
}
}
class Mom implements Runnable {
@Override
public void run() {
while (true){
System.out.println("妈妈监视我写作业!!");
}
}
}
class Me implements Runnable {
@Override
public void run() {
for (int i = 0; i < 300; i++) {
System.out.println("我在写作业!!");
}
System.out.println("===》我作业写完了!!");
}
}
2.2线程强制插入JOIN()
JOIN合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞。
案例
/**
* @Author WangYan
* @Date 2022/11/11 19:09
* @Version 1.0
* 线程插队 JOIN()
*/
public class TestJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("Vip来了....." + i);
}
}
public static void main(String[] args) throws InterruptedException {
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
for (int i = 0; i < 1000; i++) {
if (i == 500){
thread.join(); // 插队
}
System.out.println("main" +i);
}
}
}
2.3线程优先级
线程的优先级越高意味着获得调度的概率就高,但并不代表优先级低的线程就不会被调度,这都是看CPU心情的!!
/**
* @Author WangYan
* @Date 2022/11/12 22:06
* @Version 1.0
* 线程优先级
* 线程的优先级越高意味着获得调度的概率就高,但并不代表优先级低的线程就不会被调度,这都是看CPU心情的!!
* 线程默认的优先级 5 最大是 10 最小 1
* 线程设置优先级,要在线程启动前设置
*/
public class TestPriority implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "====>" + Thread.currentThread().getPriority());
}
public static void main(String[] args) {
// 主线程的默认优先级
System.out.println(Thread.currentThread().getName() + "====>" + Thread.currentThread().getPriority());
TestPriority testPriority = new TestPriority();
Thread t1 = new Thread(testPriority, "t1");
Thread t2 = new Thread(testPriority, "t2");
Thread t3 = new Thread(testPriority, "t3");
// 设置优先级3
t1.setPriority(3);
t1.start();
// 设置最大优先级
t2.setPriority(Thread.MAX_PRIORITY);
t2.start();
// 设置最小优先级
t3.setPriority(Thread.MIN_PRIORITY);
t3.start();
}
}
2.4线程休眠
sleep(时间)指定当前线程阻塞的毫秒数;
每个对象都有一个锁,sleep不会释放锁;
sleep存在异常InterruptedException;
sleep可以模拟网络延、倒计时;
案例
1.概述:模拟网络抢票延时
/**
* @Author WangYan
* @Date 2022/11/9 15:18
* @Version 1.0
* 线程睡眠 : 更容易发现多线程下方法存在的问题 (放大问题的发生性)
*/
public class TestSleep implements Runnable{
int ticket = 10;
@Override
public void run() {
while (true){
if (ticket <= 0 ){
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到了第" + ticket-- + "票");
}
}
public static void main(String[] args) {
TestSleep ticket = new TestSleep();
// 模拟三条线程同时抢票
new Thread(ticket,"小明").start();
new Thread(ticket,"小强").start();
new Thread(ticket,"黄牛").start();
}
}
1.2概述:倒计时
/**
* @Author WangYan
* @Date 2022/11/9 16:06
* @Version 1.0
* 倒计时
*/
public class TestSleep2 {
public static void main(String[] args) throws InterruptedException {
reciprocal();
}
public static void reciprocal() throws InterruptedException {
int num = 10;
while (true){
Thread.sleep(1000);
System.out.println(num--);
if (num<=0){
break;
}
}
}
}
2.5 线程的五种状态
- Thread.State 线程状态。
线程可以处于以下状态之一:
- NEW 尚未启动的线程处于此状态
- RUNNABLE 在Java虚拟中执行的线程处于此状态
- BLOCKED 被阻塞等待监视器锁定的线程处于此状态
- WAITING 正在等待另一个线程执行特定动作的线程处于此状态
- TIMED_WAITING 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态
- TERMINATED 已退出的线程处于此状态
测试
/**
* @Author WangYan
* @Date 2022/11/11 19:34
* @Version 1.0
* 线程的五种状态 : 新建 、 就绪 、运行 、 阻塞 、 死亡
* 死亡的线程是不能再继续启动的
*/
public class TestState implements Runnable{
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
TestState testState = new TestState();
Thread thread = new Thread(testState);
Thread.State state = thread.getState();
// 创建状态
System.out.println(state);
thread.start();
state = thread.getState();
// 就绪状态
System.out.println(state);
while (state != Thread.State.TERMINATED){
Thread.sleep(1000);
// 更新线程状态
state = thread.getState();
// 持续输出
System.err.println(state);
}
}
}
结果
2.6 线程停止
/**
* @Author WangYan
* @Date 2022/11/9 15:05
* @Version 1.0
* 线程停止
* 1、推荐线程自己停止下来
* 2、建议使用一个标志符来进行终止变量。当flag = false ,则终止线程运行
*/
public class TestStop implements Runnable{
private Boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag){
System.out.println(Thread.currentThread().getName() + "run......." + i++);
}
}
/**
* 线程更改标志符
*/
public void stop(){
this.flag = false;
}
public static void main(String[] args) {
TestStop stop = new TestStop();
new Thread(stop).start();
for (int i = 0; i <= 1000000000; i++) {
if (i == 999999){
// 更改线程标志符,使线程停止
stop.stop();
System.out.println("线程停止了");
}
}
}
}
2.7 线程礼让Yield()
/**
* @Author WangYan
* @Date 2022/11/11 18:21
* @Version 1.0
* 线程礼让 : 线程礼让不一定成功,看CPU心情 !!
*/
public class TestYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程开始执行!");
Thread.yield(); // 礼让
System.out.println(Thread.currentThread().getName() + "线程结束执行!");
}
public static void main(String[] args) {
TestYield testYield = new TestYield();
new Thread(testYield,"a").start();
new Thread(testYield,"b").start();
}
}
2.8 线程同步(synchronized和Lock)
那什么是线程同步呢 ? 怎么去使用 ?
是由于同一进程中的多个线程共享一块存储空间,在带来方便的同时,也带来了冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入了 “锁机制”synchronized. 当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可
案例
1.多人抢票(Sync锁)
/**
* @Author WangYan
* @Date 2022/11/7 20:23
* @Version 1.0
* 模拟: 多个人抢票
* 问题 : 会出现两个人抢到同一张票的情况
*/
public class ThreadProblem01 implements Runnable{
int ticket = 10;
boolean flag = true;
@Override
public void run() {
while (flag){
try {
this.bug();
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 使用 synchronized 锁方法
* @throws InterruptedException
*/
public synchronized void bug() throws InterruptedException {
if (ticket <= 0) {
flag = false;
return;
}else {
System.out.println(Thread.currentThread().getName() + "抢到了" + ticket-- + "票");
}
}
public static void main(String[] args) {
ThreadProblem01 thread = new ThreadProblem01();
new Thread(thread,"小三").start();
new Thread(thread,"赵四").start();
new Thread(thread,"王五").start();
}
}
2.Lock锁
/**
* @Author WangYan
* @Date 2022/11/13 17:27
* @Version 1.0
* Lock 锁
* ReentrantLock 可重入锁
*/
public class TestLock implements Runnable {
ReentrantLock lock = new ReentrantLock();
int ticket = 10;
boolean flag = true;
@Override
public void run() {
while (flag){
this.buy();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 买票方法
*/
public void buy(){
try {
lock.lock();
if (ticket <= 0){
this.flag = false;
return;
}else {
System.out.println(Thread.currentThread().getName() + "抢到了" + ticket-- + "票");
}
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
TestLock thread = new TestLock();
new Thread(thread,"小三").start();
new Thread(thread,"赵四").start();
new Thread(thread,"王五").start();
}
}
3.线程不安全集合
/**
* @Author WangYan
* @Date 2022/11/13 15:51
* @Version 1.0
* 线程集合不安全测试
*/
public class UnsafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 1; i <= 10000; i++) {
new Thread(() -> {
synchronized (list){
list.add(Thread.currentThread().getName());
}
}).start();
}
// 这里睡眠三秒,目的是等待其他线程执行完
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
2.9 死锁
那死锁是怎么产生的呢 ?
多个线程各自占用一些共享资源,并且互相等待其他线程占用的资源才能运行,而导致两个或多个的线程等待对方释放资源,都停止的执行的情形叫做 “死锁”。
说白了就是多个线程互相抱着对方需要的资源,形成僵持. 某一个同步块同时拥有 “两个以上对象锁时”,就可能发生死锁问题.
案例
1.两个人准备化妆,一个人拿镜子,一个人拿口红.都想获得对方手中的东西
/**
* @Author WangYan
* @Date 2022/11/13 17:03
* @Version 1.0
* 死锁案例 : 一个人拿镜子,一个人拿口红.都想获得对方手中的东西
*/
public class DeadLock {
public static void main(String[] args) {
Makeup makeup = new Makeup(0, "灰姑娘");
Makeup makeup1 = new Makeup(1, "白雪公主");
new Thread(makeup).start();
new Thread(makeup1).start();
}
}
// 口红
class Lipstick {
}
// 镜子
class Mirror {
}
class Makeup implements Runnable {
// 用static保证资源只有一份
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
/**
* choice 选择
* girlName 使用化妆品的人
*/
int choice;
String girlName;
Makeup(int choice,String girlName){
this.choice = choice;
this.girlName = girlName;
}
@Override
public void run() {
// 化妆
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 化妆,互相持有对方的锁,就是需要拿到对方的资源
*/
public void makeup() throws InterruptedException {
if(choice == 0){
// 获得口红的锁
synchronized (lipstick){
System.out.println(this.girlName + "获得口红的锁");
Thread.sleep(1000);
// 一秒后获得镜子的锁
synchronized (mirror){
System.out.println(this.girlName + "获得镜子的锁");
}
}
}else {
// 获得镜子的锁
synchronized (mirror){
System.out.println(this.girlName + "获得镜子的锁");
Thread.sleep(1000);
// 一秒后获得镜子的锁
synchronized (lipstick){
System.out.println(this.girlName + "获得口红的锁");
}
}
}
}
}
那怎么避免死锁的产生?
产生死锁的四个必要条件:
- 互斥条件: 一个资源每次只能被一个进程使用
- 请求与保持条件: 一个进程因请求资源堵塞时,对已获得的资源保持不放
- 不剥夺条件: 进程已获得资源,在未使用完前,不能强行剥夺.
- 循环等待条件 : 若干个进程之间形成一种头尾相接的循环等待资源关系.
3.0 线程通信
应用场景:生产者和消费者问题
- 假设仓库只能存储一件产品,生产者将生产出来的产品放入仓库,消费者从仓库中取出消费
- 如果仓库中没有产品,生产者将产品放入仓库,停止生产,等待消费者消费再去生产
- 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止.
案例
1.生产者生产品放入缓冲区,消费者从缓冲区获得产品进行消费,如果缓冲区产品放满,生产者不再生产,等待消费者消费(如上图)
/**
* @Author WangYan
* @Date 2022/11/14 22:35
* @Version 1.0
* 线程通信 管程法
*/
public class TestPC {
public static void main(String[] args) {
Buffer buffer = new Buffer();
new Producer(buffer).start();
new Consumer(buffer).start();
}
}
// 生产者
class Producer extends Thread{
Buffer buffer;
public Producer(Buffer buffer){
this.buffer = buffer;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("生产了" + i + "只鸡");
buffer.push(new Product(i));
}
}
}
// 消费者
class Consumer extends Thread{
Buffer buffer;
public Consumer(Buffer buffer){
this.buffer = buffer;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了---》" + buffer.pop().id + "只鸡");
}
}
}
// 缓冲区
class Buffer {
// 创建个容器,生产者存储,消费者进行获取
Product[] products = new Product[10];
// 计数器
int count = 0;
/**
* 生产者存储消息
*/
public synchronized void push(Product product){
// 判断容器是否满了
if (count == products.length){
// 生产者线程进行等待,消费者进行消费
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 容器没满,放入产品
products[count] = product;
count++;
// 可以通知消费者进行消费
this.notifyAll();
}
/**
* 消费者消费产品
*/
public synchronized Product pop(){
if (count == 0){
// 等待生产者进行生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 进行消费
count--;
Product product = products[count];
// 消费完了,通知生产者进行生产
this.notifyAll();
return product;
}
}
// 产品
class Product {
public Product(int id){
this.id = id;
}
int id;
}
2.演员表演节目,观众等待。观众观看节目,演员进行等待。
/**
* @Author WangYan
* @Date 2022/11/14 23:50
* @Version 1.0
* 线程通信 信号灯法
*/
public class TestPC2 {
public static void main(String[] args) {
TV tv = new TV();
new Producer2(tv).start();
new Consumer2(tv).start();
}
}
// 生产者 --> 演员
class Producer2 extends Thread {
TV tv;
public Producer2(TV tv){
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i%2 == 0){
this.tv.play("抖音:记录美好生活!");
}else {
this.tv.play("快手:精神小妹!");
}
}
}
}
// 消费者 --> 观众
class Consumer2 extends Thread {
TV tv;
public Consumer2(TV tv){
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
this.tv.watch();
}
}
}
// 产品 --> 节目
class TV{
/**
* 演员表演节目,观众等待 True
* 观众观看节目,演员等待 False
*/
String voice;
Boolean flag = true;
/**
* 演员进行表演
* @param voice
*/
public synchronized void play(String voice){
if (flag == false){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演员开始表演节目" +voice);
// 通知观众进行观看
this.notifyAll();
this.voice = voice;
this.flag = !this.flag;
}
/**
* 观众进行观看
*/
public synchronized void watch(){
if(flag == true){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观看了" + voice);
// 通知演员进行表演
this.notifyAll();
this.flag = !this.flag;
}
}
3.1 线程池
什么是线程池呢 ?为什么要用它 ?
线程池提供了一个线程队列,队列中保存着所有等待状态的线程。避免了创建与销毁额外开销,提高了响应速度。
案例
简单使用:线程池的创建有四种方式,选择其中一种进行展示
/**
* @Author WangYan
* @Date 2022/11/15 15:13
* @Version 1.0
* 线程池
*/
public class TestPool {
public static void main(String[] args) {
// 1.创建服务,创建线程池
ExecutorService service = Executors.newFixedThreadPool(10);
// 2.执行线程
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
// 3.关闭连接
service.shutdown();
}
}
class MyThread extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
五、总结
本文主要讲了对多线程入门的一些认识、方法使用和介绍,后面会更新JUC编程~
拜拜~
有任何问题欢迎大家指出~
Thank You !