一、进程&线程
- 程序
是为完成特定的任务或需求,而用某种语言编写的一组指令的集合
- 进程
是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间 - 线程
进程的一个执行路径,共享一块内存,进程至少有一个线程
并发执行,是进程的进一步划分 - 线程调度
分时调度
轮流使用cpu,平均分配cpu使用率
抢占式调度
让优先级更高的先使用cpu,优先级相同的随机分配
java默认抢占式调度
java的调度方法
同优先级线程组成先进先出队列(先到先服务),使用时间片策略
对高优先级,使用优先调度的抢占式策略 - 线程优先级
MAX_PRIORITY:10
NORM_PRIORITY:5
MIN_PRIORITY:1
- 方法
getPriority|返回此线程的优先级|
setPriority(int newPriority)|更改此线程的优先级,默认5,1~10
etDaemon|守护线程,全为守护线程JVM退出|
对于CPU的一个核来说,同一时刻只能运行一个线程,多个线程轮流切换非常快。我们看做是同一时刻多个任务执行 多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的 使用率更高。
-
线程分类
守护线程
用来服务用户线程的
通过在start方法前调用thread.setDaemon(true)可以把一个用户线程变成守护线程
若JVM都是守护线程,当前JVM将退出
JVM垃圾回收就是典型的守护线程
用户线程 -
同步&异步
同步 排队执行,效率低 异步 同时执行,效率高但数据不安全 -
并发&并行
并发 指两个或多个事件在一个时间段内的发生 并行 指两个或多个事件在同一时刻执行
二、线程的创建方式
1.Thread类
概述
- java.lang.Thread
- 每个线程都是通过Thread对象的run()方法完成操作,run()方法体称为:线程体。
- 通过该Thread对象的start()方法来启动这个线程,而非直接调用run方法。
- 一个线程对象只能调用一次start,如果重复调用,抛出
lllegalThreadStateException异常
构造
- Thread():创建新的对象
- Thread(String threadName):创建线程并指定线程实例名
- Thread(Runnable target):指定线程的目标对象,它实现了runnable接口的run方法
- Thread(Runnable target,String name):创建新的Thread对象
方法
- start 启动线程
- getName 获得线程名
- setName 设置线程名
- currentThread 返回当前线程
- run 真正的线程任务的方法
- setDaemon() 参数为true,设置该线程为守护线程 必须在线程启动之前,否则报IllegalThreadStateException
- yield 线程让步 暂停当前正在执行的线程,把机会让给优先级相同或者更高的线程 若队列中没有同优先级的线程,忽略此方法
- join 线程阻塞,加入线程运行 当某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到join()方法加入的join线程执行完为止 低优先级的线程也可以获得执行
- sleep 线程休眠 令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队 抛出InterruptedException异常
- isAlive 判断线程是否存活
- interrupt 中断线程
- interrupted 测试当前线程是否已被中断
创建线程方式
创建 继承Thread类的类的对象,该类重写run方法
A a = new A();
a.start();
2.Runnable接口
创建方式
类实现Runnable接口,重写run方法,创建该类得到对象任务,new Thread(类对象)来得到线程对象
好处
- 通过创建任务,然后线程分配的方式来实现的多线程,更适合多个线程同时执行相同任务的情况
- 可以避免单继承所带来的局限
- 任务与线程本身是分离的,提高了程序的健壮性
- 后续学习的线程池技术,接收Runnable类型的任务,不接受Thread类的线程
3.Callable接口
创建方式
- 编写类实现Callable接口 , 实现call方法
class XXX implements Callable<T> {
@Override
public <T> call() throws Exception {
return T;
}
}
- 创建FutureTask对象 , 并传入第一步编写的Callable类对象
FutureTask<Integer> future = new FutureTask<>(callable);
- 通过Thread,启动线程
new Thread(future).start();
FutureTask对象
get
如果需要等待计算完成,然后检索其结果
get(long timeout, TimeUnit unit)
如果需要,最多等待计算完成的给定时间,然后检索其结果(如果可用)。
TimeUnit定义单位
isDone
判断子线程是否完成
cancel
传入true表示取消该方法,返回值也是boolean,返回false表示该任务已经完毕,无法取消了,返回true就是取消成功
4.Runnable与Callable
接口定义
接口定义
//Callable接口
public interface Callable<V> {
V call() throws Exception;
}
//Runnable接口
public interface Runnable {
public abstract void run();
}
相同点
都是接口
都可以编写多线程程序
都采用Thread.start()启动线程
不同点
Runnable没有返回值;Callable可以返回执行结果
Callable接口的call()允许抛出异常;Runnable的run()不能抛出
获取返回值
Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,
此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。
5.线程池
Executors一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作
三、线程安全&同步
1.同步
同步锁机制
在《Thinking in java》中,是这么说的:对于并发工作,
你需要某种方式来防止两个任务访问相同的资源(其实就是共享资源竞争)。
防止这种冲突做法:当资源被一个任务使用时,在其上加锁。第一个访问
某项资源的任务必须锁定这项资源,使其他任务在其解锁之前,就无法访问它了
,而在其被解锁之时,另一个任务就可以锁定并使用它了。
注意
必须确保使用同一资源的多个线程共用一把锁,非常重要。否则无法保证共享资源
的安全
一个线程类中所有静态方法共用同一把锁(类名.class),所有非静态方法共用
同一把锁(this),同步代码块(指定需谨慎)
同步的范围
1.明确哪些是多线程执行的代码
2.多个线程是否有共享的数据
3.明确多个线程运行代码中是否有多条语句操作共享数据
所有操作共享数据的这些语句都要放在同步范围中
释放锁操作
1.当前线程的同步方法、同步代码块执行结束
2.线程在执行中遇到break、return代码
3.出现未处理的Error、Exception,导致异常结束
4.执行了当前线程对象的wait方法,当前线程暂停,释放锁
join也会释放锁资源
不会释放锁操作
1.线程执行同步代码块或同步方法时,调用Thread.sleep()\Thread.yield()方法暂停当前线程执行
yeild()方法不释放锁资源的原因。 yeild()方法是暂停当前线程,让当前线程回到了可执行状态,但是不一定释放锁资源(可能释放锁资源,也可能不会释放锁资源)。被yeild()暂停的线程可能会回到可执行状态之后立即执行。因此可能不会释放锁资源。
2.线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)
应尽量避免使用suspend和resume来控制线程
2.synchronized
- 任何对象 都可作为锁对象,同一任务含有同一锁对象才会保证线程同步 同步代码块
- synchronized(obj){ }
- 锁自己指定,可以使this或者className.class 同步方法
- public synchronized void test(){ }
- 静态方法的锁是 className.class
- 非静态方法锁是this
3.死锁
不同线程分别占用对方需要同步的资源,不放弃,都在等待对方让步,形成线程死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法
继续
解决
- 专门的算法、原则
- 尽量减少同步资源的定义
- 尽量避免嵌套同步
4.Lock锁
JDK5.0开始,加入通过显示定义同步锁对象来实现同步。同步锁使用Lock对象充当
- java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。
- 锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
- ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义。比较常用ReentrantLock,可以显示加锁,释放锁
- 格式
//加锁
lock.lock();
try{
//代码
}finally {
//释放锁
lock.unlock();
}
5.synchronized与Lock锁比较
- Lock是显示锁(手动开启和释放锁),synchronized是隐式锁,出了作用域自动释放
- Lock只有代码块锁,synchronized有代码块和方法锁
- 使用Lock锁,JVM将花费较少时间来调度来调度线程,性能更好。并具有良好扩展性(提供更多的子类)
- 优先使用顺序 同步代码块(已经进入方法体,分配了相应资源)->同步方法(在方法体外)
6.线程通信
- wait():令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当前线程等待其他线程调用notify和notifyAll方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行
- notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待。
- noyifyAll():唤醒正在排队等候资源的所有线程结束等待
注意这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则报java.lang.lllegalMonitorstateException异常 因为这三个方法必须有锁对象调用,而任意对象都可作为synchronized的同步锁,因此这三个方法只能声明在Object类中
四、线程池
1.概述
- 背景 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间
- Executors一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作
2.好处
- 降低资源消耗。
- 提高响应速度。
- 提高线程的可管理性
3.四种线程池
(1)缓存线程池:newCachedThreadPool
- ExecutorService service = Executors.newCachedThreadPool();
- (长度无限制)
- 过程:
- 判断线程池是否存在空闲线程
- 存在则使用
- 不存在,则创建线程 并放入线程池, 然后使用
(2)定长线程池:newFixedThreadPoll
- ExecutorService service = Executors.newFixedThreadPool()
- (长度固定)
- 过程:
- 判断线程池是否存在空闲线程
- 存在则使用
- 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
- 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
(3)单线程线程池:newSingleThreadExecutor
- ExecutorService service = Executors.newSingleThreadExecutor()
- 1个
- 过程:
- 判断线程池 的那个线程 是否空闲
- 空闲则使用
- 不空闲,则等待 池中的单个线程空闲后 使用
(4)周期性任务定长线程池:newScheduledThreadPool
- ScheduledExecutorService service = Executors.newScheduledThreadPool(corePoolSize);
- 周期任务 定长线程池
- 过程
- 判断线程池是否存在空闲线程
- 存在则使用
- 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
- 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
- 周期性任务执行
- 定时任务
schedule(Callable callable,long delay, TimeUnit unit);
参数1command. runnable类型的任务
参数2delay. 时长数字
参数3unit. 时长数字的单位
- 周期性任务
scheduleAtFixedRate(Runnable command,long initialDelay, long period, TimeUnit unit);
参数1command. runnable类型的任务
参数2initialDelay. 时长数字(延迟执行的时长)
参数3period. 周期时长(每次执行的间隔时间)
参数4unit. 时长数字的单位
- 周期性任务
- 定时任务
- 注意生成对象为ScheduledExecutorService
4.常用参数
- corePoolSize:核心池的大小、
- maximumPoolSize:最大线程池数
- keepAliveTime:无任务时线程最多保持时间会终止
5.相关API
ExecutorService和Excutors
ExecutorService: 真正线程池接口。常见子类:ThreadPoolEcecutor Excutors: 工具类、线程池的工厂类,用于创建并返回不同类型的线程池 有四种线程池:newFixedThreadPool、newCachedThreadPool、newScheduledThreadPool、newSingleThreadPool