多线程-Multithreading
- 多线程的创建
- 方式一:继承于Thread类
- 创建一个继承于Thread类的子类
- 重写Thread类的run()
- 创建Thread类的子类的对象
- 通过此对象调用start()
- 方式一:继承于Thread类
1 | package Intermediate.Thread; |
1 | package Intermediate.Thread.exer; |
Thread类的有关基本方法
1 | - void start()//启动线程,并执行对象的run()方法 |
1 | package Intermediate.Thread; |
线程的优先级
- 线程的优先等级
- MAX_PRIORITY:10
- MIN_PRIORITY:1
- NORM_PRIORITY:5
- 涉及的方法
1 | getPriority()//返回线程优先值 |
说明
- 线程创建时继承父线程的优先级
- 低优先级只是获得调度的概率低,并非一定是在高优先级线程只有才被调用
高优先级的线程要抢占低优先级线程cpu的执行权。优先级比较高的是高概率被cpu执行到,但不是优先级高的就一定先被执行完毕后再执行低优先级
继承Thread的方式
- 例子:创建三个窗口卖票,总票数为100张
1 | package Intermediate.Thread; |
- 存在线程安全问题待解决
创建多线程的方式二:实现Runnable接口
1 | package Intermediate.Thread; |
- 用Runnable接口实现三个窗口卖票实验:
1 | package Intermediate.Thread; |
- 存在线程安全问题待解决
比较创建线程的两种方式
开发中优先选择实现Runnable接口的方式
原因
- 实现的方式没有类的单继承的局限性
- 实现的方式更适合来处理多个线程有共享数据的情况
联系:
1 | public class Thread implements Runnable |
- 相同点:两种方式都需要重写run()方法,将线程要执行的逻辑声明在run()中
线程的分类
- 守护线程
- 非守护线程(用户线程)
线程的生命周期
说明:
声明周期关注两个概念:状态、相应的方法
关注:状态a–>状态b:那些方法执行了(回调方法)
某个方法主动调用:状态a–>状态b
阻塞:临时状态,不可以作为最终状态
最终状态:线程死亡
线程的同步
- 问题的提出
- 多个线程的执行的不确定性引起结果的不稳定
- 多个线程对账本的共享,会造成操作的不完整性,会破坏数据
问题:卖票过程中,出现了重票、错票–>出现了现成的安全问题
问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票
如何解决:当一个线程a在操作共享数据的时候,其他线程不能参与进来。直到线程a操作完之后其他线程才能参与进来,这种情况即使线程a出现了阻塞,也不能被改变
在Java中,我们通过同步机制来解决线程的安全问题
最初的方式:
方式一:同步代码块
说明:
- 操作共享数据的代码,即为需要被同步的代码。不能包含代码多了,也不能包含代码少
- 共享数据:多个线程共同操作的变量。比如:ticket
- 同步监视器:俗称“锁”,任何一个类的对象都可以充当锁,要求多个线程必须要共用同一把锁.
- 补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器
- 说明:在继承Thread类创建多线程的方式中,慎用this充当同步监视器
- ```java
synchronized(同步监视器){//需要被同步的代码 }
class MyThreadWindowRunnable implements Runnable{
}private int ticket = 100; Object obj = new Object(); @Override public void run() { while (true){ //synchronized(this) synchronized(obj){ if (ticket>0){ System.out.println(Thread.currentThread().getName()+":买了一张票,票号为:"+ticket); ticket--; }else { break; } } } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
* 方式二:同步方式
* 使用同步代码块解决继承Thread类的方式的线程安全问题
* 同步监视器可以使用当前类充当,如:windows.class
* ```java
class Window3 extends Thread{
private static int ticket = 100;
private static Object obj = new Object();
@Override
public void run() {
while (true){
//synchronized(obj){
synchronized(WindowTest3.class){//Class clazz = Window.class();
if (ticket>0){
System.out.println(getName()+":卖了一张票,票号为:"+ticket);
ticket--;
}else {
break;
}
}
}
}
}
public class WindowTest3 {
public static void main(String[] args) {
Window3 w1 = new Window3();
Window3 w2 = new Window3();
Window3 w3 = new Window3();
w1.setName("窗口一");
w2.setName("窗口二");
w3.setName("窗口三");
w1.start();
w2.start();
w3.start();
}
}
```java
package Intermediate.Thread;/**
使用同步代码块解决继承Thread类的方式的线程安全问题
/
class Window5 extends Thread{
private static int ticket = 100;
private static Object obj = new Object();
@Override
public void run() {while (true){ show(); }
}
public static synchronized void show(){//同步监视器:window5.classif (ticket>0){ System.out.println(Thread.currentThread().getName()+":卖了一张票,票号为:"+ticket); ticket--; }
}
}
public class WindowTest5 {
public static void main(String[] args) { Window5 w1 = new Window5(); Window5 w2 = new Window5(); Window5 w3 = new Window5(); w1.setName("窗口一"); w2.setName("窗口二"); w3.setName("窗口三"); w1.start(); w2.start(); w3.start(); }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
* 使用同步方式解决实现Runnable接口的线程安全问题
* ```java
class MyThreadWindowRunnable implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (true){
synchronized(this){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+":买了一张票,票号为:"+ticket);
ticket--;
}else {
break;
}
}
}
}
}
public class WindowTest2 {
public static void main(String[] args) {
MyThreadWindowRunnable myThreadWindowRunnable = new MyThreadWindowRunnable();
Thread t1 = new Thread(myThreadWindowRunnable);
Thread t2 = new Thread(myThreadWindowRunnable);
Thread t3 = new Thread(myThreadWindowRunnable);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
```java
class MyThreadWindowTest4 implements Runnable{private int ticket = 100; @Override public void run() { show(); } public synchronized void show(){//同步监视器:this while (true){ if (ticket>0){ System.out.println(Thread.currentThread().getName()+":买了一张票,票号为:"+ticket); ticket--; }else { break; } } }
}
public class WindowTest4 {
public static void main(String[] args) { MyThreadWindowTest4 myThreadWindowTest4 = new MyThreadWindowTest4(); Thread t1 = new Thread(myThreadWindowTest4); Thread t2 = new Thread(myThreadWindowTest4); Thread t3 = new Thread(myThreadWindowTest4); t1.setName("窗口一"); t2.setName("窗口二"); t3.setName("窗口三"); t1.start(); t2.start(); t3.start(); }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
* 好处:同步的方式解决了线程的安全问题
* 局限性:操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低
* 关于同步方法的总结:
1. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明
2. 非静态的同步方法,同步监视器是:this
3. 静态的同步方法,同步监视器是:当前类本身
## 使用同步机制将单例模式中的懒汉式改写为线程安全的
```java
public class BankTest {
}
class Bank{
private Bank(){
}
private static Bank instance = null;
public static synchronized Bank getBank(){
if (instance == null){
instance = new Bank();
return instance;
}
return instance;
}
}
1 | public class BankTest { |
线程死锁的问题
- 死锁
- 不同的线程分别占用对方需要的同步资源不放弃,都在等待对象放弃自己需要的同步资源,就形成了线程的死锁
- 出现死锁后,不会出现异常,不会出现提示,只是所有线程都处于阻塞状态,无法继续
- 解决方法
- 专门的算法、原则
- 尽量减少同步资源的定义
- 尽量避免嵌套同步
- 死锁的演示
1 | public class ThreadDeadLock { |
LOCK(锁)
- 从JDK5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当
- java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
- ReentrantLock类实现了Lock,它拥有与synchronized想用的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
1 |
|
线程的通信
- 涉及到三个方法
- wait()方法:一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
- notify()方法:一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程被wait,就唤醒优先级高的那个
- notifyAll()方法:一旦执行此方法,就会唤醒所有被wait的线程
- 说明:
- wait()、notify()、notifyAll()三个方法都必须使用在同步代码块或同步方法中
- wait()、notify()、notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。否则会出现IllegalMonitorStateException异常。
1 | class Number implements Runnable{ |
- 面试题:
- sleep()和wait()的异同?
- 相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态
- 不同点:1.两个方法声明的位置不同:Thread类中声明sleep()方法,Object类中声明wait()
- 调用的范围不同:sleep()可以在任何需要的场景下调用。wait()只能用在同步代码块或同步方法中
- 关于是否释放同步监视器的问题:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,而wait()会释放锁
- sleep()和wait()的异同?
- 小结
- 会释放锁的操作:
- 当前线程的同步方法、同步代码块执行结束
- 当前线程在同步代码块、同步方法中遇到break、return终止了该代码
- 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
- 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁
- 不会释放锁的操作:
- 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行
- 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)
- 应尽量避免使用suspend()和resume()来控制线程
- 会释放锁的操作:
JDK5.0新增线程创建方式
新增方式一:实现Callable接口
- 与Rannable相比,Callable功能更强大些
- 相比run()方法,可以有返回值
- 方法可以抛出异常
- 支持泛型的返回值
- 需要借助FutureTask类,比如获取返回结果
- Future接口
- 可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等
- FutureTask是Future接口的唯一的实现类
- FutureTask同时实现了Runnable,Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
- 如何理解实现Cabllable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
- call()可以有返回值
- call()可以抛出异常,被外面的操作捕获,获取异常的信息
- Callable是支持泛型的
- 与Rannable相比,Callable功能更强大些
新增方式二:使用线程池
- 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大
- 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁,实现重复利用。类似生活中的公共交通工具
- 好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理
- corePoolSize:核心池的大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
- ……..
- 线程池相关API
- JDK5.0起提供了线程池的相关API:ExecutorService和Executors
- ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
- void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
Future submit(Callable task):执行任务,有返回值,一般用来执行Callable - void shutdown():关闭连接池
- Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
- Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
- Executors.newFixedThreadPool(n):创建一个可重用固定线程数的线程池
- Executors.newSingleThreadExecutor():创建一个只有一个线程的线程池
- Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行
1 | 。/** |
- 面试题:创建多线程有几种方式?四种!
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口,可以借助FutureTask类返回结果
- 线程池