线程和进程
(由于我自己不知道怎么上传思维导图,想要思维导图的小伙伴,可以在评论区留邮箱,我看到了就会发送过去的)
进程和线程之间的区别
1 进程时资源分配的最小单位, 而线程时执行调度的最小单位
2 在一个进程中包含多个线程,线程共享该进程中的资源
3 进程要比线程消耗更多的计算机资源,一个线程挂掉将导致整个进程挂掉
进程
进程之间的通信方式
- 互斥量:多个线程使用共享内存时,其他线程必须等待该线程技术该可以使用这块内存
- 信号量:进程使用的内存地址可以限定使用量
- 管道
- 消息队列
- 套接字
线程
创建线程的三种方式
-
具体三种方式
- 继承Thread类
- 实现Runnable接口
- 实现Callback接口和Futures接口
-
三种方式对比
-
Thread类和两种接口方式的对比
-
可以避免java单继承的限制
-
线程池中只能存放又Runnable接口和Callable是接口实现的线程
-
线程池
-
线程池中概览
-
线程池的创建
-
创建线程池使用的主要函数以及对应的参数
- public ThreadPoolExecutor(int corePoolSize, 核心线程数
-int maximumPoolSize, 最大线程数 - long keepAliveTime, 非核心线程空闲时存活时间大小
-TimeUnit unit, 线程空闲时存活时间单位 - BlockingQueue workQueue, 存放任务的工作队列也称阻塞队列
- ThreadFactory threadFactory, 用户创建线程的工厂
- RejectedExecutionHandler handler) 线程的拒绝策略
- public ThreadPoolExecutor(int corePoolSize, 核心线程数
-
-
线程池的执行流程
- 图片链接:https://img-blog.csdn.net/20170618213838961?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMTI0MDg3Nw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast
-
-
线程池的四种拒绝策略
- 抛出异常
- 直接丢弃
- 丢弃工作(阻塞)队列中最老的线程任务
- 将控制权交给线程池调用的线程进行处理
-
线程池五种工作队列
-
有界队列(ArrayBlockingQueue): 使用数组来实现,按先进先出进行排序
-
无界队列(LinkedBlockingQueue):可设置容量,使用链表来实现,最大的长度为Integer.maxValue,按先进先出排序。固定长度线程池(newFixedThreadPool)中使用的该类型的队列
-
使用无界队列的线程池会导致内存飙升吗?
- 会。如果线程获取一个任务以后,该任务执行时间很长,会导致无界队列内的任务越积越多,导致机器内存飙升
-
-
延迟队列:任务定时延期执行的任务队列,定时周期性执行的线程池(newSechduledThreadPool)中使用该队列
-
优先级队列:具有优先级的无界阻塞队列。
-
同步队列:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用一处操作,否则插入操作一直处于阻塞状态。可缓存线程池(newCacheThreadPool)使用该队列
-
-
线程池四种异常处理方式
-
添加try…catch…finall块
-
- 在三个语句中都有return时,会执行什么内容
-
- finally永远执行除了以下几种情况
-
-
submit执行任务时,可以通过Future对象的get()方法接受抛出的异常,再进行处理
-
为工作着的线程设置UncaughtExceptionHandler在该方法中处理异常
-
重写ThreadPoolExecutor的afterExecute()方法处理传递异常引用
-
-
常见的线程池以及使用场景
-
固定长度线程池(newFixedThredPool)
- 核心线程数=最大线程数
- 使用无界阻塞队列
- 使用场景:cpu密集型任务,执行长期任务
-
定时周期性执行线程池(newSechduleThreadPool
- 使用延迟队列
- 使用场景:适用于周期性执行任务的场景
-
可缓存线程池(newCacheThreadPool)
- 使用同步线程池
- 无核心线程
- 使用场景:适用于并发执行大量短期的任务
-
单线程的线程池(newSingletonThreadPool)
- 最大线程数为1
- 使用场景:适用于串行执行任务的场景
-
-
-
-
适合多个线程进行资源共享
-
-
两种接口方式的对比
- 在Runnaable接口中调用的run()方法, Callable接口中调用的call()方法
- 运行Callable可以拿到一个Future对象,表示异步计算得到的结果
-
线程调度
- 抢占式线程调度
- 协同式线程调度
线程的状态
-
就绪(有执行资格,无执行权)
-
运行(有执行资格,有执行权)
-
阻塞(无执行资格,无执行权)
-
终止
-
sleep()、await()、yield()的区别
- sleep()是Object的方法,限期等待,可以执行阻塞多长时间,在线程的阻塞过程中,该线程不会释放锁,因此会造成其他也想访问该锁的线程进入阻塞的状态
- await()是Thread的方法,进入阻塞状态以后,需要显示的调用notify()或则notifyAll()才能唤醒。该线程会释放锁
- yield()同sleep()类似,只是不能像sleep()一样设置阻塞多长的时间。
线程不安全的原因
- 共享资源的存在
线程安全的实现方式
-
悲观锁
-
定义:保证共享资源在同一时刻内只能被一个线程使用
-
Synchronize
-
实现原理
- Synchronize关键字经过编译以后,会在同步块前后形成monitorenter和moniterexist这两个字节码,这两个字节码都需要一个reference类型的参数来指明要锁定和解锁的对象。
-
Synchronize修饰方法时的加锁对象
-
修饰静态方法,对类对象进行加锁
-
修饰成员方法,对实例对象进行加锁
-
多个线程访问同一对象的同一个方法
- 因为一个对象只有一把锁,所以其他线程无法获取该对象的锁,就不能访问呢该对象的其他Synchronize修饰的方法
-
多个线程作用于不同的对象
- 不影响
-
-
修饰代码块,对代码块内的对象进行加锁
-
-
Synchronize、ReentrLock、Lock之间的区别
- Synchronize的缺点:可能会让等待中的锁一直无限期的等待下去,因为该锁只能由操作兄台那个自己释放,所以才产生了Lock
-
- Synchronize是JAVA的关键字,是内置语言,而Lock是一个接口
-
- Synchronize不需要手动去释放锁,由操作系统释放,而Lock需要调用unlock()函数进行手动的释放锁
-
- Lock的锁具有等待可中段的特性(也是得益于可手动释放锁),而Synchronize不行
-
- Lock可以知道有么有成功获取到锁,而Synchronize不可以
-
- Lock可以实现公平锁、可以在多个条件上加锁,而Synchronize不可以
- 6 ReentreLock只是Lock的一个实现类
-
对Synchronize锁的优化
-
锁的开销是怎么产生的?
- 由于竞争共享资源,需要操作系统内核对该线程进行阻塞以及唤醒,操作系统在这个过程中需要完成两次线程上下文切换。
上下文切换:由于线程共享进程中的内存资源,因此只需要切换线程的私有数据、寄存器等不共享数据
- 由于竞争共享资源,需要操作系统内核对该线程进行阻塞以及唤醒,操作系统在这个过程中需要完成两次线程上下文切换。
-
自旋锁
- 拥有自旋锁的线程不会阻塞,而是自己执行几个忙循环
-
轻量级锁
- 在无实际竞争的环境下,减少重量级锁产生的性能消耗,仅仅_将Mark Word中的部分字节CAS更新指向线程栈中的Lock Record
-
偏向锁
- 减少无竞争且只有一个线程使用锁的情况下,产生的性能消耗
-
-
生产者消费者模式手写代码
-
-
-
乐观锁
-
定义: 先默认不存在竞争共享资源的情况,运行所有任务,当出现竞争状态时,把发生了刚刚那条线程的执行操作进行抛弃。就是在线编辑文档一样
-
CAS
-
实现原理
- pos=A,将B赋值给变量pos
-
ABA问题的解决方法
- JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。如果当前引用 == 预期引用,并且当前标志等于预期标志,则以原子方式将该引用和该标志的值设置为给定的更新值。
-
实现一个线程安全的计数器
- 使用java.util.concurrent.atomic.AtomicInteger来定义变量
然后使用IncrementAndGet()来实现自增
- 使用java.util.concurrent.atomic.AtomicInteger来定义变量
-
-
-
使用final关键字
-
ThreadLocal
-
定义
- 由ThreadLocal修饰的对象,在不同的线程中有自己的副本
-
实现原理
- 在ThreadLocal内部维护了一个ThreadLocalMap。{key:实例对象;value:变量}
-
存在内存泄露问题,怎么解决
- 为什么存在内存泄漏问题?
因为ThreadLocalMap中的key是弱引用,因此当在经过一次gc以后,就会对该key进行回收。这时就会出现key为null,而value继续存在,因此出现内存泄漏 - 解决方案:
使用完ThreadLocal就进行remove()操作
- 为什么存在内存泄漏问题?
-
死锁
-
定义
- 多个线程循环等待它方占有资源,而陷入无限期等待的情况
-
四个必要条件
- 互斥性
- 不可抢占性
- 循环等待
- 占有且申请
-
怎么避免死锁
- 打破互斥性的条件
- 打破不可抢占的条件
- 打破占有且申请的条件,采用资源预先分配的方法
- 打破循环等待的条件,采用资源有序分配的方法
多线程—深入理解AQS抽象队列同步器
-
AQS框架
-
ReentranLock
- 实现自Lock接口,具有可重入性,能够对资源进行重复加锁。
-
ReentranReadWriteLock
- 表示两个锁,一个是读操作相关的共享锁,一个写操作相关的排他锁
-
CountDownLatch
- 四个线程ABCD,其中D线程要等到ABC全部执行完毕以后才执行
-
- 创建一个计数器,设置初始值:CountDownLatch c = new CountDownLatch(number)
-
- 在等待线程D中调用CountDownLatch.await(),进入等待状态,直到计数值变为0
-
- 在其他线程中调用CountDownLatch.countDown()方法,该方法会把计数值递减
-
- 当其他线程中的countDown()方法把计数值变为0时,等待线程中的CountDownLatch.await()立即退出,执行接下去的代码
-
CyclicBarrier
- 三个运动员各自准备,等三个人准备后一次性跑
-
- 创建一个公共的 CyclicBarrier对象:设置同时等待的线程数
CyclicBarrier cb = new CyclicBarrier(number)
- 创建一个公共的 CyclicBarrier对象:设置同时等待的线程数
-
- 这些线程同时开始自己的准备,自身准备完以后,需要等待别人准备完毕,这是调用cb.await(),开始等待别人
-
- 当指定的同时等待线程数都调用了cb.await()时,意味着这些线程都准备好了,然后可以同时执行
-
Semaphore
- 可以控制同时访问的线程的个数,通过acquire()获得一个许可,通过release()释放一个许可
-
java.util.concurrent中都提供了哪些基础并发工具类
-
可丰富多线程操作的类
- CountDownLatch
- CylicBarrier
- Semaphore
- 比Synchronize更加高级的同步结构
-
线程安全的容器
- ConcurrentHashMap
- CopyOnWriteArrayList
-
并发队列
- BlockQueue的实现
- ArrayBlockQueue
-
强大的Executor框架
- 固定长度线程池
- 可缓存线程池
- 单线程线程池
- 周期执行线程池