JAVA高级编程第八章
JAVA高级编程
文章目录
- JAVA高级编程
- 第八章 多线程
- Thread类
- 创建线程
- 继承Thread类
- 实现Runnable接口
- Lambda表达式
- 常用方法
- 线程状态
- 线程调度
- 优先级
- 休眠
- 强制运行
- 礼让
- 多线程共享数据
- 引发问题
- 同步方法
- 同步静态方法
- 同步代码块
- 线程安全
- 常见类型对比
- 常见类型对比
第八章 多线程
什么是多线程?
如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为“多线程”
多个线程交替占用CPU资源,而非真正的并行执行
多线程好处
- 充分利用CPU的资源
- 简化编程模型
- 带来良好的用户体验
Thread类
Java提供了java.lang.Thread类支持多线程编程
- main()方法即为主线程入口
- 产生其他子线程的线程
- 必须最后完成执行,因为它执行各种关闭动作
创建线程
继承Thread类
class MyThread extends Thread {@Overridepublic void run() {System.out.println("线程执行: " + Thread.currentThread().getName());}
}public class Main {public static void main(String[] args) {MyThread thread = new MyThread();thread.start(); // 启动线程}
}
MyThread t1 = new MyThread();MyThread t2 = new MyThread();t1.start();t2.start();
/**
结果如下:
线程执行: Thread-1
线程执行: Thread-1
线程执行: Thread-0 //多个线程交替执行,不是真正的“并行”
线程执行: Thread-1
线程执行: Thread-0 //线程每次执行时长由分配的CPU时间片长度决定
线程执行: Thread-0
线程执行: Thread-1
线程执行: Thread-0
线程执行: Thread-0
.....
*/
实现Runnable接口
class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("线程执行: " + Thread.currentThread().getName());}
}public class Main {public static void main(String[] args) {Thread thread = new Thread(new MyRunnable());thread.start();}
}
比较两种创建线程的方式:
继承Thread类
编写简单,可直接操作线程
适用于单继承
实现Runnable接口
避免单继承局限性
便于共享资源
Lambda表达式
public class Main {public static void main(String[] args) {Thread thread = new Thread(() -> {System.out.println("Lambda线程: " + Thread.currentThread().getName());});thread.start();}
}
常用方法
方法 | 说明 | 示例 | 注意事项 |
---|---|---|---|
start() | 启动新线程,JVM会调用该线程的run()方法 | thread.start(); | 只能调用一次,多次调用会抛出IllegalThreadStateException |
run() | 线程的执行体,包含线程要执行的代码 | public void run() {<br> // 线程代码<br>} | 直接调用run()不会启动新线程,而是在当前线程执行 |
sleep(long millis) | 使当前线程暂停执行指定的毫秒数 | Thread.sleep(1000); | 1. 会抛出InterruptedException 2. 不释放锁 3. 时间精度取决于系统计时器 |
join() | 等待该线程终止 | thread.join(); thread.join(1000); // 最多等待1秒 | 1. 会抛出InterruptedException 2. 常用于主线程等待子线程完成 |
interrupt() | 中断线程(设置中断标志位) | thread.interrupt(); | 1. 不会直接停止线程 2. 需要线程自己检查中断状态并响应 |
isAlive() | 测试线程是否处于活动状态(已启动但未终止) | if(thread.isAlive()) {...} | 返回true表示线程已启动且尚未完成run()方法 |
线程状态
线程调度
线程调度指按照特定机制为多个线程分配CPU的使用权
方 法 | 说 明 |
---|---|
void setPriority(int newPriority) | 更改线程的优先级 |
static void sleep(long millis) | 在指定的毫秒数内让当前正在执行的线程休眠 |
void join() | 等待该线程终止 |
static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
void interrupt() | 中断线程 |
boolean isAlive() | 测试线程是否处于活动状态 |
优先级
- 线程优先级由1~10表示,1最低,默认优先级为5
- 优先级高的线程获得CPU资源的概率较大
void setPriority(int newPriority)
MyRunnable mr= new MyRunnable();Thread t1 = new Thread(mr,"线程A");Thread t2 = new Thread(mr,"线程B");t1.setPriority(Thread.MAX_PRIORITY);t2.setPriority(Thread.MIN_PRIORITY);t1.start();t2.start();
/*
运行结果:
线程A(高优先级): 0
线程B(低优先级): 0
线程A(高优先级): 1
线程A(高优先级): 2
线程B(低优先级): 1
线程A(高优先级): 3
线程B(低优先级): 2
...
*/
setPriority(Thread.MAX_PRIORITY)
和setPriority(Thread.MIN_PRIORITY)
只是给线程调度器的建议- 不保证高优先级线程(线程A)的所有语句都会在低优先级线程(线程B)之前执行
- 也不保证高优先级线程一定会先获得资源
- 优先级只是影响概率,不是确定性保证
- 开始资源不"必定"归线程A所有
- 只是线程A有更高的概率被调度器选中
- 实际执行顺序还取决于操作系统调度器的具体实现
需要确定顺序时应使用同步机制:
- 使用锁(synchronized/Lock)
- 使用等待/通知机制(wait()/notify())
- 使用更高级的并发工具(CountDownLatch等)
休眠
让线程暂时睡眠指定时长,线程进入阻塞状态
睡眠时间过后线程会再进入可运行状态
static void sleep(long millis)
public void run(){for(int i=0;i<100;i++){System.out.println("线程执行: " + Thread.currentThread().getName());try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}}
强制运行
强制执行当前线程,join写在哪个线程,就阻塞谁
public final void join()
public final void join(long mills)
public final void join(long mills,int nanos)
millis:以毫秒为单位的等待时长
nanos:要等待的附加纳秒时长
需处理InterruptedException异常
void join()
//MyThread2
public void run(){for (int i=1;i<= 20;i++){System.out.println(Thread.currentThread().getName()+"线程执行:"+i);}}//Test(Main)MyThread2 t = new MyThread2();Thread temp = new Thread(t);temp.start();for (int i = 1; i <= 20; i++){if(i == 5){try{temp.join(); //阻塞主线程,子线程强制执行}catch (InterruptedException e){e.printStackTrace();}}System.out.println(Thread.currentThread().getName()+ "线程执行: " + i);}
礼让
- 暂停当前线程,允许其他具有相同优先级的线程获得运行机会
- 该线程处于就绪状态,不转为阻塞状态
只是提供一种可能,但是不能保证一定会实现礼让
static void yield()
public class MyThread implements Runnable{public void run(){for(int i=0;i<5;i++){System.out.println(Thread.currentThread().getName()+"正在运行:"+i);if(i==3){System.out.print("线程礼让:");Thread.yield(); } }}
}
多线程共享数据
引发问题
public void run() {while (true) {//省略代码:判断是否余票num++;count--;try {Thread.sleep(500); //模拟网络延时} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName()+ "抢到第" + num + "张票,剩余" + count + "张票!");}}
发现的问题:
存在多人抢到一张票的情况
…
/*
Thread-2抢到第1张票,剩余9张票!
Thread-0抢到第1张票,剩余9张票!
Thread-1抢到第1张票,剩余9张票!
Thread-2抢到第2张票,剩余8张票!
Thread-0抢到第2张票,剩余8张票!
Thread-1抢到第2张票,剩余8张票!
Thread-2抢到第3张票,剩余7张票!
Thread-0抢到第3张票,剩余7张票!
Thread-1抢到第3张票,剩余7张票!
Thread-0抢到第4张票,剩余6张票!
Thread-1抢到第4张票,剩余6张票!
Thread-2抢到第4张票,剩余6张票!
*/
同步方法
使用synchronized修饰的方法控制对类成员变量的访问
访问修饰符 synchronized 返回类型 方法名(参数列表){……}
或者
synchronized 访问修饰符 返回类型 方法名(参数列表){……}
同步静态方法
public static synchronized void staticMethod() {// 同步代码块
}
同步代码块
public void method() {// 非同步代码synchronized(lockObject) {// 同步代码块}// 非同步代码
}
多个并发线程访问同一资源的同步代码块时
- 同一时刻只能有一个线程进入synchronized(this)同步代码块
- 当一个线程访问一个synchronized(this)同步代码块时,其他synchronized(this)同步代码块同样被锁定
- 当一个线程访问一个synchronized(this)同步代码块时,其他线程可以访问该资源的非synchronized(this)同步代码
线程安全
查看ArrayList类的add()方法定义
public boolean add(E e) {ensureCapacityInternal(size + 1); elementData[size++] = e;return true;
}
ArrayList类的add()方法为非同步方法
当多个线程向同一个ArrayList对象添加数据时,可能出现数据不一致问题
方法是否同步 | 效率比较 | 适合场景 | |
---|---|---|---|
线程安全 | 是 | 低 | 多线程并发共享资源 |
非线程安全 | 否 | 高 | 单线程 |
常见类型对比
Hashtable && HashMap
Hashtable
- 继承关系 —> 实现了Map接口,Hashtable继承Dictionary类
- 线程安全,效率较低
- 线程安全,效率较低
HashMap
- 继承关系 —> 实现了Map接口,继承了AbstractMap
- 非线程安全,效率高
- 键和值都允许为null
StringBuffer && StringBuilder
- 前者线程安全,后者非线程安全
----- |
| 线程安全 | 是 | 低 | 多线程并发共享资源 |
| 非线程安全 | 否 | 高 | 单线程 |
常见类型对比
Hashtable && HashMap
Hashtable
- 继承关系 —> 实现了Map接口,Hashtable继承Dictionary类
- 线程安全,效率较低
- 线程安全,效率较低
HashMap
- 继承关系 —> 实现了Map接口,继承了AbstractMap
- 非线程安全,效率高
- 键和值都允许为null
StringBuffer && StringBuilder
- 前者线程安全,后者非线程安全