进程、线程、协程
- 进程:是程序运行和资源分配的基本单位。一个程序至少有一个进程,一个进程可以有多个线程、多个协程。
- 线程:是进程的实体,是cup能够进行调度的最小单位。多个线程共享内存资源,减少切换次数,提高效率,多个线程之间可以并发执行。
- 协程:是一种用户态的轻量级线程,协程的调度完全由用户控制。
并行和并发
- 并行是指两个或者多个事件在同一时刻发生;而并发是指两个或者多个事件在同一时间间隔发生。
- 并行是在不同实体发生多个事件;而并发是在同一个实体发生多个事件。
创建线程方式
- 继承Thread类,重写run方法。
- 实现Runnable接口,定义接口实现类,重写run方法。
- 实现Callable接口和Future创建线程,定义接口实现类并实现call方法,使用FutureTask类包装Callable对象。使用FutureTask对象作为Thread对象的Target创建并启动新线程。
- 通过线程池创建线程。
Runnable和Callable区别
- Runnable接口中的run()方法的返回值是void,只是单纯的去Run而已。
- Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。
线程状态
- 创建状态
- 就绪状态
- 运行状态
- 阻塞状态
- 死亡状态
(此图片来源https://blog.csdn.net/pange1991/article/details/53860651)
sleep()和wait()区别
sleep():方法是线程类(Thread)的静态方法,让调用此方法的线程进入睡眠状态,让出执行机会给其他线程,等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争Cup的执行时间。调用sleep()方法的对象的锁不会释放,开玩笑的说,sleep()方法是抱着锁睡觉的。
wait()方法:是Object类的方法,当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,与此同时会释放对象的锁,使其他线程能够进行访问,可以通过notify、notifyAll方法来唤醒等待的线程。
notify()和notifyAll()区别
- notify()方法一次只能唤醒一个线程从等待池进入锁池。
- notifyAll()方法一次会唤醒所有等待池中的线程进入锁池。
join()方法和yield()方法区别
- join()方法:调用join()方法的线程会先执行,并且阻塞原先正在执行的线程,阻塞的线程要等调用join()方法的线程执行完才会继续执行。这就是“join(加入)”方法的作用。
- yield()方法:和join()方法相反,调用yield()方法的线程会让出位置,给别的线程执行,等别的线程执行完了再执行调用yield()方法的线程。
创建线程池方式
- newFixedThreadPool(int n)
创建一个固定长度(n)的线程池,每提交一个任务就会创建一个线程,当线程池达到最大规模的时候,超出的线程会在队列等待;当线程发生未知的错误而结束时,线程池会补充新线程。
- newCachedThreadPool()
创建一个可缓存的线程池,如果线程池的规模超过处理需求,会回收空闲线程;否则会创建新线程,线程池的规模不存在任何限制。
- newSingleThreadExecutor()
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行。
- newScheduledThreadPool(int n)
创建一个固定长度的线程池,而且以延时或定时的方式来执行任务。
线程安全三大原则
- 原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作 。
- 可见性:一个线程对数据的修改可以及时地被其他线程看到。
- 有序性:一个线程观察其他线程的指令执行顺序。
死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或彼此通信而造成一种阻塞的现象,若无外力的作用下,它们都将无法推进下去。此时称系统处于死锁状态,这些永远在等待的进程称为死锁进程。
死锁的四个必要条件:
- 互斥条件:进程对所分配的资源不允许其他进程进行访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放资源。
- 请求和保持条件:进程获得一定的资源后,又对其他资源发出请求,但是该资源可能被其他进程占有,此时请求阻塞,但又对自己获得的资源保持不放。
- 不可剥夺条件:指进程获得资源后,在未完成使用之前,不可被剥夺,只能等自己使用完后释放。
- 环路等待条件:指进程发送死锁后,若干进程之间形成一种头尾相接的循环等待资源的关系。
ThreadLocal
ThreadLocl是线程局部变量,属于线程自身所有,不在多个线程之间共享。ThreadLocal适用于每个线程需要自己的独立实例而且需要在多个方法中使用。
Synchronized
Synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法进入临界区,同时它还可以保证共享变量的内存可见性。
java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
- 普通同步方法:锁是当前实例对象。
- 静态同步方法:锁是当前类对象。
- 同步方法块:锁是括号里面的对象。
synchronized和volatile的区别
volatile关键字的作用:保证了变量的可见性(visibility)。被volatile关键字修饰的变量,如果值发生了变更,其他线程立马可见,避免出现脏读的现象。
- volatile作用在变量级别;synchronize则可以使用在变量、方法和类级别上。
- volatile仅能实现变量的修改可见性,但保证不了原子性;而synchronized可以保证变量修改的可见行以及原子性。
- volatile不会造成线程阻塞;而synchronized会造成线程阻塞。
- volatile标记的变量不会被编译器优化,synchronized标记的变量会被编译器优化。
synchronized和lock区别
- synchronized是java的关键字;在jvm层面,Lock是个java类。
- synchronized无法判断是否获取锁的状态;Lock可以判断是否获取到锁。
- synchronized会自动释放锁;Lock需要在finally中手动释放锁(调用unlock()方法释放锁)。
- synchronized适合少量代码同步问题;Lock适合大量代码同步问题。
- synchronized锁可重入、不可中断、非公平;Lock锁可重入、可中断、可公平。