java多线程浅谈
多线程
多线程的概念
多线程是一种用于实现并发执行的计算机编程技术,这允许单个程序创建和管理多个线程,以同时执行多个任务或执行块。
- 并发:在同一时刻,有多个指令在单个CPU上交替执行
- 并行:在同一时刻,有多个指令在CPU上同时执行
线程的生命周期
- 新建(New):创建后尚未启动的线程处于这个状态
- 就绪(Runnable):调用start()方法后,线程进入就绪状态,此时线程只是开辟了内存空间,正在等待被线程调度器选中获得对CPU的使用权
- 运行(Running):线程获得CPU权限进行执行
- 阻塞(Blocked):线程在等待监视器锁(即等待 synchronized 锁)时,会进入阻塞状态
- 等待(WAITING):线程等待另一个线程通知或中断时,会进入等待状态。进入这个状态的线程需要等待其他线程调用
notify()
或notifyAll()
方法 - 超时等待(TIME_WAITING):线程在指定的等待时间内等待另一个线程的通知时,会进入超时等待状态。这通常发生在调用了带有指定等待时间的
wait(long timeout)
、join(long millis)
、sleep(long millis)
或LockSupport.parkNanos()
等方法时 - 死亡(Terminated):线程执行结束或者因为异常退出的状态
多线程的实现方式
- 继承Thread类的方式进行实现
- 自己定义一个类继承Thread
- 重写run方法
- 创建子类对象,并启动线程
1 | //定义一个类继承Thread |
- 实现Runnable接口的方式进行实现
- 自己定义一个类实现Runnable接口
- 重写run方法
- 创建自己定义的类的对象
- 创建一个Thread类的对象,并开启线程
1 | //定义一个类实现Runnable接口 |
- 利用Callable接口和Future接口方式实现
- 特点:可以获取多线程运行的结果
- 自己定义一个类实现Callable接口
- 重写call(有返回值,表示多线程运行的结果)
- 创建自己定义的类的对象(表示多线程要执行的任务)
- 创建FutureTask的对象(管理多线程运行的结果)
- 创建Thread类的对象,并启动(表示线程)
1 | public class MyCallable implements Callable<Integer> { |
三种实现方式的比较
优点 | 缺点 | |
---|---|---|
继承Thread类 | 编程比较简单,可以直接使用Thread类中的方法 | 可以扩展性较差,不能再继承其他类 |
实现Runnable接口 | 扩展性强,实现该接口的同时还可以继承其他的类 | 编程相对复杂,不能直接使用Thread类中的方法 |
实现Callable接口 | 扩展性强,实现该接口的同时还可以继承其他的类 | 编程相对复杂,不能直接使用Thread类中的方法 |
多线程中常用的成员方法
- 返回此线程的名称
String getName()
- 设置线程的名字(构造方法也可以设置名字)
void setName(String name)
- 获取当前线程的对象
当JVM虚拟机启动之后,会自动的启动多条线程,其中有一条线程就叫做main线程,它的作用就是去调用main方法,并执行里面的代码
static Thread currentThread()
- 让线程休眠指定的时间,单位为毫秒
static void sleep(long time)
- 设置线程的优先级
1 -> 最小
10 -> 最大
5 -> 默认
setPriority(int newPriority)
- 获取线程的优先级
final int getPriority()
- 设置守护线程
当其他的非守护线程完毕之后,守护线程回陆续结束
final void setDaemon(boolean on)
- 出让线程/礼让线程(礼让不一定会成功)
public static void yield()
- 插入线程/插队线程(插队一定会成功)
public static void join()
- 中断线程
public static void interrupt()
通知线程退出
可以通过使用变量来控制run方法退出的方式停止线程,也即通知方式
1 | public class MyThread extends Thread { |
等待唤醒机制
- 当前线程等待,直到被其他线程唤醒
void wait()
- 随即唤醒单个线程
void notify()
- 唤醒所有线程
void notifyAll()
线程同步机制
在多线程编程中,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。
同步具体方法-Synchronized
- 同步代码块
1 | synchronized(对象) {//得到对象的锁,才能操作同步代码 |
- 同步方法
1 | public synchronized void method(String name) { |
互斥锁
- 互斥锁用来保证共享数据操作的完整性。
- 每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
- 关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问
- 同步的局限性:导致程序的执行效率降低
- 同步方法(非静态的)的锁可以是this,也可以是其他对象(但一定是同一对象)
- 同步方法(静态的)的锁为当前类本身(当前类.class)
实现步骤
- 先分析上锁的代码
- 选择同步代码块或同步方法
- 要求多个线程的锁对象为同一个即可
死锁
多个线程都占用了对方的锁资源,但不肯相让,导致了死锁
释放锁
下列操作会释放锁:
- 当前线程的同步方法,同步代码块执行结束
- 当前线程在同步代码块,同步方法中遇到break,return
- 当前线程在同步代码块,同步方法中出现了未处理的Error或Exception,导致异常结束
- 当前线程在同步代码块,同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁
下列操作不会释放锁:
- 线程执行同步代码块或同步方法时,程序调用了Thread.sleep(),Thread.yield()方法暂停当前线程的执行,不会释放锁
线程池
线程池的主要核心原理:
创建一个池子,池子中是空的
提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可
但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待
- 创建一个没有上限的线程池
1 | public class MyRun implements Runnable { |
- 创建有上线的线程池
1 | public class MyRun implements Runnable { |