线程
实现方式
通过继承Thread类实现一个线程
通过实现Runnable接口实现一个线程
使用Callable和Future
线程生命周期:
创建线程——>就绪——>运行——>阻塞——>死亡
涉及方法:
new,start,run,wait,notify,sleep,interrupt
sleep() 和 wait() 的区别
- sleep 来自 Thread类,wait 来自 Object 类
- sleep 没有释放锁,wait 释放了锁
- 使用范围不一样,wait、notify/notifyAll 必须在同步块或者同步方法中使用,sleep任意地方都可以使用
- sleep必须捕获异常,wait不需
进程:
资源分配的基本单位,运行调度的基本单位,系统中并发执行的单位
线程:
进程中执行运算的最小单位,亦是执行处理机调度的基本单位
synchronized与Lock区别
| 类别 | synchronized | Lock |
|---|---|---|
| 存在层次 | Java的关键字,在jvm层面上 | 是一个类 |
| 锁的释放 | 1、以获取锁的线程执行完同步代码,释放锁2、线程执行发生异常,jvm会让线程释放锁 | 在finally中必须释放锁,不然容易造成线程死锁 |
| 锁的获取 | 假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待 | 分情况而定,Lock有多个锁获取的方式,具体下面会说道,大致就是可以尝试获得锁,线程可以不用一直等待 |
| 锁状态 | 无法判断 | 可以判断 |
| 锁类型 | 可重入 不可中断 非公平 | 可重入 可判断 可公平(两者皆可) |
| 性能 | 少量同步 | 大量同步 |
线程间的协作:
**同步:**synchronized与Lock实现,上锁保证同步;
**通信:**线程间的唤醒;wait()、notify()、notifyAll()
ps:只有在同步控制方法或同步控制块里才能调用 wait() 、notify() 和 notifyAll()。
Synchronized 原理
在编译的字节码中加入了两条指令来进行代码的同步。
monitorenter :
每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:
如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
monitorexit:
执行monitorexit的线程必须是objectref所对应的monitor的所有者。
指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。
通过这两段描述,我们应该能很清楚的看出Synchronized的实现原理,Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。
实现线程安全的方法
锁
- synchronized 关键字
- Lock
- 乐观锁:设置时间戳或者版本号之类的(JPA中的乐观锁的实现)
线程本地存储
- 成员变量 int num = 10;
- 多个线程都要操作这个变量,但是需求是每个线程中的num值初始都为10,互不影响
- ThreadLocal
锁的类型:
可重入锁、自旋锁、公平锁、读写锁、独占锁、乐观锁
synchronized的优化(JDK1.6后加入):
偏向锁(无锁):
大多数情况下锁不仅不存在多线程竞争,而且总是由同一线程多次获得。偏向锁的目的是在某个线程获得锁之后(线程的id会记录在对象的Mark Word中),消除这个线程锁重入(CAS)的开销,看起来让这个线程得到了偏护。
轻量级锁(CAS):
轻量级锁是由偏向锁升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁;轻量级锁的意图是在没有多线程竞争的情况下,通过CAS操作尝试将MarkWord更新为指向LockRecord的指针,减少了使用重量级锁的系统互斥量产生的性能消耗。
重量级锁:
虚拟机使用CAS操作尝试将MarkWord更新为指向LockRecord的指针,如果更新成功表示线程就拥有该对象的锁;如果失败,会检查MarkWord是否指向当前线程的栈帧,如果是,表示当前线程已经拥有这个锁;如果不是,说明这个锁被其他线程抢占,此时膨胀为重量级锁。
ThreadLocal基础知识
很多地方叫做线程本地变量,也有些地方叫做线程本地存储。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
线程池
\1. 线程复用:实现线程复用的原理应该就是要保持线程处于存活状态(就绪,运行或阻塞)
\2. 控制并发数量:(核心线程和最大线程数控制)
\3. 管理线程(设置线程的状态)