多线程编程
线程和进程的概念
进程
进程是执行程序的一个执行过程,是一个动态的概念,系统资源分配的单位
程序是代码,静态概念
一个进程至少包含一个线程(主线程)
线程
线程是CPU调度和执行的单位
线程不一定调用就立即执行,由CPU调度执行
线程创建
继承Thread类
public class MyThread extends Thread(){
@override
public void run(){
// 方法体
}
// 线程调用
public static void main(String args[]){
MyThread thread = new MyThread();
thread.start();
}
}
实现Runnable接口
public class MyThread implements Runnable{
@override
public void run(){
// 方法体
}
// 线程调用
public static void main(String args[]){
Thread thread = new Thread(new MyThread());
thread.start();
// 或者使用 匿名内部类
new Thread(new MyThread(){
@override
public void run(){
// 方法体
}
}).start();
// 或者使用 Lambda 表达式
new Thread(()->{ // 方法体 }).start();
}
}
实现Callable接口
public class MyThread implements Callable<Boolean>{
@override
public Boolean call(){
// 方法体
return true;
}
// 线程调用
MyThread thread = new MyThread();
// 开启服务 线程池
ExecutorService ser = Executors.newFixedThreadPool(1);
// 提交执行
Future<Boolean> result = ser.submit(thread);
// 获取结果 会抛出异常 ExecutionException InterruptedException
boolean r1 = result.get();
//关闭服务
ser.shutdownNow();
}
线程状态
- 创建状态 new 新建一个线程
- 就绪状态 调用 start 方法启动线程
- 阻塞状态 运行时等待用户输入,线程休眠 sleep wait
- 运行状态 得到CPU资源时
- 死亡状态 线程自然执行完毕,外部干涉终止
方法 | 说明 |
---|---|
setPriority(int i) | 更改线程优先级 |
getPriotity() | 获得线程优先级 |
currentThread().getName() | 获得当前线程的名字 |
static void sleep(long millis) | 休眠当前线程,毫秒数 |
void join() | 等待该线程终止后再执行其他线程,其他线程阻塞 插队 |
static void yield() | 线程礼让 |
void interrupt() | 中断线程 尽量别用 |
boolean isAlive() | 是否处于运行状态 |
stop() | 终止线程 |
destroy() | 终止线程 |
setDaemon(true) | 开启守护线程,默认false |
线程休眠
sleep方法
- 指定当前线程阻塞的毫秒数
- 存在 InterruptedException 异常
- sleep时间到达后线程进入就绪状态
- 可以模拟网络延时,倒计时等
- 每一个对象都有一个锁,sleep不会释放锁
线程状态观测
Thread.State 线程状态 常量
- NEW 新建未启动线程
- RUNNABLE 在java虚拟机中执行的线程处于此状态
- BLOCKED 被阻塞等待监视器锁定的线程处于此状态
- WAITING 正在等待另一个线程执行特定动作的线程处于此状态
- TIMED_WAITING 正在等待另一个线程自行动作达到指定等待时间的线程处于此状态
- TERMINATER 已经退出的线程处于此状态
一个线程可以在给定时间点处于一个状态。这些状态是不反应任何操作系统线程状态的虚拟机状态
线程优先级
优先级范围 1~10 常量
Thread.MIN_PRIORITY =1
Thread.MAX_PRIORITY=10
Thread.NORM_PRIORITY=5
主线程优先级为 5 但是最先启动
小于 1 或大于 10 会直接出现IllegalArgumentException异常 参数异常
但是并不是优先级最高的优先执行,由CPU决定 但是多数是这样,出现这样的情况叫性能倒置
守护线程
setDaemon(true),开启守护线程
线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕 gc 后台记录操作日志,内存监控等
线程同步
多线程操作同一个资源
队列和锁
synchronized
当一个线程获得对象的排他锁,独占资源,其他线程必须等待,使用后释放锁即可,可能存在以下问题
- 一个线程尺有所会导致其他所有需要此锁的线程挂起
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题
线程不安全:
- 没有队列会取到重复值
- 没有锁会出现负数,临界资源问题
同步方法和同步代码块
- 使用sychronized 修饰方法
- 使用sychronized修饰对象或者资源
sychronized方法控制对 对象 的访问,每个对象对应一把锁,每个sychronized 方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,知道方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
缺陷:若将一个大的方法申明为synchronized 将会影响效率
// 同步方法
public sychronized void method(){
// 同步块
synchronized(obj){}
}
synchronized(obj){}
同步块称之为同步监视器
- obj 可以是任何对象,但是推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class
- 同步监视器的执行过程
- 第一个线程访问。锁定同步监视器,执行其中的方法
- 第二个线程访问,发现同步监视器被锁定,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
JUC java.util.current包下面的线程同步类
Lock 锁
JDK5.0开始 同步锁使用Lock对象充当
java.util.concurrent.locks.Look接口是控制多个线程对共享资源进行访问的工具
锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享之前应先获得Lock对象
ReentrantLock(可重入锁)类实现了Lock,它拥有与synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁,释放锁
//定义lock锁
private final ReentrantLock lock = new ReentrantLock();
try{
// 加锁
lock.lock();
// 代码
}catch(InterruptedException e){
//建议使用try catch 但是可以不使用
}finaly{
// 释放锁
lock.unlock()
}
死锁
某一个同步块,同时拥有两个以上对象的锁时,就可能会发生死锁问题
产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞是,对已获得资源保持不放
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
线程协作
生产者消费者模式
方法名 | 作用 |
---|---|
wait() | 表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁 |
wait(long timeout) | 指定等待的毫秒数 |
notify() | 唤醒一个处于等待状态的线程 |
notifyAll() | 唤醒同一个对象上所有调用wait()方法的线程,优先级高的线程优先调度 |
注意:均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常IllegaMonitorStateException异常
-
管程法
使用缓冲区
-
信号灯法
使用标志位
线程池
经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响较大
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。避免频繁创建销毁、重复利用。类似生活中的交通工具
- 提高了响应速度(减少创建新线程的时间)
- 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
- 便于线程管理
- corePoolSize:核心池的大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务最多保持多长时间后会终止
JDK5.0提供了线程池相关API
ExecutorService Executors
ExecutorService:真正的线程池接口,常见的自雷ThreadPoolExecutor
- void execute(Runnable command):执行任务/命令没有返回值,一般用于执行Runnable
Future submit(Callable task):执行任务,有返回值,一般用来执行Callable - void shutdown():关闭连接池
executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
未更新完