Java多线程Day01-多线程之多线程基本概念
多线程的基本概念
- 基本概念
- 线程状态图
- 多线程机制
- Monitor
- synchronized
- volatile
- 等待队列
- Object类
- Thread类
- synchronized关键字
- 同步队列
- Thread中的方法
- Thread.sleep(long millis)
- Thread.yield()
- thread.join
- obj.wait()
- obj.notify()
- obj.notifyAll()
- LockSupport
- 多线程控制类
- ThreadLocal类
- 原子类
- Lock类
- 容器类
- 线程管理类
基本概念
Java多线程内容:
- Object类中的wait(),notify() 等接口
- Thread类中的接口
- synchronized关键字
- JUC是指java.util.concurrent包
进程: 计算机中已经运行的程序
- 计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础
- 在面向进程设计的系统中,进程是程序的基本执行实体
- 在面向线程设计的系统中,进程是线程的容器
- 程序本身只是指令,数据和自身组织形式的描述名称,进程才是程序的真正运行实例
线程: 操作系统能够进行运算调度的最小单位
- 线程包含在进程中,是进程中的实际运行单位
- 一条线程表示的是进程中的一个单一顺序的控制流
- 一个进程中可以并发多个线程,每条线程并行执行不同的任务
- 多线程: 一个进程运行时产生不止一个线程
- 并行: 多个CPU实例或者多台机器同时执行一段处理逻辑,是真正的同时进行
- 并发: 通过CPU的调度算法,让处理逻辑看上去是同时执行,实际从CPU操作层面上来说不是真正的同时进行
线程安全: 在并发的情况下,代码经过多线程的使用,线程的调度顺序不影响处理结果
- 线程安全的情况下,只需要关注系统内存,CPU是否够用
- 线程不安全的情况下,意味着线程的调度顺序会影响最终的处置结果,除了要关注系统内存,CPU是否够用外,还需要关注线程的调度顺序
同步: 通过控制和调度,保证共享资源的多线程访问是线程安全的,来保证结果的准确性
- 在保证结果准确的同时,提高性能,才是优秀的程序
- 线程安全的优先级高于性能需求的优先级
线程状态图
- Java中线程的状态分为6种: 新建状态New, 运行状态Runnable( 包括就绪状态ready和运行状态running), 阻塞状态Blocked, 等待状态WAITING, 超时等待状态 (TIMED_WAITING), 终止状态TERMINATED(消亡状态Dead)
新建状态NEW: 新创建了一个线程对象,但还没有调用start() 方法
- 线程对象被创建后,就进入了新建状态
- 实现Runnable接口和继承Thread可以得到一个线程类 ,new一个实例出来,线程就进入了初始状态
- 比如 : Thread thread = new Thread();
运行状态RUNNABLE: Java线程中将就绪状态Ready和运行状态Running统称为运行状态Runnable
就绪状态Ready: 线程对象创建后,其余线程调用该对象的start() 方法.该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态
- 调用线程的start() 方法,此线程进入就绪状态
- 当前线程sleep() 方法结束,其余线程join() 结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入就绪状态
- 当前线程时间片用完了,调用当前线程的yield() 方法,当前线程进入就绪状态
- 锁池里的线程拿到对象锁后,进入就绪状态
- 就绪状态只是表示线程可以运行,如果调度程序调度到线程,线程就永远处于就绪状态
运行状态Running: 就绪状态的线程在获得CPU时间片后变为运行状态
- 线程调度程序从可运行池中选择一个就绪状态Ready的线程进行调度
- 这是线程进入运行状态的唯一方式
阻塞状态BLOCKED: 线程阻塞于锁
- 线程阻塞在进入synchronized关键字修饰的方法或代码块获取锁时的状态
等待状态WAITING: 进入该状态的线程需要等待其余线程做出一些特定动作,比如通知或中断
- 处于这种状态的线程不会被分配CPU执行时间
- 等待被显式地唤醒,否则会处于无限期等待的状态
超时等待状态TIMED_WAITING: 该状态不同于WAITING, 可以在指定的时间后自行返回
- 处于这种状态的线程不会被分配CPU执行时间
- 不需要无限期等待被其余线程显式地唤醒,在达到一定时间后会自动唤醒
终止状态TERMINATED: 线程已经执行完毕
- 当线程的run() 方法完成时,或者主线程的main() 方法完成时,就认为线程终止
- 线程对象也许是活的,但是已经不是一个单独执行的线程.线程一旦终止,就不能复生
- 在一个终止的线程上调用start() 方法,会抛出java.lang.IllegalThreadStateException异常
多线程机制
Monitor
- Monitor监视器应用于同步问题的线程调度工具
Java中的每个对象都有一个Monitor监视器,用来监测并发代码的重入
- 非多线程编码时监视器不发挥作用
- 在synchronized范围内,监视器发挥作用
synchronized
- wait() 和notify() 必须存在于synchronized块中,这三个关键字针对的是同一个对象监视器. 也就是说,在wait() 之后,其余线程可以进入同步块中执行
- 如果某块代码没有持有Monitor监视器的使用权时而去进行wait() 和notify() 操作,会抛出java.lang.IllegalMonitorStateException
- 在synchronized代码块中调用另一个对象的wait() 和notify() 操作,由于对象的Monitor监视器不同,会抛出java.lang.IllegalMonitorStateException
代码块使用synchronized:
- 在多线程环境中 ,synchronized块中的方法获取某个对象实例的Monitor监视器
- 如果对象实例相同,那么就是同一个Monitor监视器,同一时间只有一个线程可以执行synchronized块中的方法
方法中使用synchronized:
- 实际获取的是类的Monitor监视器
- 修饰static方法时,则锁定的是这个类的所有实例
volatile
多线程的内存模型:
- 主存: main memory
- 线程栈: working memory
- 处理数据时,线程会将数据从主存main memory中加载load到本地栈working memory, 完成操作以后,再保存save回主存main memory
- volatile的作用: 在这里的作用是,每次对变量操作,都会激发一次load and save
volatile:
- 多线程的变量如果不使用volatile或者final进行修饰,会造成不可预知的后果. 比如某个变量被一个线程修改后,另一个线程获取到的是修改之前的值
- 原因在于同一个实例的同一属性本身应该只有一个副本,但是多线程中会对值进行缓存
- volatile就是不使用缓存,直接取值
- 在线程安全的情况下,使用volatile会影响性能
等待队列
- 调用obj的wait(),notify() 方法前,必须获得obj锁.即wait() 和notify() 方法必须写在synchronized(obj) 代码内
- 线程1获取对象的锁,正在使用对象A
- 线程1调用对象A的wait() 方法
- 线程1释放对象A的锁,进入等待队列
- 同步队列的线程争抢对象锁
- 线程5获取对象的锁,使用对象A
- 线程5调用A对象的notifyAll() 方法唤醒所有等待线程
- notifyAll() 方法所在的synchronized结束,线程5释放对象A的锁
- 同步队列的线程争抢对象锁
Object类
- wait()
- notify()
- notifyAll()
Thread类
- 线程休眠函数 : sleep()
- 线程中断函数 : interrupt()
- 获取线程名称 : getName()
synchronized关键字
- 分为synchronized代码块和synchronized方法
- 作用: 使得线程获取对象的同步锁
同步队列
- 当前线程调用对象A的同步方法时,发现对象A的锁被其余线程占用,此时当前线程进入同步队列. 即同步队列中的线程都是准备争抢对象锁的线程
- 当一个线程1被另外一个线程2唤醒时,线程1进入同步队列,去争抢对象锁
- 同步的环境下才会有同步队列.一个对象对应一个同步队列
超时等待线程到了等待时间或者等待线程被notify() 方法或者notifyAll() 方法唤醒,会进入同步队列中去竞争锁:
- 如果获得锁,则进入运行状态RUNNABLE
- 如果没有获得锁,则进入阻塞状态BLOCKED等待获取锁
Thread中的方法
Thread.sleep(long millis)
- 作用: 给其余线程执行的机会
- 一定是当前线程调用此方法
- 当前线程进入超时等待状态TIMED_WAITING, 不释放对象锁, millis后线程自动苏醒进入就绪状态
Thread.yield()
作用:
- 使得相同优先级的线程轮流执行,但是并不保证相同优先级的线程一定会轮流执行
- 实际中无法保证yield() 达到让步目的,因为让步的线程还有可能会被线程调度程序再次选中
- Thread.yield() 不会导致线程阻塞,该方法与sleep() 类似,只是不能由用户指定暂停多长时间
- 一定是当前线程调用此方法
- 当前线程放弃获取的CPU时间片,不释放资源,由运行状态Running转变为就绪状态Ready, 重新选择执行线程
thread.join
- 包括 : thread.join() 和 thread.join(long millis)
- 当前线程调用其余线程的join方法,当前线程进入等待状态WAITING或者超时等待状态TIMED_WAITING. 当前线程不会释放已经持有的对象锁
- 调用的其余线程执行完毕或者超时等待时间millis时间结束,当前线程进入RUNNABLE状态,也可能进入BLOCKED状态
obj.wait()
- 当前线程调用对象的wait() 方法,当前线程释放对象锁,进入等待队列
- 进入等待队列的线程依靠notify() 或者notifyAll() 唤醒
- 如果是调用对象的超时等待方法wait(long timeout), 则超时等待时间timeout时间结束自动唤醒
obj.notify()
- 唤醒此对象监视器上等待的单个线程,选择是任意性的
obj.notifyAll()
- 唤醒在此对象监视器上等待的所有线程
LockSupport
- 包括LockSupport.park(), LockSupport.parkNanos(long nanos), LockSupport.parkUntil(long deadlines)
- 当前线程进入等待状态WAITING或者超时等待状态TIMED_WAITING
- 不需要获得锁就可以让线程进入等待状态WAITING或者超时等待状态TIMED_WAITING
- 需要通过LockSupport.unpark(Thread thread) 方法唤醒线程
多线程控制类
- Java的多线程包 : java.util.concurrent中提供很多高效的多线程控制类,用于Java中多线程的控制
ThreadLocal类
作用: 保存线程的独立变量
- 对于一个继承自Thread的线程类,使用ThreadLocal维护变量时,ThreadLocal会为每个使用这个变量的线程提供独立的变量副本
- 这样每一个线程都可以独立的改变自己拥有的变量副本,不会影响其余线程的变量副本
实现方式:
- 每一个线程Thread持有一个ThreadLocalMap类型的变量
- ThreadLocalMap是一个轻量级的Map, 功能和Map一样,区别在于桶中存放的是entry不是entry链表
- 线程本身作为key, 目标变量作为value
主要方法有set() 和get()
- set(): 在ThreadLocalMap中维护threadLocal和变量的关系
- get(): 将目标变量返回
- ThreadLocal是一个特殊的容器
应用场景:
- 用户登录控制中记录session信息
原子类
- 如果使用原子包装类,比如AtomicInteger. 或者使用自定义的方式保证原子操作,本质上和synchronized类似
对于某些对象出现属性丢失的情况:
- 对象oldObject == currentObject
- oldObject.getProperty() != currentObject.getProperty()
- 这时,可以使用AtomicReference中的AtomicStampedReference为对象加上版本号
Lock类
- 作用: 为了解决同步问题,处理资源争用. 主要目的和synchronized类似
Lock和synchronized比较:
Lock:
- Lock更加灵活,可以灵活定义多把锁的加锁和解锁顺序
- 提供多种加锁方案.比如阻塞式lock, 无阻塞式trylock, 打断式lockInterruptily, 带超时时间版本的trylock
- 可以和Condition类联合使用
- Lock锁的性能比synchronized更高
synchronized:
- synchronized中的锁按照先加后解的顺序定义
Lock锁的三种实现:
- ReentrantLock
- ReentrantReadWriteLock.ReadLock
- ReentrantReadWriteLock.WriteLock
ReentrantLock:
- 可重入锁
- 可重入是指持有锁的线程可以继续持有,并且要释放对等的次数才能真正释放锁
ReentrantReadWriteLock:
- 可重入读写锁
- 可重入读写锁中的读锁ReadLock和写锁WriteLock都有lock()方法和unlock()方法
- 写写互斥,写读互斥
- 读读不互斥
- 可以实现高效的线程安全的并发读
容器类
BlockingQueue:
阻塞队列
- 队列Queue是一个单向队列,可以在队头添加元素和队尾删除或者取出元素
- 类似一个管道,适用于一些先进先出策略的应用场景
- 普通的Queue接口的主要实现有优先队列PriorityQueue
- 阻塞队列BlockingQueue在队列的基础上添加了多线的协作的功能
除了Queue中的方法外 ,BlockingQueue中提供了阻塞接口put() 和take(), 带有超时功能的阻塞接口offer() 和poll()
- put: 在队列满时阻塞,直到有空间才会被唤醒
- take: 队列空时阻塞,直到有元素才会被唤醒
- 适用于生产者-消费者模型的应用场景
常见的阻塞队列:
- ArrayListBlockingQueue
- LinkedListBlockingQueue
- DelayQueue
- SynchronousQueue
ConcurrentHashMap:
- 高线的线程安全的HashMap, 功能和HashMap类似
线程管理类
- 线程管理类: 用于管理线程,本身可能并不是多线程的,但提供一些机制对多线程工具进行封装来实现对多线程的管理
线程管理类主要有以下两种:
- ThreadPoolExecutor
- ThreadMXBean(JMX)
ThreadPoolExecutor中构造参数说明:
- corePoolSize: 线程的初始值.也是最小值.空闲状态时,也会保持这个数目的线程
- maximumPoolSize: 线程的最大值. 线程的数目始终不会超过这个值
- keepAliveTime: 当线程池内的线程数目高于corePoolSize时,经过这个时间后多余的线程才会被回收,回收前处于wait状态
- unit: 时间参数的单位. 可以使用TimeUnit实例
- workQueue: 待入任务Runnable的等待队列. 这个参数主要会影响调度策略,比如公平策略,是否产生starving线程
- threadFactory: 线程工厂类. 默认的参数实现,如果需要自定义实现,需要实现ThreadFactory接口并作为参数传入
还没有评论,来说两句吧...