再探多线程Ⅰ--- (创建思路+核心方法+代码样例)
上篇我们详细的拆解了多线程的基本概念及架构 , 我希望通过本篇文章系统性的介绍并展示多线程的创建思路, 常用方法及线程安全等补充, 我们从温故线程的基本状态开始!
1. 线程状态
1.1 线程状态总览
- 我绘制了以下流程图作为上一篇的总结
1.2 核心要点
1.2.1 sleep(time) 和 wait(time) 有啥区别?
- a. sleep(time): 线程睡眠, 睡眠过程中不会释放锁, 此时其他线程无法抢到锁, 设置的时间一旦超时, 自动醒来并继续执行
- b. wait(time): 线程等待,等待过程中会释放锁, 其他线程就有可能抢到锁, 如果在等待期间被唤醒或者等待超时, 会和其他线程共同重新抢锁, 如果抢到了才能继续执行,抢不到则锁阻塞
1.2.2 解释wait()和notify()?
- a. 空参wait: wait() ,线程会进入到无限等待状态,会释放锁,需要其他同一锁下的线程调用notify(一次只能唤醒一条线程且是随机的)或者 使用notifyAll(将所有同一个对象锁下的等待线程全部唤醒), 被唤醒之后会和其他线程重新抢锁 ,抢到了继续执行,抢不到则阻塞
- b. notify():一次只能唤醒一条线程且是随机的
- c. notifyAll():将所有同一个对象锁下的等待线程全部唤醒
1.2.3 wait()和notify()用法?
- a. 两个方法都需要锁对象调用, 所以两个方法需要用到同步代码块(同步方法)
- b. 两个方法的调用必须是同一个锁对象
2. 创建多线程的方式
2.1 继承Thread
- a. 定义一个类并继承(extends)Thread
- b. 重写run 方法并设置线程任务
- c. 创建自定义线程类, 将其放到Thread对象之中
- d. 调用start方法并开启线程,jvm自动执行run方法
- 代码示例:
public class Test01 {public static void main(String[] args) {MyThread t1=new MyThread();//创建线程对象t1.start();//调用start开启线程,jvm自动调用run方法for(int i=0;i<3;i++){System.out.println("main线程--------"+i);}} }
public class MyThread extends Thread{@Overridepublic void run() {for(int i=0;i<3;i++){System.out.println("my thread---"+i);}} }
运行结果:
2.2 实现Runnable
- a. 定义一个类, 实现Runnable接口
- b. 重写run方法, 设置线程任务
- c. 创建自定义线程类并创建线程任务的对象, 将该对象其放到Thread对象中
- d. 调用 start方法, 开启线程, jvm自动执行run方法
- 代码示例:
public class MyRunnable implements Runnable{//创建线程的第二种方式-->实现接口@Overridepublic void run() {for(int i=0;i<10;i++){System.out.println(Thread.currentThread().getName()+"...执行了"+i);}} }
public class Test01 {public static void main(String[] args) {MyRunnable myRunnable=new MyRunnable();Thread t1=new Thread(myRunnable);//调用Thread中的方法开启线程t1.start();for(int i=0;i<10;i++){System.out.println(Thread.currentThread().getName()+"...执行了"+i);}} }
代码结果:
2.3 匿名内部类(2.2的扩展)
public class Test02 {public static void main(String[] args) {Thread t2=new Thread(new Runnable(){//匿名内部类实现Runnable接口@Overridepublic void run() {for(int i=0;i<4;i++){System.out.println(Thread.currentThread().getName()+"...执行了"+i);}}});t2.setName("t2");t2.start();} }
代码结果:
3. Thread 中的方法
- a. void start() -> 开启线程, jvm自动调用run方法
- b. void run() -> 设置线程任务,这个run 方法是Thread重写的接口Runnable中的run方法
- c. String getName() ->获取线程名字
- d. void setName(String name) ->命名
- e. static void sleep(time) -> 线程睡眠(超时后自动醒来继续执行,传递的是毫秒值)
- f. staticThread currentThread() -> 获取当前正在执行中的线程
4. 线程安全
4.1 什么时候发生? (synchronized出场?)
- a. 共享数据修改: 多个线程同时读写同一变量(如全局计数器、集合元素)
- b. 非原子操作: 复合操作被线程切换打断(如
i++
实际包含"读-改-写"三步) - c. 可见性问题: 线程A修改数据后,线程B看不到最新值,
- 原因:CPU缓存 vs 主内存不一致
- d. 指令重排序问题: 编译器/CPU优化打乱代码顺序
// 风险点标记 class ThreadUnsafeExample {// 风险1:共享可变数据private static int counter = 0; // 风险2:非线程安全集合private static List<String> dataList = new ArrayList<>();public void addData(String item) {// 风险3:非原子复合操作if (!dataList.contains(item)) {dataList.add(item);}}public void increment() {// 风险4:非原子自增counter++; } }
4.2 同步代码块(Ⅰ)
- synchronized(锁对象){可能出现线程安全问题的代码}
- 代码示例:
public class MyTicket implements Runnable {int ticket = 1000;//定义100张票//任意new一个对象final Object obj=new Object();@Overridepublic void run() {//买票while (true) {try {//一定延时Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (obj) {//方法一:对于代码块(可能出现线程安全问题的)if (ticket > 0) {System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");ticket--;}}}} }
public class Test01 {public static void main(String[] args) {MyTicket myTicket=new MyTicket();Thread t1=new Thread(myTicket,"马牛逼");Thread t2=new Thread(myTicket,"蔡徐坤");Thread t3=new Thread(myTicket,"鸽哥");t1.start();t2.start();t3.start();} }
代码结果:
4.3 同步方法
4.3.1 非静态的同步方法
- a. synchronized 返回值类型 方法名 (形参){方法体 返回值}
- b. 默认锁: this(即当前创建的对象)
- 代码示例:
public class MyTicket implements Runnable {int ticket = 1000;//定义100张票@Overridepublic void run() {//买票while (true) {try {//一定延时Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);} // method01();method02();}}// public synchronized void method01(){//方法2:单独写一个非静态的synchronized方法独立于run方法(在run中调用即可) // if (ticket > 0) { // System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票"); // ticket--; // } // }public void method02() {//方法3:单独写一个非静态的方法独立于run方法并在核心代码块外包裹synchronized(在run中调用即可)synchronized (this) {//默认锁:this(当前唯一new的对象)System.out.println(this);if (ticket > 0) {System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");ticket--;}}} }
public class Test01 {public static void main(String[] args) {MyTicket myTicket=new MyTicket();System.out.println(myTicket);Thread t1=new Thread(myTicket,"马牛逼");Thread t2=new Thread(myTicket,"蔡徐坤");Thread t3=new Thread(myTicket,"鸽哥");t1.start();t2.start();t3.start();} }
代码结果:
4.3.2 静态的同步方法
- a. 修饰符 static sychronized 返回值类型 方法名 (形参){方法体 返回值}
- b. 默认锁: class 对象
- 代码示例:
public class MyTicket implements Runnable {static int ticket = 1000;//@Overridepublic void run() {//具体操作while (true) {try {//延时Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}method03();}}public static void method03(){//方法4:静态同步方法synchronized (MyTicket.class) {//默认锁:当前类的class对象if (ticket > 0) {System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");ticket--;}}}}
public class Test01 {public static void main(String[] args) {MyTicket myTicket=new MyTicket();System.out.println(myTicket);Thread t1=new Thread(myTicket,"马牛逼");Thread t2=new Thread(myTicket,"蔡徐坤");Thread t3=new Thread(myTicket,"鸽哥");t1.start();t2.start();t3.start();} }
代码结果:
5. 死锁
5.1 死锁?
- 两个或多个线程互相持有对方需要的资源,并无限等待对方释放,导致所有线程永久阻塞的状态。
5.2 死锁发生的四个必要条件
- a. 互斥: 资源只能被一个线程独占使用(如锁)
- b. 持有并等待: 线程已持有资源,同时等待获取其他线程的资源
- c. 不可剥夺: 资源只能由持有者主动释放,无法强行抢夺
- d. 循环等待: 线程间形成环形等待链(
T1 等待 T2 的资源
→T2 等待 T3 的资源
→ ... →Tn 等待 T1 的资源
)
5.3 经典场景
- 代码示例:
public class Deadlock implements Runnable {private final boolean flag;public Deadlock(boolean flag) {this.flag = flag;}@Overridepublic void run() {if (flag) {synchronized (LockA.locka) {System.out.println("if...locka");synchronized (LockB.lockb) {System.out.println("if...lockb");}}} else {synchronized (LockB.lockb) {System.out.println("else...lockb");synchronized (LockA.locka) {System.out.println("else...locka");}}}} }
public class Test {public static void main(String[] args) {Deadlock deadlock1=new Deadlock(true);Deadlock deadlock2=new Deadlock(false);new Thread(deadlock1).start();new Thread(deadlock2).start();} }
public class LockA {public final static LockA locka=new LockA(); }
public class LockB {public final static LockB lockb=new LockB(); }
执行结果:(99%的可能如下除了某一线程比较牛逼同时抢到了lockA和lockB)