本文主要是介绍3.5 多线程,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
5.1 线程与进程
5.1.1 概念
- 进程:一个内存中运行的应用程序,每个进程都有一个独立的内存空间
- 线程:进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个线程
5.1.2 线程调度
- java采用抢占式调度。多线程不能提高运行速度,可以提高运行效率,提高CPU使用率。
5.1.3 同步与异步
- 同步:排队执行,效率低但是安全
- 异步:同时执行,效率高但是不安全
5.1.4 并发与并行(面试题)
- 并发:两个或多个事件在同一个时间段内发生
- 并行:两个或多个事件在同一时刻发生
5.2 多线程
5.2.1 实现途径
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口(较少使用)
// 1. 编写类实现Callable接口 , 实现call方法
class XXX implements Callable<T> {
@Override public <T> call() throws Exception {
return T;
}
}
// 2. 创建FutureTask对象 , 并传入第一步编写的Callable类对象
FutureTask<Integer> future = new FutureTask<>(callable);
// 3. 通过Thread,启动线程
new Thread(future).start();
Runnable与Callable异同
- 相同点:
- 都是接口
- 都可以编写多线程
- 都采用Thread.start()启动线程
- 不同点:
- Runnable没有返回值;Callable可以返回执行结果
- Callable接口的call()允许抛出异常;Runnable的run()不能抛出
Callable获取返回值
- 调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。
5.2.1.1 Runnable比Thread的优势
- 更适合多个线程执行相同任务的情况
- 避免单继承带来的局限性
- 任务与线程分离,提高程序健壮性
- 线程池技术只接受Runnable类型任务,不接受Thread线程
5.2.2 线程中断
5.2.3 进程同步方法
- 法一:同步代码块
- run()方法内通过将同一对象传入synchronized(),可调用this关键字或提前实例化一个对象
- 法二:同步方法
- 方法权限修饰符后添加synchronized,通过同一任务不同线程的start方法调用
* 若添加了同步代码块和同步方法,若某一线程抢到资源,则其他线程不能执行同步方法中的内容
- 法三:显式锁
- run()方法前创建锁对象 Lock l=new ReentrantLock();
- 上锁:l.lock(); 解锁:l.unlock();
5.2.3 显式锁和隐式锁区别
- 两者出身不同
- synchronized是Java中的关键字,由JVM维护,是JVM层面的锁;
- lock是JDK5之后才出现的具体的类,使用Lock是调用对应的API,是API层面的锁。
- 使用方式不同
- synchronized隐式锁;lock是显式锁
- 显式锁和隐式锁的区别在于:使用显式锁的时候,使用者需要手动去获取和释放锁。如果没有释放锁,就可能出现死锁的现象
- 加锁的时候是否可以公平
- Sync:非公平锁
- lock:默认是非公平锁。在其构造方法的时候可以传入Boolean值。true:公平锁
(原文链接:https://zhuanlan.zhihu.com/p/131286865)
5.2.4 线程阻塞
- 在某一时刻某一个线程在运行一段代码的时候,这时候另一个线程也需要运行,但是在运行过程中的那个线程执行完成之前,另一个线程是无法获取到CPU执行权的,这个时候就会造成线程阻塞。
5.2.5 公平锁和非公平锁
- 公平锁:保证获取锁的线程按照先来后到的顺序
- 非公平锁:获取锁的顺序和申请锁的顺序不一定一致
5.2.6 死锁
- 两个或两个以上的线程互相持有对方所需要的资源,由于synchronized的特性,一个线程持有一个资源,或者说获得一个锁,在该线程释放这个锁之前,其它线程是获取不到这个锁的,而且会一直死等下去,因此这便造成了死锁。
5.2.7 线程六种状态
- 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
- 运行(RUNNABLE):Java线程中将就绪(ready)和**运行中(running)****两种状态笼统的称为“运行”。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
- 阻塞(BLOCKED):在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。
- 等待(WAITING):处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。(wait方法)
- 超时等待(TIMED_WAITING):处于这种状态的线程不会被分配CPU执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。(sleep方法)
- 终止(TERMINATED):表示该线程已经执行完毕。
5.2.8 线程池(较少使用)
5.2.8.1 分类
- 缓存线程池
- 定长线程池
- 单线程线程池
- 周期性任务定长线程池
5.2.8.2 应用
/*** 缓存线程池.
* (长度无限制)
* 执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在,则创建线程 并放入线程池, 然后使用
*/
ExecutorService service = Executors.newCachedThreadPool();
//向线程池中 加入 新的任务
service.execute(new Runnable() {
@Override public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
/*** 定长线程池.
* (长度是指定的数值)
* 执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
* 4. 不存在,且线程池已满的情况下,则等待线程池存在空闲线程
*/
ExecutorService service = Executors.newFixedThreadPool(2); //线程池个数
service.execute(new Runnable() {
@Override public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
//效果与定长线程池 创建时传入数值1 效果一致.
/*** 单线程线程池.
* 执行流程:
* 1. 判断线程池 的那个线程 是否空闲
* 2. 空闲则使用
* 3. 不空闲,则等待 池中的单个线程空闲后 使用
*/
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(new Runnable() {
@Override public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
/*** 周期任务 定长线程池.
* 执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
* 4. 不存在,且线程池已满的情况下,则等待线程池存在空闲线程
*/
/*** 格式1:
* 参数1. runnable类型的任务
* 参数2. 时长数字
* 参数3. 时长数字的单位
*/
ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
service.schedule(new Runnable() {
@Override public void run() {
System.out.println("xxx");
}
},5,TimeUnit.SECONDS); //5s后执行
/*** 格式2:
* 参数1. runnable类型的任务
* 参数2. 时长数字(延迟执行的时长)
* 参数3. 周期时长(每次执行的间隔时间)
* 参数4. 时长数字的单位
*/
service.scheduleAtFixedRate(new Runnable() {
@Override public void run() {
System.out.println("俩人相视一笑~ 嘿嘿嘿");
}
},5,2,TimeUnit.SECONDS);//5s后执行,每隔2s执行
5.3 Lambda表达式
5.3.1 概念
- Lambda 表达式是 JDK8 的一个新特性,可以取代大部分的匿名内部类.
5.3.2 作用与要求
- 对某些接口进行简单的实现,但并不是所有的接口都可以使用 Lambda 表达式来实现。Lambda 规定接口中只能有一个需要被实现的方法,不是规定接口中只能有一个方法
- @FunctionalInterface
修饰函数式接口的,要求接口中的抽象方法只有一个。 这个注解往往会和 lambda 表达式一起出现。
- 语法形式为 () -> {},其中 () 用来描述参数列表,{} 用来描述方法体,-> 为 lambda运算符 ,读作(goes to)
这篇关于3.5 多线程的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!