一、进程&线程
- 程序
是为完成特定的任务或需求,而用某种语言编写的一组指令的集合 - 进程
是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间
3. 线程
进程的一个执行路径,共享一块内存,进程至少有一个线程
并发执行,是进程的进一步划分
4. 线程调度
分时调度
轮流使用cpu,平均分配cpu使用率
抢占式调度
让优先级更高的先使用cpu,优先级相同的随机分配
java默认抢占式调度
java的调度方法
同优先级线程组成先进先出队列(先到先服务),使用时间片策略
对高优先级,使用优先调度的抢占式策略
5. 线程优先级
**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垃圾回收就是典型的守护线程
用户线程
7. 同步&异步
1 | 复制代码 同步 排队执行,效率低 |
- 并发&并行
1 | markdown复制代码 并发 |
二、线程的创建方式
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方法
1 | bash复制代码 A a = new A(); |
2.Runnable接口
创建方式
类实现Runnable接口,重写run方法,创建该类得到对象任务,new Thread(类对象)来得到线程对象
好处
- 通过创建任务,然后线程分配的方式来实现的多线程,更适合多个线程同时执行相同任务的情况
- 可以避免单继承所带来的局限
- 任务与线程本身是分离的,提高了程序的健壮性
- 后续学习的线程池技术,接收Runnable类型的任务,不接受Thread类的线程
3.Callable接口
创建方式
- 编写类实现Callable接口 , 实现call方法
1 | java复制代码class XXX implements Callable<T> { |
- 创建FutureTask对象 , 并传入第一步编写的Callable类对象
1 | java复制代码FutureTask<Integer> future = new FutureTask<>(callable); |
- 通过Thread,启动线程
1 | java复制代码new Thread(future).start(); |
FutureTask对象
1 | arduino复制代码get |
4.Runnable与Callable
接口定义
1 | csharp复制代码接口定义 |
相同点
1 | scss复制代码都是接口 |
不同点
1 | scss复制代码Runnable没有返回值;Callable可以返回执行结果 |
获取返回值
1 | csharp复制代码Callalble接口支持返回执行结果,需要调用FutureTask.get()得到, |
5.线程池
Executors一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作
三、线程安全&同步
1.同步
同步锁机制
1 | markdown复制代码 在《Thinking in java》中,是这么说的:对于并发工作, |
注意
1 | kotlin复制代码 必须确保使用同一资源的多个线程共用一把锁,非常重要。否则无法保证共享资源 |
同步的范围
1 | 复制代码1.明确哪些是多线程执行的代码 |
所有操作共享数据的这些语句都要放在同步范围中
释放锁操作
1 | bash复制代码1.当前线程的同步方法、同步代码块执行结束 |
不会释放锁操作
1 | scss复制代码1.线程执行同步代码块或同步方法时,调用Thread.sleep()\Thread.yield()方法暂停当前线程执行 |
应尽量避免使用suspend和resume来控制线程
2.synchronized
- 任何对象 都可作为锁对象,同一任务含有同一锁对象才会保证线程同步
同步代码块 - synchronized(obj){ }
- 锁自己指定,可以使this或者className.class
同步方法 - public synchronized void test(){ }
- 静态方法的锁是 className.class
- 非静态方法锁是this
3.死锁
1 | 复制代码不同线程分别占用对方需要同步的资源,不放弃,都在等待对方让步,形成线程死锁 |
解决
- 专门的算法、原则
- 尽量减少同步资源的定义
- 尽量避免嵌套同步
4.Lock锁
JDK5.0开始,加入通过显示定义同步锁对象来实现同步。同步锁使用Lock对象充当
- java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。
- 锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
- ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义。比较常用ReentrantLock,可以显示加锁,释放锁
- 格式
1 | java复制代码 //加锁 |
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
本文转载自: 掘金