概念
- 进程
- 进程间通信方式
- 共享内存
- 消息传递 Emailbox机制
- 进程状态
- PCB负责记录进程信息
- 创建,就绪,运行,等待,消亡
- 就绪 意味着只差cpu了
- 等待 即程序运行缺少非CPU资源的依赖
- 进程间通信方式
- 同步和异步
- 同步 需要等待依赖于其他程序或资源才能完成本次执行
- 非阻塞 无需待续结果,立即返回
- 信号量 实现进程同步的方式
- 并发(Concurrency)和并行(Parallelism)
- 并发偏重于多个任务交替执行
- 在多个CPU中可以同时运行(并行)
- 高并发 保证系统能够同时并行处理很多请求
- 临界区
- 表示一种公共资源或者说是共享数据
- 每一次,只能有一个线程使用
- 并行程序中,临界区资源是保护的对象
多线程
- 使用方式
- Thread类
- 继承该类,重写run方法
- 实例start方法启动线程,
- 以随机时间来调用线程中的run方法
- 实现Runnable接口
- 线程池
- Thread类
- 实例变量与线程安全
- synchronized 保证任意时刻只能有一个线程执行该方法
- 利用 AtomicInteger 类
- 常用方法
- currentThread()
- 返回对当前正在执行的线程对象的引用
- getId()
- getName()
- getPriority()
- isAlive()
- 测试这个线程是否还处于活动状态
- sleep(long millis)
- 使当前正在执行的线程以指定的毫秒数“休眠”(暂时停止执行)
- interrupt()
- 标记中断状态为true,并不会影响线程的继续执行
- 若中断后调用了wait、jion、sleep方法中的一种(即线程状态转移),抛出InterruptedException异常,且中断标志被清除
- interrupted() 和isInterrupted()
- setName(String name)
- isDaemon()
- 测试线程是否是守护线程
- setDaemon(boolean on)
- join()
- 等待该线程终止
- 主线程需要等待子线程执行完成之后再结束
- yield()
- 放弃当前的CPU资源
- setPriority(int newPriority)
- currentThread()
- 分类
- 用户线程 在前台执行具体的任务
- 守护线程 在后台为其他前台线程服务
- 特点 一旦所有用户线程都结束运行,守护线程会随JVM一起结束工作
- 应用
- 常见的守护线程:垃圾回收线程
- 数据库连接池中的检测线程,JVM虚拟机启动后的检测线程
- 线程操作
- 如何停止一个线程
- 使用interrupt方法
- 使用return停止线程
- 线程的优先级
- 如何停止一个线程
synchronized 关键字
- 变量安全性
- 两个线程同时操作对象中的实例变量,则会出现“非线程安全”
- 解决办法就是在方法前加上synchronized关键字
- synchronized取得的锁都是对象锁,而不是把一段代码或方法当做锁
- 可重入锁
- 自己可以再次获取自己的内部锁
- 可重入锁也支持在父子类继承的环境中
- 同步不具有继承性
- 同步语句块
- 当一个线程访问一个对象的synchronized同步代码块时,另一个线程任然可以访问该对象非synchronized同步代码块
- 不在synchronized代码块中就异步执行,在synchronized代码块中就是同步执行
- 若两个线程使用了同一个“对象监视器”,运行结果是同步的
- 类对象同步
- synchronized关键字加到static静态方法和synchronized(class)代码块都是给class类上锁
- synchronized关键字加到非静态方法上是给对象上锁
- 在Jvm中具有String常量池缓存功能
volatile关键字
- 特性
- 定义
- volatile 指示jvm变量是不稳定的,每次使用它都到主存中进行读取
- volatile关键字声明的变量或对象通常具有与优化、多线程相关的特殊属性
- 无volatile关键字,编译器可能优化读取和存储,将出现数据不一致的现象
- 在C中,volatile关键字可以用来提醒编译器它后面所定义的变量随时有可能改变,都会直接从变量地址中读取
- 可见性
- Java的内存模型是如何读取变量
- volatile 修饰的成员变量在每次被线程
- 访问时 强迫从主存(共享内存)中重读该成员变量的值
- 在变量发生变化时 强迫线程将变化值回写到主存(共享内存)
- 目的 保证同步数据的可见性
- volatile 修饰的成员变量在每次被线程
- while死循环中加入输出语句或sleep方法会出现什么效果
- jvm尽可能保证内存的可见性
- Java的内存模型是如何读取变量
- volatile 关键字无法保证对变量的原子性
- 要保证数据的原子性还是要使用synchronized关键字
- 定义
- synchronized和volatile之比较
- 运行效率
- volatile关键字是线程同步的轻量级实现
- volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块
- 原子性
- volatile关键字能保证数据的可见性,但不能保证数据的原子性
- synchronized关键字两者都能保证
- 是否会产生阻塞
- 多线程访问volatile关键字不会发生阻塞
- synchronized关键字可能会发生阻塞
- 处理问题
- volatile关键字用于解决变量在多个线程之间的可见性
- synchronized关键字解决的是多个线程之间访问资源的同步性
- 运行效率
wait/notify
- 等待/通知机制
- 需求
- 若轮询时间的间隔太小会浪费CPU资源,太大可能取不到自己想要的数据
- 不使用等待/通知机制
- 两个线程之间存在生产和消费关系
- 第一个线程(生产者)做相应的操作然后第二个线程(消费者)感知到变化又进行相应的操作
- 两个线程之间存在生产和消费关系
- 机制定义
- 线程A调用了对象O的wait()方法进入等待状态
- 另一个线程B调用了对象O的notify()或notifyAll()方法
- 线程A收到通知后退出等待队列,进入可运行状态,进而执行后续操作
- 相关方法
- notify() 随机唤醒等待队列中等待同一共享资源的一个线程,退出等待队列
- notifyAll() 使所有正在等待队列中等待同一共享资源的全部线程退出等待队列,进入可运行状态
- wait() 使用该方法的线程释放共享资源锁,从运行状态退出,进入等待队列,直到被再次唤醒
- 方法使用
- Java为每个Object都实现了等待/通知(wait/notify)机制的相关方法,必须用在synchronized关键字同步的Object的临界区内
- 调用wait()方法可以使处于临界区内的线程进入等待状态,同时释放被同步对象的锁
- notify()方法可以唤醒一个因调用wait操作而处于阻塞状态中的线程,使其进入就绪状态
- 重新唤醒的线程会视图重新获得临界区的控制权也就是锁
- Java为每个Object都实现了等待/通知(wait/notify)机制的相关方法,必须用在synchronized关键字同步的Object的临界区内
- notify()锁不释放
- 当方法wait()被执行后,锁自动被释放
- 执行完notify()方法后,锁不会自动释放
- 必须执行完notify()方法所在的synchronized代码块后才释放
- 当线程呈wait状态时,对线程对象调用interrupt方法会出现InterrupedException异常
- 需求
线程间通信
- 管道输入/输出流
- 用于线程之间的数据传输,传输媒介为内存
- 方式分类
- 面向字节 PipedOutputStream,PipedInputStream
- 面向字符 PipeWriter,PipedReader
- Thread.join()
- 应用场景
- 主线程需要等待子线程执行完成之后再结束
- 一个线程需要等待另一个线程
- 原型 join(long mills)
- 应用场景
- ThreadLocal类
- 作用
- 让每个线程绑定自己的值,可存储每个线程的私有数据
- 相关方法
- get(), set(T value),remove(),initalValue()
- InheritableThreadLocal
- 解决子线程并不能取到父线程的ThreadLocal类的变量的问题
- 作用
Lock锁
- Lock接口
- 用于通过多个线程控制对共享资源的访问工具
- 在Lock接口出现之前,Java程序是靠synchronized关键字实现锁功能的
- Lock接口提供synchronized关键字不具备的特性
- 尝试非阻塞地获取锁
- 当前线程尝试获取锁,如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁
- 能被中断的获取锁
- 获取到锁的线程能够响应中断,当获取到锁的线程被中断时,中断异常将会被抛出,同时锁会被释放
- 超时获取锁
- 在指定的截止时间之前获取锁, 超过截止时间后仍旧无法获取则返回
- 尝试非阻塞地获取锁
- 常用方法
- void lock()
- Condition newCondition()
- boolean tryLock()
- void unlock()
- 用于通过多个线程控制对共享资源的访问工具
- Lock接口的实现类ReentrantLock
- 作用:和synchronized关键字一样可以用来实现线程之间的同步互斥
- Condition接口
- 使用Condition实现等待/通知机制
- 单个condition
- 多个condition
- 使用condition实现顺序执行
- 公平锁与非公平锁
- ReentrantLock(boolean fair)方法
- ReadWriterLock接口的实现类:ReentrantReadWriteLock
- 读读共享
- 写写互斥
- 读写互斥
- 写读互斥
并发编程
- 并发是否好?
- CPU通过给每个线程分配CPU时间片来实现伪同时运行
- 并发编程并不总是能提高程序运行速度的
- 上下文切换
- 任务从保存到再加载的过程就是一次上下文切换
- 上下文切换对系统来说意味消耗大量的CPU时间
- 上下文切换的方式
- 让步式上下文切换
- 抢占式上下文切换
- 减少上下文切换
- 减少锁的使用
- 使用CAS无锁算法
- CAS比较与交换(Compare and swap)
- 减少线程的使用
- 创建大量的线程会导致大量的线程都处于等待状态
- CAS算法和协程
- 使用协程
- 避免死锁
- 使用不当会造成死锁
- 减少资源限制
- 资源限制引发
- 在并发编程中,程序运行加快的原因是运行方式从串行运行变为并发运行
- 若某段程序的并发执行由于资源限制仍然在串行执行的话,反而会变慢,因可能增加资源调度时间
- 解决资源限制
- 硬件资源限制,使用集群并行执行程序
- 软件资源限制,使用资源池将资源复用
- 用连接池将数据库和Socket复用
- 资源限制引发
线程池与Executor框架
- 用线程池优点
- 降低资源消耗
- 提高响应速度
- 提高线程的可管理性
- Executor
- Java5+之后,通过Executor来启动线程比使用Thread的start方法好
- 框架结构组成
- 任务
- Runnable接口或Callable接口实现类
- 执行
- ScheduledThreadPoolExecutor
- ThreadPoolExecutor
- 异步计算的结果
- Future接口
- Future接口的实现类FutrueTask类
- 任务
- ThreadPoolExecutor
- 常用属性
- corePoolSize
- maxinumPoolSize
- queue
- rejectedExecutionHandler
- 4个构造方法
- 创建
- FixedThreadPool 可重用固定线程数的线程池,常在需要限制当前线程数量的应用场景
- SingleThreadExecutor 使用单个worker线程的Excutor
- 适用于需要保证顺序地执行各个任务并且在任意时间点,不会有多个线程是活动的应用场景
- CachedThreadPool 根据需要创建新线程的线程池
- 适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器
- 常用属性
- ScheduledThreadPoolExecutor
- 作用 延迟或定欺执行任务
- 执行周期任务的步骤
- 线程池的应用场景
- ScheduledThreadPoolExecutor: 适用于需要多个后台执行周期任务
- SingleThreadScheduledExecutor: 适用于需要单个后台线程执行周期任务