概念
进程
- 程序由指令和数据组成,但指令要运行,数据要读写,就必须将指令加载至CPU,数据加载至内存。在指令运行过程中还需要用到磁盘,网络等设备。进程就是用来加载指令、管理内存、管理IO的
- 当一个程序被运行,从磁盘加载这个程序的代码至内存,这就开启了一个进程
- 进程可以视为程序的一个实例。大部分程序可以同时运行多个实例进程,与有的程序只能启动一个实例进程
线程
- 一个进程之内可以分配多个线程
- 一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给CPU执行
区别:
- Java中,线程作为最凶爱的调度单位,进程作为资源分配的最小单位
- 在windows中进程是不活动的,只是作为线程的容器
- 进程基本上相互独立的,而线程存在于进程内,是进程的一个子集
- 进程拥有共享的资源,如内存空间等,供其内部的线程共享
- 进程间通信较为复杂,同一台计算机的进程通信成为IPC,不同计算机之间的今天通信需要通过网络并遵守共同的协议例如HTTP
- 线程通信相对简单,因为他们共享进程内的内存,一个例子是多个线程可以访问同一个共享变量
- 线程更轻量,线程上下文切换一般比进程上下文切换低
串行与并行
单核cpu下,线程还是串行执行的,操作系统中有一个组件任务调度器,将cpu的时间片发给不同的程序使用,微观串行,宏观并行
并发
同一时间应对多件事情的能力
做饭,打扫卫生,一个人轮流交替做这些事
并行
同一时间动手做多件事情的能力
俩个人,他们一个人做饭,一个人打扫卫生
异步/同步
- 需要等待结果返回,同步
- 不需要等待结果返回,异步
应用
- 在项目中,视频文件需要转换格式等操作比较费时,这时开一个新线程处理视频转换,避免阻塞主线程
- tomcat的异步servlet也是类似的,让用户线程处理耗时时间长的操作,避免阻塞tomcat的工作线程
- ui程序中,开线程进行其他操作,避免阻塞ui线程
配置
解决maven工程不支持lambda表达式
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
Thread与Runnable的关系
- Thread把线程和任务合并在了一起,Runnable把线程和任务分开了
- 用Runnable更容易与线程池等高级API配合
- 用Runnable让任务类脱离了Thread基础体系更加灵活
FutureTask配合Thread
public class FutureTaskDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
FutureTask<String> task = new FutureTask<String>(()->{
TimeUnit.SECONDS.sleep(3);
return "abc";
});
new Thread(task,"a").start();
// System.out.println(task.get());
//给定时间内完成,否则抛异常 java.util.concurrent.TimeoutException
System.out.println(task.get(2, TimeUnit.SECONDS));
}
}
Linux查看进程线程的方法
- ps -fe 查看所有进程
- ps -fT -p 查看某个进程PID的所有线程
- kill 杀死进程
- top 按大写H切换是否显示线程
- top -H -P 查看某个进程的所有线程
栈与栈帧
Java Virtual Machine Stacks(Java虚拟机栈)
JVM有堆,栈,方法区等组成,其中栈给线程用,每个线程虚拟机都会为其分配一块栈内存
- 每个栈由多个栈帧组成,对应着每次方法调用时所占用的内存
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
线程上下文切换
Thread Context Switch
因为一下一些原因导致cpu不再执行当前线程,转而执行另一个线程的代码
- 线程的cpu时间片用完
- 垃圾回收
- 有更高优先级的线程需要运行
- 线程子集调用了sleep yield wait join park synchronized lock
- 当Context Switch发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,对应的程序计数器,他的作用是记住下一条JVM指令的执行地址
- 状态包括程序计数器、虚拟机栈中每个栈帧的消息,如局部变量、操作数栈、返回地址等
- Context Switch 频繁发生会影响性能
Thread常用方法
方法名 | 功能说明 | 注意 |
start | 启动一个新线程,在新的线程中运行run方法代码 | start方法只是让线程进入就绪,不一定立即运行,每个start方法只能调用一次 |
run | x线程启动后调用的方法 | 如果在构造Thread对象是传递了Runnable参数线程启动后会调用Runnable的run方法,否则默认不执行任何操作 |
join | 等待线程运行结束 | |
join(long n) | 等待线程运行结束,最多等待n毫秒 | |
getId | 获取线程长整型id | id唯一 |
getName | 获取线程名 | |
setName(String name) | 修改线程名 | |
getPriority | 获取线程优先级 | Java中规定线程优先级是1-10的整数,提高被cpu调度的概率 |
setPriority | 设置优先级 | |
geState | 获取线程状态 | NEW RUNNABLE BLOCKED WAITING TIMED_WAITING TERMINATED |
isInterruoted | 判断是否被打断 | 不会清除打断标记 |
isAlive | 线程是否存活 | |
interrupt | 打断线程 | 如果被打断线程正在sleep wait join会导致被打断的线程抛出InterruptedException,并清除打断标记,如果打断的正在运行的线程,则设置打断标记,park的线程被打断,也会设置打断标记 |
interrupted | static 判断当前线程时候被打断 | 会清除打断标记 |
currentThread | static 获取当前正在执行的线程 | |
sleep(long n) | static 让当前执行的线程休眠n毫秒,休眠是让出cpu的时间片给其他线程 | |
yield | static 提示线程调度器让出当前线程对cpu的使用 | 主要是为了测试和调试 |
start与run
调用run程序仍在main线程运行,方法调用还是同步的
调用start方法调用是异步的
sleep与yield
- 调用sleep会让当前线程从running进入TimedWaiting状态(阻塞)
- 其他线程可以使用interrupt方法打断正在睡眠的线程,这是sleep方法会抛出InterruptedException
- 睡眠结束后线程未必会立刻得到执行
- 建议用TimeUnit的sleep代替Thread的sleep来获得更好的可读性
- yidle会让当前线程从Running 进入Runnable就绪状态,然后调度之前其他线程
- 具体实现依赖于操作系统的任务调度器
Join
public class JoinDemo {
static int a = 0;
public static void main(String[] args) throws InterruptedException {
Thread a1 = new Thread(() -> {
a = 10;
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "a");
Thread a2 = new Thread(() -> {
a = 20;
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "b");
a1.start();
a2.start();
a1.join();
a2.join();
System.out.println(a);
}
}
interrupt
public class 两阶段中止 {
private static Thread t1;
public static void stops(){
t1.interrupt();
System.out.println("打断");
}
public static void main(String[] args) throws InterruptedException {
t1 = new Thread(() -> {
while (true) {
if(t1.isInterrupted()){
System.out.println("被打断");
break;
}
System.out.println("监控");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
t1.interrupt();
}
}
}, "t1");
t1.start();
TimeUnit.SECONDS.sleep(3);
stops();
}
// 监控
// 监控
// 监控
// 打断
// 被打断
}
打断park线程
打断park线程不会清空打断状态
主线程与守护线程
默认情况下Java进程需要所有的线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其他非守护线程结束了,即使守护线程代码还没有执行完也会强制结束
垃圾回收器就是一种守护线程
Tomcat中的Acceptor和Poller线程都是守护线程
public class DaemonDeom {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
System.out.println("t1");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1");
Thread t2 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
System.out.println("t2");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t2");
Thread t3 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(4);
System.out.println("守护线程");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t3");
//设置t3为守护线程
t3.setDaemon(true);
t1.start();
t2.start();
t3.start();
}
}
t1
t2
线程五种状态
操作系统层面
- 初始状态]仅是在语言层面创建了线程对象,还未与操作系统线程关联
- 可运行状态] (就绪状态)指该线程已经被创建(与操作系统线程关联),可以由CPU调度执行
- 运行状态]指获取了CPU时间片运行中的状态
当CPU时间片用完,会从[运行状态]转换至[可运行状态],会导致线程的上下文切换
- 阻塞状态]
如果调用了阻塞API,如BIO读写文件,这时该线程实际不会用到CPU,会导致线程上下文切换,进入
阻塞状态
等BIO操作完毕,会由操作系统唤醒阻塞的线程,转换至[可运行状态]
与[可运行状态]的区别是,对[阻塞状态]的线程来说只要它们一直不唤醒,调度器就一直不会考虑
调度它们
- 终止状态]表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态
java API层面
- NEW 线程刚被创建还没有调用start方法
- RUNNABLE 覆盖可运行 运行 阻塞状态 由于BIO导致的线程阻塞,在Java里无法区分
- BLOCKED WAITING TIME_WAITING
- TERMINATED
统筹算法
public class AsAWhole {
public static void paocha() throws InterruptedException {
System.out.println("开始泡茶");
TimeUnit.SECONDS.sleep(2);
}
public static void shaoshui() throws InterruptedException {
System.out.println("开始烧水");
TimeUnit.SECONDS.sleep(10);
}
public static void maicha() throws InterruptedException {
System.out.println("买茶");
TimeUnit.SECONDS.sleep(8);
}
public static void qingxi() throws InterruptedException {
System.out.println("清洗茶具");
TimeUnit.SECONDS.sleep(3);
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
try {
shaoshui();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread t2 = new Thread(()->{
try {
maicha();
qingxi();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
paocha();
}
}
开始烧水
买茶
清洗茶具
开始泡茶
共享模型之管程
共享带来的问题,俩个线程同时对初值为0的静态变量进行自增自减,各做500次,结果是什么?
public class MisTakeShareDemo {
public static int a = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
for (int i = 0; i < 500; i++) {
a++;
}
},"T1");
Thread t2 = new Thread(()->{
for (int i = 0; i < 500; i++) {
a--;
}
},"T2");
t1.start();
t2.start();
t1.join();
t2.join();
// TimeUnit.SECONDS.sleep(4);
System.out.println(a);
}
}
-74
问题分析
对于a++而言(a为静态变量),实际会产生四条字节码指令,而java内存模型完成静态变量的自增自减需要在主存中进行数据交换
如果单线程执行是没有问题的,但是多线程情况下指令会交错
临界区 Critical Section
一段代码内如果存在对共享资源的多线程读写操作,称这段代码为临界区
竞态条件 Race Condition
多个线程在临界区内执行,由于代码的执行序列不同而导致无法预测,称之为发生了竞态条件
synchronized
为避免临界区的竞态条件发生,有多种手段
- 阻塞式的解决方案:synchronized Lock
- 非阻塞式:原子变量
synchronized实际是使用对象锁保证了临界区内代码的原子性,临界区代码对外是不可分割的,不会被线程切换所打断
1.轻量级锁
轻量级锁的使用场景:如果一个对象虽然有多线程要加锁,但加锁的时间是错开的,那么可以使用轻量级锁进行优化
2.锁膨胀
如果在尝试加轻量级锁的过程中,CAS操作无法成功,这时需要进行锁膨胀,将轻量级锁变为重量级锁
3.自旋优化
重量级锁竞争的时候还可以使用自旋锁来进行优化,如果自旋成功(这时候持有锁的线程已经退出了同步代码块,释放了锁),这时当前线程就可以避免阻塞
4.偏向锁
轻量级锁在没有竞争时每次重入仍然需要执行CAS操作,可以使用偏向锁进行优化,只有第一次使用CAS将线程ID设置到对象的MarkWord头,之后发现这个线程ID是自己的表示没有竞争,不用重新CAS,以后只要不发生竞争,这个对象就归该线程所有
注意:调用对象的hashCode偏向锁会撤销,wait/notify也会撤销
5.锁消除
变量的线程安全分析
成员变量and静态变量
- 如果他们没有共享,则线程安全
- 如果他们被共享,根据他们的状态是否能够改变又分为俩种情况
- 如果只有读则线程安全,如果有写操作,则需要考虑线程是否安全
局部变量是安全的,但是局部变量的引用未必,如果对象逃离了方法的作用范围则需要考虑线程安全
常见线程安全类
- String
- Integer
- StringBuffer
- Random
- Vector
- HashTable
- java.util.concurrent
线程安全是指多个线程调用他们同一个实例的某个方法时是线程安全的,他们每个方法都是原子的,但是多个方法的组合不是原子的
卖票
public class SellingTickets {
private static volatile int num = 1000;
private static volatile int sum = 0;
// public static synchronized void selod(){
// int n = new Random().nextInt(5)+1;
// if(num>0&&num>=n){
// num -= n;
// sum+=n;
// System.out.println(Thread.currentThread().getName()+"卖出 "+n+" 张,余票:"+num+" sum "+sum);
// }
//
// }
static Lock lock = new ReentrantLock();
public static void selod(){
int n = new Random().nextInt(5)+1;
lock.lock();
try{
if(num>0&&num>=n){
num -= n;
sum+=n;
System.out.println(Thread.currentThread().getName()+"卖出 "+n+" 张,余票:"+num+" sum "+sum);
}
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
new Thread(()->{
for (int i = 0; i < 1000; i++) {
selod();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 1000; i++) {
selod();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 1000; i++) {
selod();
}
},"C").start();
}
}
转账
class Account{
int money ;
public Account(int money) {
this.money = money;
}
public void shiftTo(Account a,int m){
synchronized (Account.class){
if(a.rollOut(m)){
money += m;
}
}
}
public boolean rollOut(int m){
if(money>=0&&money>m){
money -= m;
return true;
}
return false;
}
}
public class TransferAccountsDemo {
public static void main(String[] args) throws InterruptedException {
Account a1 = new Account(2000);
Account a2 = new Account(2000);
Thread t1 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
int m = new Random().nextInt(100)+1;
a1.shiftTo(a2,m);
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
int m = new Random().nextInt(100)+1;
a2.shiftTo(a1,m);
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(a1.money);
System.out.println(a2.money);
System.out.println(a1.money+a2.money);
}
}
Monitor
监视器/管程
每个java对象都可以关联一个Monitor对象,如果使用synchronized给对象上锁之后,该对象的MarkWord中就被设置指向Monitor对象对象的指针
wait/notify
wait方法会释放对象的锁,进入WaitSet等待区,从而让其他线程有机会获得对象的锁,无限制等待知道notify
wait(long n) 有时限等待,到n毫秒后结束等待,或被notify
notify唤醒wait的线程
notifyAll唤醒全部
public class Wait_Notify {
static volatile int num = 0;
public synchronized void add() throws InterruptedException {
//判断 干活 通知
while(num==10){
System.out.println("库充满了");
this.wait();
}
num++;
System.out.println("生产"+num);
this.notifyAll();
}
public synchronized void del() throws InterruptedException {
while(num<=0){
System.out.println("没货了");
this.wait();
}
num--;
System.out.println("消费"+num);
this.notifyAll();
}
public static void main(String[] args) throws InterruptedException {
Wait_Notify wait_notify = new Wait_Notify();
new Thread(()->{
while(true) {
try {
wait_notify.add();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(()->{
while(true) {
try {
wait_notify.del();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
System.out.println(wait_notify.num);
}
}
Park&Unpark
它们是LockSupport类中的方法
LockSupport.park() 暂停当前线程
LockSpport.unpark(暂停线程对象)恢复某个线程的运行
先park后unpark
public class Park_unparkDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
System.out.println("进入线程");
LockSupport.park();
System.out.println("park后");
});
thread.start();
TimeUnit.SECONDS.sleep(1);
System.out.println("unpark");
LockSupport.unpark(thread);
System.out.println("unpark后");
}
}
进入线程
unpark
park后
unpark后
特点:
- 与Object的wait¬ify相比,wait必须配合Object Monitor一起使用,而park,unpark不必
- park&unpark是以线程为单位来阻塞和唤醒线程的,而notify只能随机唤醒,notifyAll唤醒所有,不精确
线程状态转换
1. NEW-->RUNNABLE
- 当调用start()方法时
2. RUNNABLE <-->WAITING
- 线程使用synchronized获取对象锁后,调用notify,notifyall,interrupt时
- 竞争锁成功,线程从WAITING-->RUNNABLE
- 竞争锁失败,线程从WAITING-->BLOCKED
3.RUNNABLE<-->WAITING
- 当前线程调用join方法时,当前线程从RUNNABLE-->WAITING,当前线程在线程对象的监视器上等待
- 线程运行结束,或调用了当前线程的interrupt时,当前线程从WAITING-->RUNNABLE
- 当前线程调用LockSupport.park()方法会让当前线程从RUNNABLE-->WAITING
- 调用LockSupport.unpark()方法或调用interrupt()会让目标线程从WAITING-->RUNNABLE
4.RUNNABLE<-->TIMED_WAITING
- 当线程获取对象锁后,调用wait(long n)方法时,t线程从RUNNABLE-->TIMED_WAITING
- 线程调用join(long n)方法时 RUNNABLE-->TIMED_WAITING
- 调用Thread.sleep(long n)当前线程从RUNNABLE-->TIMED_WAITING
- 如果等待超过了n毫秒,成功TIMED_WAITING-->RUNNABLE
- 失败 TIMED_WAITING-->BLOCKED
- 调用LockSupport.parkNanos(long nanos)或LockSupport.parkUntil(long millis)时
- 调用LockSupport.unpark
5.RUNNABLE<-->BLOCKED
- 线程用synchronized获取了对象锁时如果竞争失败 RUNNABLE<-->BLOCKED
- 持obj锁线程的同步代码块执行完毕,会唤醒该对象上所有的BLOCK的线程重新竞争,如果其中t线程竞争成功,从BLOCKED-->RUNABLE,其他失败的线程仍然CLOCKED
6.RUNNABLE<-->TERMINATED
- 当前线程执行完毕
多把锁
- 好处,可以增强并发度
- 坏处,如果一个县城需要同时获得多把锁,就容易发生死锁
活跃性
死锁
有这样的情况:一个线程需要同时获得多把锁就容易发生死锁
活锁
活锁出现在俩个线程相呼应改变对方的结束条件,最后谁也无法结束
ReentrantLock
相对于synchronized
- 可中断
- 可以设置超时时间
- 可以设置为公平锁
- 支持多个条件变量
与synchronized一样支持可重入
可重入
可重入指同一个线程如果首次获得了这把锁,那么他是这把锁的拥有着,因此有权利再次或者这把锁
如果不是,那么第二次获得锁自己会被锁挡住
多条件变量
synchronized中也有条件变量,就是waitSet休息室,当条件不满足进入waitSet等待,ReentrantLock的条件变量比synchronized强大之处在于支持多条件变量
注意判断条件
while (a != 0){
condition1.await();
}
- await前需要获得锁
- await执行后,会释放锁,进入conditionObject等待
- await的线程被唤醒(或打断、或超时)取重新竞争lock锁
- 竞争lock锁成功后,从await后继续执行
public class ConditionDemo {
static int a = 0;
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
new Thread(()->{
for (int i = 0; i < 5; i++) {
lock.lock();
try{
//判断
while (a != 0){
condition1.await();
}
//干活
a=1;
System.out.println("生产"+a);
//通知
condition2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}).start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
lock.lock();
try{
while (a !=1){
condition2.await();
}
//干活
a=0;
System.out.println("消费"+a);
condition1.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}).start();
}
}
共享模型
JMM javaMemory Model 它定义了内存,工作内存抽象概念,底层对应着CPU寄存器、缓存、硬件内存、CPU指令优化等
JMM体现以下几个方面:
- 原子性:保证指令不会受到线程上下文切换的影响
- 可见性:保证指令不会受到CPU缓存的影响
- 有序性:保证指令不会受到CPU指令并行化的影响
volatile(易变关键字)读写 后前
他可以用来修饰成员变量和静态变量,他可以避免线程从自己的工作缓存中找变量的值,必须到主存中获取他的值,不能保证原子性
防止指令重排
synchronized语句块既可以保证代码的原子性也同时保证代码块内变量的可见性,但缺点是synchronized是属于重量级操作,性能相对较低
原理:happens-before规则
Balking模式
public class BalkingDemo {
private static volatile boolean a = true;
static Lock lock = new ReentrantLock();
public static void init(){
lock.lock();
try{
if(a){
System.out.println("开始");
a = false;
return;
}
System.out.println("已经开始了");
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
new Thread(()->{
init();
}).start();
new Thread(()->{
init();
}).start();
new Thread(()->{
init();
}).start();
}
}
线程安全的单例
public final class Single {
private static Single single = null;
public static synchronized Single getSingle(){
if (single == null) {
single = new Single();
System.out.println("调用构造方法");
}
return single;
}
}
class Test{
public static void main(String[] args) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" "+Single.getSingle().hashCode());
},"A").start();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" "+Single.getSingle().hashCode());
},"B").start();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" "+Single.getSingle().hashCode());
},"C").start();
}
}
调用构造方法
A 805244208
B 805244208
C 805244208
共享模型~无锁CAS与volatile
发现错误 100个线程减10 最后1000会变成0
public class CASdemo {
private int num;
public CASdemo(int num) {
this.num = num;
}
public void mistake(){
List<Thread> list = new ArrayList<>();
long start = System.nanoTime();
for(int i = 0;i<100;i++){
list.add(new Thread(()->{
num-=10;
}));
}
list.forEach(Thread::start);
list.forEach(t ->{
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.nanoTime();
System.out.println(num+" "+(end-start)/100);
}
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
CASdemo mistake_cas = new CASdemo(1000);
mistake_cas.mistake();
}
}
}
可以发现,上面代码是不安全的,原因是指令交错
改进1 给对象加锁
public synchronized void improvement(){ //312259
List<Thread> list = new ArrayList<>();
long start = System.nanoTime();
for(int i = 0;i<100;i++){
list.add(new Thread(()->{
num-=10;
}));
}
list.forEach(Thread::start);
list.forEach(t ->{
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.nanoTime();
System.out.println(num+" "+(end-start)/100);
}
虽然线程安全了,但是时间消耗 312259
改进2 无锁
public void cas(){ //126244
List<Thread> list = new ArrayList<>();
long start = System.nanoTime();
for(int i = 0;i<100;i++){
list.add(new Thread(()->{
while (true){ //加循环是为了确保cas正确
int a = nums.get();
if (nums.compareAndSet(a,a-10)) {
break;
}
}
}));
}
list.forEach(Thread::start);
list.forEach(t ->{
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.nanoTime();
System.out.println(nums.get()+" "+(end-start)/100);
}
发现时间少了很多,效率更高了
CAS与volatile
cas
- CAS的底层是lock cmpxchg指令(X86架构),在单核COU和多核CPU下都能保证 比较交换的原子性
- 在多核状态下,某个核执行到带lock的指令时,CPU会让线程锁住,当这个线程把指令执行完毕,再开启总线,这个过程不会ibei线程的调度机制所打断,保证了多个线程对内存操作的准确性,时原子的
volatile
- 获取共享变量时为了保证该变量的可见性,需要使用volatile修饰
- 他可以用来修饰成员变量核静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取他的值
- 线程操作volatile变量都是之间操作主存,即一个线程对volatile变量的修改,对另一个线程可见
- volatile仅仅保证了共享变量的可见性,让其他线程能够看到最新值,但不能解决指令交错,不能保证原子性
- CAS必须借助volatile才能读取到共享变量的最新值来实现比较并交换的效果
为什么无锁效率高?
- 在无锁情况下,即使重试失败,线程始终在告诉运行,没有停歇,而synchronized会让线程在没有获得锁的时候发生上下文切换,进入阻塞
CAS特点
- 结合CA和volatile可以实现无锁并发,适合与线程数少,多核CPU的场景
- CAS是基于乐观锁的思想,最乐观的估计,不怕别的线程来修饰共享变量,就算改了也没关系,会重试
- synchronized基于悲观锁,最悲观的估计防着其他线程来修改共享变量
- 如果竞争激烈,重试必然会频繁发生,效率会受到影响
原子引用
AtomicReference
class People{
private int id;
private String name;
public People(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public People addId(People people){
people.setId((people.getId()+1));
return people;
}
@Override
public String toString() {
return "People{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
class ReferenceDemo{
private AtomicReference<People> atomicReference;
public ReferenceDemo(People people) {
this.atomicReference = new AtomicReference<>(people);
}
public void setMethod(People p){
while (true){
People people = atomicReference.get();
if(atomicReference.compareAndSet(people,p)){
return;
}
}
}
public void show(){
System.out.println(atomicReference.get().toString());
}
}
public class AtomicReferenceDemo {
public static void main(String[] args) throws InterruptedException {
People people = new People(1, "qyc");
ReferenceDemo referenceDemo = new ReferenceDemo(people);
{
new Thread(() -> {
for (int i = 0; i < 1000; i++) {
referenceDemo.setMethod(people.addId(people));
}
}).start();
new Thread(() -> {
for (int i = 0; i < 1000; i++) {
referenceDemo.setMethod(people.addId(people));
}
}).start();
new Thread(() -> {
for (int i = 0; i < 1000; i++) {
referenceDemo.setMethod(people.addId(people));
}
}).start();
}
TimeUnit.SECONDS.sleep(8);
referenceDemo.show();
}
}
People{id=3001, name='qyc'}
AtomicStampedReference
版本号
public class AtomicStampedReferenceDemo {
public static void main(String[] args) throws InterruptedException {
AtomicStampedReference<String> stampedReference = new AtomicStampedReference<>("A",0);
new Thread(()->{
String s1 = stampedReference.getReference();
int stamp = stampedReference.getStamp();
System.out.println("A->B "+stampedReference.getReference()+" "+stampedReference.getStamp()
+stampedReference.compareAndSet(s1,"B",stamp,stamp+1));
}).start();
new Thread(()->{
String s1 = stampedReference.getReference();
int stamp = stampedReference.getStamp();
System.out.println("B->A "+stampedReference.getReference()+" "
+stampedReference.getStamp()+stampedReference.compareAndSet(s1,"A",stamp,stamp+1));
}).start();
new Thread(()->{
String s1 = stampedReference.getReference();
int stamp = stampedReference.getStamp();
System.out.println("A->C "+stampedReference.getReference()+" "
+stampedReference.getStamp()+stampedReference.compareAndSet(s1,"C",0,stamp+1));
}).start();
}
}
A->B A 0 true
B->A B 1 true
A->C A 2 false
AtomicMarkableReference
是否变过
函数是接口
原子数组
- AtomicIntegerArray
- AtomicReferenceArray
- AtomicLongArray
不安全的数组
public static void main(String[] args) throws InterruptedException {
int a[] =new int [10];
Thread t1 = new Thread(()->{
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 1000; j++) {
a[i]++;
}
}
});
Thread t2 = new Thread(()->{
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 1000; j++) {
a[i]++;
}
}
});
t1.start();
t2.start();
t1.join();
t2.join();
for (int i = 0; i < 10; i++) {
System.out.print(a[i]+" ");
}
}
}
1251 1130 1163 1365 1343 1697 2000 2000 2000 2000
AtomicIntegerArray
public static void main(String[] args) throws InterruptedException {
AtomicIntegerArray a = new AtomicIntegerArray(10);
Thread t1 = new Thread(()->{
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 1000; j++) {
a.getAndIncrement(i);
}
}
});
Thread t2 = new Thread(()->{
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 1000; j++) {
a.getAndIncrement(i);
}
}
});
t1.start();
t2.start();
t1.join();
t2.join();
for (int i = 0; i < 10; i++) {
System.out.print(a.get(i)+" ");
}
}
}
原子累加器
LongAddr
public class LongAddrDemo {
static int n = 0;
public static void main(String[] args) throws InterruptedException {
LongAdder adder = new LongAdder();
Thread thread = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
adder.increment();
n++;
}
});
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
adder.increment();
n++;
}
});
thread.start();
thread1.start();
thread.join();
thread1.join();
System.out.println(adder.sum());
System.out.println(n);
}
}
2000
1755
日期线程安全类
DateTimeFormatter
ofPattern("yyyy-MM-dd")
不可变final
- 属性用final修饰保证了该属性是只读,不能修改的
- 类用final修饰保证了该类中方法不能被覆盖,防止子类无意间破坏不可变性
Fork/Join
概念:
- fork/join是jdk1.7之后加入的新的线程池的实现,它实现的是一种分治思想,适用于能够进行任务拆分的cpu密集型运算
- fork/join在分治的基础上加入了多线程,可以把每个任务的分解合并交给不同的线程来完成,进一步提升运算效率
- fork/join默认会创建与cpu核心数大小相同的线程池
使用:
提交给fork/join线程池的任务需要继承RecursiveTask(有返回值)或RecursiveAction(没有返回值)
public class Fork_Join extends RecursiveTask<Integer> {
private int star;
private int end;
public Fork_Join(int star, int end) {
this.star = star;
this.end = end;
}
@Override
protected Integer compute() {
if(star==end){
return star;
}
int min = (star+end)/2;
Fork_Join fork_join1 = new Fork_Join(star,min);
fork_join1.fork();
Fork_Join fork_join2 = new Fork_Join(min+1,end);
fork_join2.fork();
int result = fork_join1.join()+fork_join2.join();
System.out.println(result);
return result;
}
}
class Test01{
public static void main(String[] args) {
Fork_Join fork_join = new Fork_Join(1,100);
System.out.println("fork_join.compute() = " + fork_join.compute());
}
}
读写锁 StampedLock
读锁写锁配合戳使用,乐观读,验戳
class Demo{
private int a;
private StampedLock lock = new StampedLock();
public Demo(int a) {
this.a = a;
}
public int read(){
long stamp = lock.tryOptimisticRead();
if (lock.validate(stamp)){
System.out.println("验戳");
return a;
}
try{
stamp = lock.readLock();
System.out.println("读取");
return a;
}finally {
lock.unlockRead(stamp);
}
}
public void write(int b){
long stamp = lock.writeLock();
try{
a=b;
System.out.println("写");
}finally {
lock.unlockWrite(stamp);
}
}
}
public class StampedLockDemo {
public static void main(String[] args) {
Demo demo = new Demo(0);
Thread thread = new Thread(()->{
demo.write(100);
});
Thread thread1 = new Thread(()->{
System.out.print(demo.read()+" ");
});
thread.start();
thread1.start();
}
}