欢迎访问个人网站
保证可见性、不保证原子性、禁止指令重排
不可分割,完整性,也即某个正在做某个具体业务时,中间不可以被加塞或者被分割,需要整体完整,要么同时成功,要么同时失败。number++在多线程下时非安全的,如何不加synchronized解决使用原子变量AtomicInteger、使用锁
计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排,一般分为三种
单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致
处理在进行重排序时必须要考虑指令间的数据依赖性
多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性时无法确定的,结果无法预测
class ReSortSeqDemo{
int a = 0;
boolean flag = false;
public void method01(){
a = 1;
flag = true;
}
//多线程环境中线程交替执行,由于编译器优化重排的存在,
//两个线程中使用的变量能否保持一致性时无法确定的,结果无法预测
public void method02(){
if (flag) {
a = a+5;
System.out.println("******retValue" + a);
}
}
}
对volatile变量进行写操作时,会在写操作后加入一条store屏障指令,将工作内存中的共享变量赋值刷新到主内存 | 对volatile变量进行读操作时,会在读操作前加入一条load屏障指令,从主内存读取共享变量 |
---|---|
工作内存与主内存同步延迟现象导致的可见性问题可以使用synchronized或volatile关键字解决,它们都可以使一个线程修改后的变量立即对其它线程可见。对于指令重排导致的可见性问题和有序性问题,可以利用volatile的另外一个作用就是cpu禁止指令重排
单例模式DCL(双重检验机制)
代码
class SingleonDemo {
private static volatile SingleonDemo instance = null;
private SingleonDemo() { System.out.println(Thread.currentThread().getName());
}
public static SingleonDemo getInstance() {
//双重校验机制
if (instance == null) {
synchronized (SingleonDemo.class) {
if (instance == null) {
instance = new SingleonDemo();
}
}
}
return instance;
}
}
等等。。。。
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(5);
atomicInteger.compareAndSet(5,10);
atomicInteger.compareAndSet(5,1024);
System.out.println(atomicInteger.get());
}
####2. CAS底层原理?以及对UnSafe的理解
#####2.1 atomicInteger.getAndIncrement()
atomicInteger.getAndIncrement()方法源码:
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
引出来的一个问题:UnSafe类是什么?
a. UnSafe
是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,UnSafe相当于一个后门,基于该类可以直接操作特定内存的数据。
Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为Java中CAS操作的执行依赖与UnSafe类的方法。
注意Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都是直接调用操作系统底层资源执行相应任务。
b. 变量valueOffset,表示该变量在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的。
c. 变量value用volatile修饰,保证了多线程之间的内存可见性。
CAS的全称为Compare-And-Swap,他是一条cpu并发原语。它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。 CAS并发原语体现在JAVA语言中就是sun.misc.Unsafe类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮助我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的过程,并且原始的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。
var1 AtomicInteger对象本身。
var2 该对象值的引用地址。
var4 需要变动的数量。
var5 是用var1、var2找出的内存中真实的值。
用该对象当前的值与var5比较:
如果相同,更新var5+var4并返回true,
如果不同,继续取值然后再比较,直到更新完成。
#####2.4 CAS缺点
a. 循环时间长开销大
我们可以看到getAndAddInt方法执行时,有个do while
如果失败,会一直进行尝试。如果长时间不成功,可能给CPU带来很大开销。
b. 只能保证一个共享变量的原子操作
c. ABA问题(原子引用说明)
####3. 原子类Atomiclnteger的ABA问题,原子更新引用
#####3.1 ABA产生原因
CAS算法实现一个重要前提需要去除内存中某个时刻的数据并在当下时刻比较并替换,那么在这个时间差内会导致数据变化。比如一个线程一从内存位置V中取出A,这时另外一个线程二也从内存中取出A值,并且线程二进行了一些操作将值变成了B,然后线程二又将V位置的值变回为A,这时线程一进行CAS操作发现内存中V的值仍然是A,然后线程一操作成功。尽管线程一的CAS操作成功,但不代表这个过程就没问题。
/**
* @author Mr. Zhang
* @description
* @date 2019/4/18 15:34
* @website https://www.zhangguimin.cn
*/
public class CollectionSafety {
public static void main(String[] args) {
//List list = new ArrayList<>();
//List list = new Vector<>();
//List list = Collections.synchronizedList(new ArrayList<>());
//List list = new CopyOnWriteArrayList<>();
Set<String> set = new CopyOnWriteArraySet<>();
//Map map = new ConcurrentHashMap<>();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(set);
}, String.valueOf(i)).start();
}
/**
* 1 故障现象
* java.util.ConcurrentModificationException
* 2 导致原因
* 并发争抢修改导致
* 3 解决方案
* 3.1 new Vector<>();
* 3.2 Collections.synchronizedList(new ArrayList<>());
* ...Collections.synchronizedMap(new HashMap<>());
* ...Collections.synchronizedSet(new HashSet<>());
* 3.3 new CopyOnWriteArrayList<>();
* ...Set set = new CopyOnWriteArraySet<>();
* ...Map map = new ConcurrentHashMap<>();
* 4 优化建议
*/
}
}
/**
* @author Mr. Zhang
* @description 自旋锁
* @date 2019/4/19 9:39
* @website https://www.zhangguimin.cn
*/
public class SpinLockDemo {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void myLock() {
Thread thread = Thread.currentThread();
//System.out.println(thread.getName() + "\t come in");
while (!atomicReference.compareAndSet(null, thread)) {
System.out.println(thread.getName() + "\t get lock fail");
}
System.out.println(thread.getName() + "\t get lock success");
}
public void myUnLock() {
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread, null);
System.out.println(thread.getName() + "\t myUnLock");
}
public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(() -> {
spinLockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLockDemo.myUnLock();
}, "AAA").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
spinLockDemo.myLock();
spinLockDemo.myUnLock();
}, "BBB").start();
}
}
是什么?
独占锁:指该锁一次只能被一个线程锁持有。对ReentrantLock和Synchronized而言都是独占锁。
共享锁:指该锁可以被多个线程持有。
对ReentrantReadWriteLock其读锁是共享,其写锁是独占的。
读锁的共享可保证并发读是非常高效的,读写,写读,写写的过程是互斥的。
ReadWriteLockDemo
/**
* @author Mr. Zhang
* @description
* @date 2019/4/19 10:46
* @website https://www.zhangguimin.cn
*
* 多个线程同时读一个资源没任何问题,所以为了满足并发量,读取共享资源应该是可以同时进行。
* 但是
* 如果一个线程想去修改共享资源,就不应该再有其他线程可以对该资源进行读或写。
* 小总结:
* 读-读能共享
* 读-写不能共享
* 写-写不能共享
* 写操作:原子+独占,整个过程必须是一个完整的统一体,中间不许被分割,被打断
*/
public class ReadWriteLockDemo {
/**
* 读写锁
*/
private volatile Map<Integer, Integer> map = new HashMap<>();
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
public void put(Integer key, Integer val) {
rwl.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "开始写入");
map.put(key, val);
Thread.sleep(500);
System.out.println(Thread.currentThread().getName() + "写入完成");
} catch (InterruptedException e) {
} finally {
rwl.writeLock().unlock();
}
}
public void get() {
rwl.readLock().lock();
try {
map.entrySet().forEach(p -> {
System.out.println(Thread.currentThread().getName() + "开始读取:" + p.getValue());
});
} finally {
rwl.readLock().unlock();
}
}
public static void main(String[] args) {
ReadWriteLockDemo readWriteLockDemo = new ReadWriteLockDemo();
for (int i = 0; i < 5; i++) {
final int kv = i;
new Thread(() -> {
readWriteLockDemo.put(kv, kv);
},"AA").start();
}
for (int i = 0; i < 20; i++) {
new Thread(() -> {
readWriteLockDemo.get();
},"BB").start();
}
}
/**
* @author Mr. Zhang
* @description
* @date 2019/4/19 17:39
* @website https://www.zhangguimin.cn
*/
public class CountDownLatchDemo {
public static void main(String[] args) throws Exception {
CountDownLatch latch = new CountDownLatch(6);
List<Integer> list = Collections.synchronizedList(new ArrayList<>(6));
for (int i = 0; i < 6; i++) {
final Integer nu = i;
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(nu);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add(nu);
latch.countDown();
System.out.println(Thread.currentThread().getName() + "\t 分布计算:"+nu);
}).start();
}
latch.await();
System.out.println(Thread.currentThread().getName() + "\t 计算完毕"+list.stream().collect(Collectors.summingInt(Integer::intValue)));
}
}
/**
* @author Mr. Zhang
* @description
* @date 2019/4/19 17:39
* @website https://www.zhangguimin.cn
*/
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(6);
for (int i = 1; i <=6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "国,被灭");
latch.countDown();
}, CountryEnum.forEach_CountryEnum(i).getRetMessage()).start();
}
latch.await();
System.out.println(Thread.currentThread().getName() + "\t***************秦灭六国,统一华夏");
}
}
enum CountryEnum {
ONE(1, "齐"), TWO(2, "楚"), THREE(3, "燕"), FOUR(4, "赵"), FIVE(5, "魏"), SIX(6, "韩");
private Integer retCode;
private String retMessage;
CountryEnum(Integer retCode, String retMessage) {
this.retCode = retCode;
this.retMessage = retMessage;
}
public Integer getRetCode() {
return retCode;
}
public String getRetMessage() {
return retMessage;
}
public static CountryEnum forEach_CountryEnum(int index) {
CountryEnum[] values = CountryEnum.values();
for (CountryEnum value : values) {
if (index == value.getRetCode()) {
return value;
}
}
return null;
}
}
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* @author Mr. Zhang
* @description
* @date 2019/4/23 10:15
* @website https://www.zhangguimin.cn
*/
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("********召唤神龙");
});
for (int i = 1; i <= 7; i++) {
final int temp = i;
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "\t收集到第:" + temp + "龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* @author Mr. Zhang
* @description
* @date 2019/4/23 10:43
* @website https://www.zhangguimin.cn
*/
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 6; i++) {
new Thread(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "\t抢到车位");
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "\t停车3s后离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//释放资源
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}
抛出异常 | 当阻塞队列满时,再往队列里add插入元素会抛出java.lang.IllegalStateException: Queue full异常当阻塞队列空时,再往队列里remove移除元素会抛出NoSuchElementException |
---|---|
特殊值 | 插入方法,成功ture失败false移除方法,成功放回队列的元素,队列里没有就返回null |
一直阻塞 | 当阻塞队列满时,生产者线程继续往队列里put元素,队列会一直阻塞生产线程直到put数据or响应中断退出,当阻塞队列空时,消费者线程试图从队列里take元素,队列会一直阻塞消费者线程直到队列可用 |
超时退出 | 当阻塞队列满时,队列会阻塞生产者线程一定时间,超时后生产者线程会退出 |
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author Mr. Zhang
* @description
* @date 2019/4/24 16:13
* @website https://www.zhangguimin.cn
*/
public class ProdConsumerBlockQueueDemo {
public static void main(String[] args) throws InterruptedException {
MyResource resource = new MyResource(new ArrayBlockingQueue<>(10));
new Thread(() -> {
try {
resource.myProduct();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "prod").start();
new Thread(() -> {
try {
resource.myConsumer();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "Consumer").start();
TimeUnit.SECONDS.sleep(5);
resource.stop();
}
}
class MyResource {
private volatile Boolean flag = true;
private AtomicInteger atomicInteger = new AtomicInteger();
private BlockingQueue<String> blockingQueue = null;
public MyResource(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
}
public void myProduct() throws InterruptedException {
String data = null;
boolean retValue;
while (flag) {
data = atomicInteger.incrementAndGet() + "";
retValue = blockingQueue.offer(data, 2L, TimeUnit.SECONDS);
if (retValue) {
System.out.println(Thread.currentThread().getName() + "\t插入队列" + data + "成功");
} else {
System.out.println(Thread.currentThread().getName() + "\t插入队列" + data + "失败");
}
TimeUnit.SECONDS.sleep(1);
}
System.out.println(Thread.currentThread().getName() + "\t停止生产,flag=" + flag);
}
public void myConsumer() throws InterruptedException {
String result = null;
while (flag) {
result = blockingQueue.poll(2L, TimeUnit.SECONDS);
if (result == null || "".equals(result)) {
flag = false;
System.out.println(Thread.currentThread().getName() + "\t超过2s未取到,退出");
return;
}
System.out.println(Thread.currentThread().getName() + "\t消费队列" + result + "成功");
TimeUnit.SECONDS.sleep(1);
}
}
public void stop() {
this.flag = false;
}
}
架构说明
Java中线程池是通过Executor框架实现的,该框架中用到Excutor,Executors,ExecutorService,ThreadPoolExecutor这几个类。
编码实现
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
ThreadPoolExecutor threadPoolExecutor =
new ThreadPoolExecutor(3,5,5, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
1、是什么?
死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那他们都将无法推进下去,如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限资源而陷入死锁。
2、原产生死锁主要因
/**
* @author Mr. Zhang
* @description
* @date 2019/4/25 17:34
* @website https://www.zhangguimin.cn
*
* 死锁是指两个或两个以上的进程在执行过程中,
* 因争夺资源而造成的一种互相等待的现象,若无外力干涉那他们都将无法推进下去,
*/
public class DeadLockDemo {
public static void main(String[] args) {
String lockA = "lockA";
String lockB = "lockB";
new Thread(new HoldLockThread(lockA,lockB),"AAA").start();
new Thread(new HoldLockThread(lockB,lockA),"BBB").start();
}
}
class HoldLockThread implements Runnable{
private String lockA;
private String lockB;
public HoldLockThread(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run() {
synchronized (lockA){
System.out.println(Thread.currentThread().getName() + "\t自己持有:" + lockA + "\t尝试获得:" + lockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB){
System.out.println(Thread.currentThread().getName() + "\t自己持有:" + lockB + "\t尝试获得" + lockA);
}
}
}
}
1、原始构成
synchronized是关键字属于JVM层面,monitorenter(底层是通过monitor对象来完成,其实wait/notify等方法也依赖monitor对象只有再同步块或方法中才能调用wait/notify等方法)monitorexit。
lock是具体类(java.util.concurrent.locks.Lock)是api层面的锁。
2、使用方法
synchronized不需要用户手动释放锁,当synchronized代码执行完后系统会自动让线程释放对锁的占用。
ReentrantLock则需要用户手动释放锁,如果没主动释放锁,可能会造成死锁,需要使用lock()和unLock()方法配合try/finally语句块来完成
3、等待是否可中断
synchronized不可中断,除非抛出异常或正常运行完成。
ReentrantLock 可中断, 1)、设置超时方法 tryLock(long timeout,TimeUnit unit) 2)、lockInterruptibly()放代码块中,调用interrupt()方法可中断
4、加锁是否公平
synchronized非公平锁。
ReentrantLock两者都可以,默认为非公平锁(false)
5、锁绑定多个条件Condition
synchronized没有
ReentrantLock用来实现分组唤醒的线程,可以精确唤醒,而不像synchronized要么随机唤醒要么全部唤醒。