目录
一、复习回顾
进程
线程
创建线程
常用方法
线程状态及生命周期
二、多线程
优点:
缺点:
三、并行执行与并发执行
并发执行:
并行执行:
四、并发编程核心问题(缺点)
一、不可见性
二、乱序性
三、非原子性
三、Volatile关键字
四、如何保证原子性
一、锁
二、原子变量
五、原子类
六、CAS
CAS 机制
会产生ABA问题:
如何解决ABA问题:
另:锁分类:
乐观锁/悲观锁
乐观锁:
悲观锁:
可重入锁
读写锁(ReentrantReadWriteLock)
分段锁
自旋锁
共享锁/独占锁
共享锁:
独占锁:
公平锁/非公平锁
公平锁:
非公平锁:
偏向锁/轻量级锁/重量级锁
无锁:
偏向锁:
轻量级锁:
重量级锁:
进程就是运行时程序,是计算分配内存的资源的最小单位。
线程是进程中的一个执行单元,是cpu执行的最小单位。
1.类 继承 Thread 重写run()
2.实现Runnable接口 重写run(), 再创建Thread对象,去执行任务
3.实现Callable接口重写call(),再创建Thread对象,去执行任务call()可以有返回值,可以抛出异常
4.线程池
run(),start(),join(),yield(),sleep(),wait(),notify()
提过程序响应速度,可以多线程完成自己工作,提高了硬件设备利用率。
多线程同时访问共享数据,会出现资源抢夺。
是在一个时间段内,多个线程依次执行。
两个或多个在同一时间点上一起执行。
一个线程对共享数据修改,另一个线程立即看见,称为可见性。
由于想让程序响应处理速度更快java内存模型设计有主内存和工作内存(线程使用的内存).
线程中不能直接对主内存中的数据进行操作,必须将主内存数据加载到工作内存(本地内存), 这样在
多核cpu下就会产生不可见性.我们的目标就是要做到可见性。
为了优化性能,有时候会改变程序中语句的先后顺序。
volatile 可以解决不可见性, volatile修饰的变量在一个线程修改后,对其他线程立即可见还可以
解决乱序性, volatile修饰的变量在执行时禁止指令重排序,但是不能解决非原子性问题,volatile的
底层实现。
一个或多个操作在 CPU 执行的过程中整体执行不被中断,称为原子性。
如何解决非原子性问题?加锁,互斥的,A线程执行时加锁,此时其他线程就不能执行了。
一个共享变量(类的成员变量、类的静态成员变量)被 volatile 修饰之后:
1. 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2. 禁止进行指令重排序。
3. volatile 不能保证对变量操作的原子性。
锁是一种通用的技术方案,Java 语言提供的 synchronized 关键字,就是锁的一种实现。
加锁是一种阻塞式方式实现。
synchronized 是独占锁,一定能保证原子性,因为被synchronized 修饰某段代码 后,无论是单核 CPU 还是多核 CPU,只有一个线程能够执行该代码,所以一定能保证原子操作。synchronized 也能够保证可见性和有序性。
JUC(java.util.concurrent 包)中有 locks 包和 atomic 包,它们可以解决原子性问题。
原子变量是非阻塞式方式实现。
在java.util.concurrent 包下面提供一些类,可以在不加锁的情况下,实现++操作的原子性,这
这些类称为原子类 AtomicInteger。
原子类的原子性是通过 volatile + CAS 实现原子操作的。适合在低并发的情况下使用。
是一种不加锁的实现(乐观锁),采用自旋的思想,当一个线程要进行++操作时,可以
先从内存中取出共享变量,记录一个预估值,然后在工作内存中改变共享变量,当将修改后的变量
写会主内存前,要判断预估值和主内存中的值是否一致,如果一致说明,没有其他线程修改过,如
果不一致,说明其他线程修改过,需要重新获取主内存的共享变量,重复之前的操作。
原子类内部实现使用了不加锁的CAS机制。线程不会被阻塞,所有的线程都会不不断的重试进
行操作,在访问量大的情况下,会导致cpu消耗过高。
一个线程获取了内存值为A,当线程修改后,要写回内存时,已经有线程改变了内存值B,又
有线程将内存值改回到与当前线程预估值相同的值 A
使用有版本号的原子类。
乐观锁其实就是不加锁, 乐观的认为不加锁的并发操作是没有问题的,并发修改时,进行比较,
然后满足条件进行更新,否则再次比较。适合读多写少的情况。
悲观锁认为不加锁的是肯定会出问题的,使用java中提供的各种锁实现加锁。适合写操作比较
多的情况。
当一个线程进入到一个同步方法中,然后在此方法中要调用,另一个同步方法,而且两个方法共
用同一把锁,此时线程是可以进入到另一个同步方法中的。
可以实现写锁和读锁 ,读写可以用一个锁实现,都是读的时候,多个线程可以共享这把锁(可以同
时进入),一旦有写的操作,那么就要一个一个操作。读读共享,读写互斥,写写互斥。
jdk8之后,去除了真正的分段锁,现在的分段锁不是锁,是一种实现的思想。将锁的粒度更小化。
例如:ConcurrentHashMap 没有给方法加锁,用hash表中的第一个节点当做锁的,这样就可以
有多把锁,提高了并发操作效率。
自旋锁也不是锁,是获取锁的方式。
例如: 原子类,需要改变内存中的变量,需要不断尝试;
synchronized加锁,其他线程不断尝试获取锁 。
多个线程共享一把锁, 读写锁中的读锁,都是读操作时,多个线程可以共用。
互斥锁。该锁一次只能被一个线程所持有。
例如:synchronized ReentrantLock 读写锁的写锁。
按照请求锁的顺序分配,谁先来,先获得锁。
不按照请求锁的顺序分配,谁抢到 谁获得 。例如:synchronized。
但ReentrantLock底层可以设置为公平锁,也可以设置为非公平锁。 默认非公平锁。
偏向锁/轻量级锁/重量级锁是针对synchronized锁的状态。
没有任何线程使用锁对象。
就是当前只有一个线程访问,在对象头Mark Word中记录线程id,下次此线程访问时,可以直接
获取锁。
当锁的状态为偏向锁时,此时继续有线程来访问,升级为轻量级锁,会让线程以自旋的方式获取
锁,,先程不会阻塞。
当锁的状态为轻量级锁时,线程自旋获取锁的次数到达一定数量时,锁的状态升级为重量级
锁,会让自旋次数多的线程进入到阻塞状态。 原因是访问大时,线程都自旋获得锁,cpu消耗大。
以上的状态设计,是java为了优化synchronized锁
轻量级锁自旋
重量级锁需要操作系统调度