Java多线程解析
1.1 程序/进程
什么是程序?
程序就是一堆静止的代码。
什么是进程?
进程就是程序的一次执行。
1.2 操作系统的分类
单任务OS
一段时间内只能执行一个任务,如果要执行其他任务,必须把前一个任务结束后,才能进行第二个任务
代表:DOS
多任务OS
一段时间可以运行多个任务(程序),比如有两个任务A,B,当前运行A任务,如果想运行B任务,可以把A任务切换出来,让B运行。
在某个时刻只能有一个任务运行。
进程的概念和并发的概念
在单CPU的情况下,多个任务产生多个进程,这些进程之间可以切换,通过CPU时间片轮转来达到多任务。
并发:多个进程在一段时间内运行,叫做进程的并发。
并行:多个进程在同一时刻运行,叫做进程的并行。
一定个多个CPU才能并行。
进程提高了CPU的利用率。
线程
线程是把进程中的执行单元进行细分,分为能够独立运行的功能体,这些功能体就叫做线程
线程进一步提高了CPU的利用率
案例:
把学生上课比作学习的进程
总结
多线程进一步提高了CPU的利用率
多线程增加的程序的复杂度
1.3 进程和线程的区别和联系
可参考文章:
https://www.cnblogs.com/geeta/p/9474051.html
1.4 实现线程的方式
1.4.1 继承Thread
public class MyThread extends Thread{
@Override public void run() { for(int i=0;i<10;i++) { System.out.println("MyThread:"+i); } } } |
public class Test01 { public static void main(String[] args) {
// 【1】创建一个线程 MyThread t1 = new MyThread(); // 【2】启动线程,不是调用run t1.start();
} }
|
注意:
[1]启动线程通过start() 方法
[2] 一个进程中一定有一个主线程,一般是main方法所在的线程。
[3] 从执行结果看,具有多线程的程序,程序执行结果不确定 => 增加了程序控制的复杂性
分析线程执行轨迹
1.4.2 实现Runnable接口
public class MyRun implements Runnable {
@Override public void run() { for(int i=0;i<10;i++) { System.out.println("MyThread:->"+i); } } } |
public class TestRun { public static void main(String[] args) {
MyRun run = new MyRun(); Thread t1 = new Thread(run); t1.start();
for(int i=0;i<10;i++) { System.out.println("MainThread:=>"+i); } } }
|
实现Runnable接口表示类具备多线程运行的能力。
1.4.3 两种实现方式的对比
从形式上
一个类继承了Thread类后就不能再继承其他类。
一个类实现了Runnable接口,还可以继承其他类。
从本质上
实现Runnable接口的类可以给多个线程 资源共享
案例:请模拟火车站购票的案例
通过继承Thead实现
public class ThreadTicket extends Thread{
private static int ticket = 5;
public ThreadTicket(String aName) { super(aName); }
@Override public void run() { for(int i=0;i<10;i++) { if(ticket > 0) { ticket--; System.out.println(this.getName()+"还剩"+ticket+"张票"); } } } } |
public class TestTicket { public static void main(String[] args) {
ThreadTicket t1 = new ThreadTicket("窗口1"); ThreadTicket t2 = new ThreadTicket("窗口2"); ThreadTicket t3 = new ThreadTicket("窗口3"); ThreadTicket t4 = new ThreadTicket("窗口4");
t1.start(); t2.start(); t3.start(); t4.start(); } }
|
通过Runnable接口实现
public class RunTicket implements Runnable {
private int ticket = 5;
@Override public void run() { for(int i=0;i<10;i++) { if(ticket > 0) { ticket--; System.out.println(Thread.currentThread().getName() + "还剩"+ticket+"张票"); } } } } |
public class TestRun { public static void main(String[] args) {
RunTicket run = new RunTicket(); Thread t1 = new Thread(run,"窗口1"); Thread t2 = new Thread(run,"窗口2"); Thread t3 = new Thread(run,"窗口3"); Thread t4 = new Thread(run,"窗口4");
t1.start(); t2.start(); t3.start(); t4.start();
} } |
分析多线程运行轨迹
1.5 多线程和代理设计模式
1.6 线程的生命周期
1.7 线程的常用方法
1.7.1 [1]获取当前线程
public static void main(String[] args) { // 返回当前正在执行的线程的对象 Thread mainThread = Thread.currentThread(); System.out.println("当前线程的名称:"+mainThread.getName()); System.out.println("当前线程的Id:"+mainThread.getId()); } |
1.7.2 [2]线程优先级
Thread中定义三个静态字段表示优先级
优先级字段 |
值 |
MAX_PRIORITY |
10 |
MIN_PRIORITY |
1 |
NORM_PRIORITY |
5 |
MyThread02 t1 = new MyThread02("t1"); t1.setPriority(6); t1.start();
MyThread02 t2 = new MyThread02("t2"); t2.setPriority(Thread.MAX_PRIORITY); t2.start(); |
结论
[1]优先级高不一定先运行。
[2]优先级高说明线程有可能被最先调度
1.7.3 [3]测试线程的活动状态
使用Thread类中的isAlive()方法,判断线程的状态
活动状态的界定:[调用start后,未死亡]
public static void main(String[] args) { MyThread03 t1 = new MyThread03(); System.out.println(t1.isAlive()); t1.start();
System.out.println(t1.isAlive()); } |
1.7.4 [4]线程的强制执行
Join方法
调用该方法的线程强制执行,其它线程处于阻塞状态,该线程执行完毕后,其它线程再执行
=> 其他线程阻塞,直到该线程执行完成。
public static void main(String[] args) {
MyThread04 t1 = new MyThread04("t1"); t1.start();
for (int i = 0; i < 5; i++) { if(i == 3) { try { t1.join(); } catch (InterruptedException e) { e.printStackTrace(); } }else { System.out.println(Thread.currentThread().getName() + "=>" + i); } } } |
1.7.5 [5]线程的休眠
1.7.6 [6]线程的礼让
public static void main(String[] args) {
MyThread06 t1 = new MyThread06(); t1.start();
for (int i = 0; i <5; i++) { if(i==3) { // 线程礼让 Thread.yield(); }else { System.out.println(Thread.currentThread().getName()+"=>"+i); } } } |
Thread-0->0 main=>0 Thread-0->1 main=>1 Thread-0->2 main=>2 ----àmain线程礼让一次 Thread-0->3 -----à主线程礼让一次,礼让给t1执行。 main=>4 Thread-0->4
|
注意:
[1]礼让的线程进入就绪状态。
[2]线程礼让完成后,各个线程又开始抢占CPU。
其中的一种特殊情况
main=>0 Thread-0->0 Thread-0->1 main=>1 main=>2 -----à main开始礼让 main=>4 Thread-0->2 Thread-0->3 Thread-0->4 |
API解析
A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore this hint. |
1.7.7 [7]线程中断
public class MyThread07 extends Thread{ @Override public void run() { System.out.println("[1] 线性开始执行"); for (int i = 0; i < 5; i++) { try { Thread.sleep(1000); System.out.println("[2] 线程休眠正常结束"); } catch (InterruptedException e) { System.out.println("[3] 线程被中断"); break; // e.printStackTrace(); } }
System.out.println("[4]线程结束"); } } |
public class Test07Interrupt { public static void main(String[] args) {
MyThread07 t1 = new MyThread07(); t1.start();
try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
t1.interrupt(); } }
|
总结
[1]终止线程的方法stop已经不建议使用,容易导致死锁。
[2]一般可以通过中断线程的方法iterrupt中断线程的执行,让线程提前结束。提前让线程结束是一种正常的结束。
1.8 线程安全
多线程虽然提高了CPU的利用率,但是导致严重的数据错乱的问题。
窗口1还剩5张票 窗口2还剩5张票 窗口2还剩3张票 窗口2还剩2张票 窗口1还剩4张票 窗口2还剩1张票 |
同步的概念
同步让一个逻辑单元执行完成。例如买票系统中把
if(ticket > 0) { System.out.println(this.getName()+"还剩"+ticket+"张票"); ticket--; } |
定义成逻辑单元(一个整体)。
逻辑单元要么都执行,要么都不执行
如何保证?è 引入同步的概念保证逻辑单元的统一执行。
1.8.1 [1]同步代码块
// 同步代码块 // mutex(互斥元,互斥体;互斥量) ==> 互斥锁 synchronized (mutex) { if(ticket > 0) { System.out.println(this.getName()+"还剩"+ticket+"张票"); ticket--; } } |
[1]把逻辑单元放到同步代码块中
[2]给同步代码块加互斥锁
互斥锁一定要是一个对象;互斥锁最好要选择共享资源作为互斥锁
1.8.2 [2]同步方法
当逻辑单元过于复杂时,可以选择使用同步方法,使用synchronized修饰
public class MyRun implements Runnable{
private int ticket = 10;
@Override public void run() { for(int i=0;i<20;i++) { this.buyTicket(); } }
public synchronized void buyTicket() { if(ticket>0) {
System.out.println(Thread.currentThread().getName()+"还剩"+ticket+"张票");
try { // 模拟买票的过程 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
ticket--; } }
} |
同步总结
1.8.3 死锁(B)
同步保证多线程访问共享资源时数据不会错乱,但是过多的同步会导致死锁。
当线程t1需要访问A资源,把A加锁,同时还需要B资源,t1也将B资源加锁,而B资源被线程t2加锁,而t2又需要A资源,结果导致t1,t2陷入相互等待的过程。
public class MyThread01 extends Thread{
private Object A; private Object B;
public MyThread01(Object a,Object b) { super(); this.A = a; this.B = b; }
@Override public void run() {
synchronized (A) {
System.out.println("t1用于A,然后申请B资源");
synchronized (B) { System.out.println("t1用于A,申请到B资源"); } }
System.out.println("t1正常结束..."); } } |
public class MyThread02 extends Thread{
private Object A; private Object B;
public MyThread02(Object a,Object b) { super(); this.A = a; this.B = b; }
@Override public void run() {
synchronized (B) {
System.out.println("t2拥有B资源,然后申请A资源"); synchronized (A) { System.out.println("t2拥有B资源,申请到A资源"); } }
System.out.println("t2正常结束..."); } } |
public class Test { public static void main(String[] args) {
// 创建两个资源A,B Object A = new Object(); Object B = new Object();
MyThread01 t1 = new MyThread01(A, B); //MyThread01 t2 = new MyThread01(A, B); MyThread02 t2 = new MyThread02(A, B);
t1.start(); t2.start(); } } |
死锁一般情况下表示互相等待,是程序运行时出现的一种问题!
实际案例:买饭
1.9 生产者和消费者问题
package sxt06.thread07;
public class GoodsStore {
private String brand; private String name;
// 声明一个标识,标识仓库中是否有商品 private boolean hasGoods = false;
public String getBrand() { return brand; }
public void setBrand(String brand) { this.brand = brand; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
/** * 生产者生产商品 * @param brand * @param name */ public synchronized void push(String brand, String name) {
if(hasGoods) { try { // 当前线程等待 super.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }
this.setBrand(brand);
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
this.setName(name); System.out.println("生产了-->"+brand+name);
hasGoods = true; super.notify(); }
/** * 消费者取商品 */ public synchronized void get() {
if(!hasGoods) { try { super.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("消费了<--"+brand+name);
hasGoods = false; super.notify(); } } |
注意:
以上方法都只能在同步方法或者同步代码块中使用,否则会抛出异常
总结:
在线程间通信过程中,要解决两个核心问题
[1]线程同步问题。把程序的逻辑单元放到同步代码块和同步方法中
[2]线程间通信问题。wait/notify