Java基础复习-线程与并发
Java基础复习-线程与并发
这方面接触的不多,希望不会出错误
1.线程、进程和协程的区别
进程是操作系统在内存中分配的一个应用程序,进程之间互相独立,有独立的内存空间。
一条进程可以包括多条线程,线程是执行任务的主要单位,同进程内的所有线程共享该进程的数据,可以并发执行,需要同步机制避免线程对数据操作的冲突。
线程不共享的资源包括:栈、寄存器、线程局部存储、线程信息等
一条线程可以拥有多条协程,协程调度由程序(用户)来执行,而进程线程都由内核调度。Java没有实现协程(Kilim框架模拟实现了协程),Python中通过yield/send或async/await实现协程。
2.线程的6个状态
在java中,一个线程共有六种状态,状态之间可以切换
- 1.new 线程刚被创建,没有开始运行的状态
- 2.runnable (与running状态合并)调用start()方法后进入就绪/运行状态,注意start()方法会用一个新的线程去调用run()方法
- 3.blocked 线程阻塞状态,释放CPU(如试图获取锁时被其他线程占用)
- 4.waiting 等待状态,无限等待,通过wait(),join()等方法实现,需要被其他线程唤醒或者中断
- 5.time_waiting 超时等待,为等待状态增加限时,sleep()或者带参数的wait()方法均可以实现
- 6.terminated 线程执行完毕
3.创建线程的4个方法
- 1.继承Thread类并重写run()方法创建线程
- 2.实现Runnable接口并实现run()方法创建线程
- 3.实现Callable接口并实现call()方法创建线程
- 4.通过框架如Executor创建
4.线程间的通信
通过Object类提供的wait()、notify()、notifyAll()可以实现进程间的通信,wait()方法会释放锁,notify()不会,这两个方法需要配合synchronized关键词的修饰。
5.Runnable和Callable的区别是什么
- 1.接口方法不同,一个是run()方法,另一个是call()方法
- 2.call()方法存在返回值,run()不存在返回值
- 3.Callable允许抛出异常,Runnable无法继续向上抛异常
6.volatile关键词是什么
可以用于修饰变量,用于保证该变量对于所有线程可见(线程对变量的修改可以立刻被其他线程发现),同时禁止了jvm内部的指令重排(防止指令执行顺序发生混乱)
volatile不具备原子性,仍然可能会导致多个线程对数据操作的冲突(例如线程刚读取完数据,在写入之前发生阻塞)
7.synchronized
synchronized是一个同步关键词,可以修饰类、变量、普通方法、静态方法和代码块。具有原子性、可见性和有序性,实质上是一种悲观锁和非公平锁,会导致阻塞。
1.修饰普通方法: 对于当前对象的实例,一个线程需要获得锁才能够进入这部分同步代码,完成这部分代码后线程会自动释放锁
2.修饰代码块:与修饰方法类似,主要是作用范围不同,当一个线程访问这部分代码块时会拥有锁,其他进程试图访问该对象的这部分代码时会被阻塞,不影响访问其他非同步代码块
public void run() {
synchronized (this){
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
3.修饰类:一个线程访问这个类时,这个类的所有对象都会被锁定,即这个类的所有方法会共享同一把锁
4.修饰静态方法:与修饰类的情况相似,对于这个静态方法,这个类的所有实例化对象占用同一把锁
5.注意同一个对象的所有synchronized方法是共享锁的
8.乐观锁与悲观锁
都是并发情况下锁的设计思想
乐观锁
- 默认其余人不会同时修改数据,不对数据加锁。
- 在执行更新时判断数据是否被修改,若已经被修改则拒绝操作。
- 自旋CAS算法实现,包括三个操作数,待读写内存位置V,比较预期值A,准备写入的新值B,若V=A,则将A更新为B,否则自旋保持重试直到成功
- 版本号机制实现,在数据中增加一个version版本数据,数据被修改时版本+1,更新数据时需要判断当前版本号与读取版本号是否一致
- 乐观锁本身不加锁,仅仅只是进行判断。适用场景限制较高,适用于竞争不激烈的情况
悲观锁
默认别人会同时修改数据,操作数据时对数据加锁,直到完成后才释放锁。 适用于竞争激烈的场合,加锁和释放锁需要消耗额外资源,并且锁的存在影响并发性能
9.进程间的通信方式有哪些
- 1.管道:数据单向流动,亲缘进程之间的通信
- 2.命名管道:与管道类似,管道具有名称,可在文件系统中访问,对亲缘关系无要求
- 3.信号:异步通信方式,处理异步事件
- 4.共享内存:允许进程共享同一块物理内存,速度快,需要考虑并发和同步
- 5.信号量:控制进程对资源的访问,常用于同步和互斥,保护共享内存数据
- 6.消息队列MQ:进程之间传递结构化的数据
- 7.套接字Socket:允许不同主机间访问,实现分布式系统和网络通信
10.创建多线程的方式
- 直接创建多个单线程,见前面创建线程方式
- 通过线程池创建,可以提高响应速度,降低资源损耗,便于线程管理
ExecutorService executorService = Executors.newFixedThreadPool(10);
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor)executorService;
//设置线程池的属性
threadPoolExecutor.setCorePoolSize(12);
threadPoolExecutor.setMaximumPoolSize(10);
/**
* executorService.execute(Runnable commd);
* executorService.submit(Callable commd):可用FutureTask获取结果;
*/
executorService.submit(new ThreadTest());
executorService.submit(new ThreadTest());
线程池包括如下参数:
| 线程池参数 | 解释 |
|---|---|
| corePoolSize | 核心线程数,维护最小线程数量 |
| maximumPoolSize | 最大线程数,池内允许最大线程数量 |
| keepAliveTime | 空闲线程的存活时长 |
| unit | 指定keepAliveTime时间的单位 |
| workQueue | 指定线程工作队列类型 |
| threadFactory | 指定线程工厂 |
| handler | 指定超出线程和工作队列时的处理策略 |