生产者与消费者
线程等待唤醒方法
线程通信 等待唤醒机制 生产者与消费者模式
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Object obj = new Object();
//生产者
new Thread(new Runnable() {
@Override
public void run() {
while(true){
synchronized (obj){
//如果有 等待
if(list.size() > 0){
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//没有
list.add("aaa");
System.out.println(list);
//唤醒消费的线程
obj.notify();
}
}
}
}).start();
//消费者
new Thread(new Runnable() {
@Override
public void run() {
while(true){
synchronized (obj){
//如果没元素 等待
if(list.size() == 0){
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果有元素
String s = list.remove(0);
System.out.println(list);
//唤醒生产的线程
obj.notify();
}
}
}
}).start();
}
}
Condition接口
Condition接口常用方法
java.util.concurrent.locks.Condition
方法
void await()
void signal()
void signalAll()
java.util.concurrent.locks.Lock
方法
Condition newCondition() 获取到绑定次Lock的Condition对象
常用实现类
ReentrantLock
生产者和消费者案例改进
public class Test02 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Lock l = new ReentrantLock();
Condition con1 = l.newCondition();// 生产线程 对象监视器
Condition con2 = l.newCondition();// 消费线程 对象监视器
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
l.lock();
if (list.size() > 0) {
try {
con1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add("abc");
System.out.println(list);
//唤醒消费线程
con2.signal();
l.unlock();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while(true){
l.lock();
if(list.size()==0){
try {
con2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.remove(0);
System.out.println(list);
//唤醒生产线程
con1.signal();
l.unlock();
}
}
}).start();
}
}
JMM模型
CPU多核并发缓存架构
主内存(RAM):也就是所谓的内存条,一般当数据写入电脑磁盘的时候需要先写入主 内存,然后再写入磁盘。
系统主线:连接系统cpu和主内存的系统主线,就比如台式电脑连接主板上连接cpu和内存条的主线。
CPU工作原理
- 首先CPU工作的时候,由控制单元充当大脑,负责协调。
- 让运算单元做运算的时候,会首先从最靠近CPU的寄存器(其实是和CPU一体的)上读取数据,在寄存器上有CPU运行的常用指令
- 如果寄存器上没有想要的数据,则就从三级缓存的L1级缓存中获取,如果L1取到数据了,会加载到寄存器中,再转输给CPU运算单元。
- 如果L1中没有,则从L2级缓存中读取,同理,如果没有,则从L3中取。
- 如果L3中也没有,这个时候,就比较麻烦了。要从主内存中取。
- 而从主内存中取的时候,会经过系统总线及内存总线,这时因受到总线的限制,速度会大大降低。而且会存在众多问题。
为什么要加入缓存
在计算机最开始的时候,cpu直接于主内存交换数据,由于cpu性能日新月异的增长,远高于主内存读写,而需要等待主内存,这样发挥不了多核cpu高性能的优势,于是便增加了cpu多级缓存.读入缓存,下次用时从缓存读取,效率会大大提高
为了解决一致性的问题
常用的方法是总线加锁或是缓存一致性协议-`MESI`
回顾JVM内存模型
Java多线程内存模型概述
Java内存模型(即Java Memory Model,简称JMM)本身是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。
JMM
java并发编程的三大特性
原子性
原子类AtomicInteger
java.util.concurrent.atomic.AtomicInteger
作用
可以用原子方式更新的 int 值。
构造方法
public AtomicInteger() 创建具有初始值 0 的新 AtomicInteger。
public AtomicInteger(int initialValue) 创建具有给定初始值的新 AtomicInteger。
方法
int getAndIncrement() 以原子方式将当前值加 1。 i++
int incrementAndGet() ++i
int getAndDecrement() i--
int decrementAndGet() --i;
int getAndAdd(int delta)
int addAndGet(int delta)
CAS无锁机制
CAS机制当中使用了3个基本操作数:一个当前内存值V、旧的预期值A、即将更新的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B,如果不相等,cas失败,返回当前内存值V.
悲观锁和乐观锁:
无论是悲观锁还是乐观锁,都是人们定义出来的概念,可以认为是一种思想.
悲观锁:简单来说操作系统会悲观的认为如果不严格同步线程调用那么一定会产生异常.所以互斥锁将会锁定资源,只供一个线程调用.而阻塞其他线程.因此这种同步机制也叫做悲观锁..
乐观锁:比如CAS不会对资源进行锁定,而且当线程需要修改共享资源时,总是会乐观的认为值没有被其他线程修改过,而自己主动尝试修改对应的值.相较于悲观锁,这种同步机制称为乐观锁.
可见性
如何保证可见性?
加锁,比如使用synchronized
JMM关于synchronized的两条规定:
1)线程解锁前,必须把共享变量的最新值刷新到主内存中
2)线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新获取最新的值
使用volatile关键字保证可见性
volatile缓存可见性实现原理
底层实现主要是通过汇编lock前缀指令,会锁住这块区域的缓存,并写回主内存.
1.会将当前处理器缓存的行数据立即写回系统内存
2.这个写回内存的操作导致CPU的缓存该内存地址的数值失效(MESI协议)
注意:volatile只能保证可见性,但是不能保证原子性,如果要保证原子性,请使用锁
volatile关键字有两个作用
保证可见性
禁止重排序
注意:锁也可以保证有序性,因为在代码块中,一次只有一个线程执行,系统重排对单线程执行是没有任何影响的.
有序性
一般来说,程序的执行顺序按照代码的先后顺序执行.但是处理器为了提高程序的效率,可能会对代码的执行顺序进行优化,它不保证程序中各个语句的执行先后顺序一致,但是保证程序的最终结果和代码顺序执行的结果一致.
面试题:DCL单例是否需要使用volatile关键字?
设计模式
前人遇到问题 总结出来的经验
单例设计模式
单例代表单个实例,保证一个类的对象永远只有一个!
饿汉式:
单例 饿汉式
优点
简单 多线程下没有任何问题
缺点
当类加载时 对象就会直接创建
如果一直不被使用 对象就白创建了
懒汉式(延迟加载):
单例 懒汉式
优点
延迟加载 什么时候调用方法 什么时候创建对象
缺点
多线程 代码有问题
DCL双检查锁机制单例(DCL:double checked locking)
Callable接口
1.定义实现类 实现Callable接口
2.重写call方法
3.开启线程
创建实现类对象
创建FutureTask对象 传入线程任务
public FutureTask(Callable c);
创建Thread对象 将FutureTask对象 传入
Thread(Runnable r)
FutureTask是Runnable实现类
调用start方法
今日总结:总结了近期所学,收获不错,努力学💪
明日计划:认真听,认真练