Day31
1.线程安全 -- 单例模式(懒汉式)
理解:该类的对象在整个项目中只创建一次(只实例化一次)
java
public class Test01 {
public static void main(String[] args) {
A a1 = A.getIntance();
A a2 = A.getIntance();
A a3 = A.getIntance();
A a4 = A.getIntance();
System.out.println(a1);
System.out.println(a2);
System.out.println(a3);
System.out.println(a4);
}
}
java
public class A {
private static A a;//私有化属性
private A(){}
public static A getIntance(){
if(a == null){
a = new A();
}
return a;
}
}
注意:单例模式(懒汉式)不是线程安全的
java
public class Test02 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程1:" + A.getIntance());
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程2:" + A.getIntance());
}
});
t1.start();
t2.start();
}
}
2.线程安全 -- 枚举单例模式(饿汉式)
理解:该类的对象在整个项目中只创建一次(只实例化一次)
java
public class Test01 {
public static void main(String[] args) {
A a1 = A.getIntance();
A a2 = A.getIntance();
A a3 = A.getIntance();
A a4 = A.getIntance();
System.out.println(a1);
System.out.println(a2);
System.out.println(a3);
System.out.println(a4);
}
}
java
public class A {
private static A a = new A();
private A(){}
public static A getIntance(){
return a;
}
public static void method(){
System.out.println("好好学习");
}
}
注意:枚举单例模式(饿汉式)是线程安全的
java
public class Test02 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程1:" + A.getIntance());
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程2:" + A.getIntance());
}
});
t1.start();
t2.start();
}
}
缺点:如果只调用了类里的静态方法,没用到单例对象,就是浪费空间
java
public class Test03 {
public static void main(String[] args) {
A.method();
}
}
注意:在工作中,都不会使用懒汉式,饿汉式
3.线程安全 -- 枚举单例模式(饿汉式)
理解:该类的对象在整个项目中只创建一次(只实例化一次)
把上面那个饿汉式的class改成enum
java
public class Test01 {
public static void main(String[] args) {
A a1 = A.getIntance();
A a2 = A.getIntance();
A a3 = A.getIntance();
A a4 = A.getIntance();
System.out.println(a1);
System.out.println(a2);
System.out.println(a3);
System.out.println(a4);
}
}
java
public enum A {
//public static final A a = new A();
a;
private A(){}
public static A getIntance(){
return a;
}
public static void method(){
System.out.println("好好学习");
}
@Override
public String toString() {
return String.valueOf(a.hashCode());
}
}
注意:枚举单例模式(饿汉式)是线程安全的
java
public class Test02 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程1:" + A.getIntance());
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程2:" + A.getIntance());
}
});
t1.start();
t2.start();
}
}
缺点:如果只调用了枚举里的静态方法,没用到单例对象,就是浪费空间
java
public class Test03 {
public static void main(String[] args) {
A.method();
}
}
4.线程安全 -- 双重检验锁的单例模式
理解:该类的对象在整个项目中只创建一次(只实例化一次)
java
public class Test01 {
public static void main(String[] args) {
A a1 = A.getIntance();
A a2 = A.getIntance();
A a3 = A.getIntance();
A a4 = A.getIntance();
System.out.println(a1);
System.out.println(a2);
System.out.println(a3);
System.out.println(a4);
}
}
volatile -- 防止指令重排
创建对象的过程:a.开辟空间 ----- new 对象() -- 0x001
b.调用构造方法 -- 初始化数据
c.将空间赋值给引用 -- 类型 引用 = 0x001
创建对象的步骤:a/b/c 或 a/c/b
注意:如果创建对象的步骤是a/c/b,多线程的情况下可能会导致获取的属性为null解决方案:使用volatile,防止指令重排,创建的步骤必须按照a/b/c
java
public class A {
private static volatile A a;
private A(){}
public static A getIntance(){
//项目中一般使用这个方法
//这个方法比下一个方法效率更高
//检测是否为空
if(a == null){
//加锁
synchronized (A.class) {
if(a == null){
a = new A();
}
}
}
return a;
}
// public static A getIntance(){
//
// if(a != null){
// return a;
// }
// synchronized (A.class) {
// if(a == null){
// a = new A();
// }
// }
// return a;
// }
}
注意:双重检验锁的单例模式是线程安全的
java
public class Test02 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程1:" + A.getIntance());
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程2:" + A.getIntance());
}
});
t1.start();
t2.start();
}
}
5.线程安全 -- ArrayList
前言:ArrayList是线程不安全的集合
解决方案1:使用Vector -- synchronized锁(不可取)
解决方案2:使用Collections的synchronizedList方法将ArrayList转换为线程安全的集合 -- synchronized锁(不可取)ArrayList list = new ArrayList<>();
List synchronizedList = Collections.synchronizedList(list);
解决方案3:使用CopyOnWriteArrayList -- lock锁CopyOnWriteArrayList list = new CopyOnWriteArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
总结:Vector和synchronizedList()底层使用synchronized(重量级锁),效率很低。项目中推荐使用CopyOnWriteArrayList
6.线程安全 -- 死锁
注意:多个线程中的多个锁对象被互相占用
死锁只是有概率发生
解决方案:尽可能的不要使用锁嵌套
java
public class Test01 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (KuaiZi.a) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (KuaiZi.b) {
System.out.println("哲学家1吃饭饭");
}
}
}
}, "哲学家1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (KuaiZi.b) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (KuaiZi.a) {
System.out.println("哲学家2吃饭饭");
}
}
}
}, "哲学家2");
t1.start();
t2.start();
}
}
class KuaiZi{ //筷子
public static Object a = new Object();
public static Object b = new Object();
}
7.线程安全 -- 可重入锁
理解:
就是一个线程不用释放,可以重复的获取一个锁n次,只是在释放的时候,也需要相应的释放n次。
简单来说:A线程在某上下文中获取了某锁,当A线程想要再次获取该锁时,不会因为锁已经被自己占用,而需要先等到锁的释放。
不是上第二次锁,而是在对象上做标记,哪个线程在用,如果还是这个线程就继续用注意:
synchronized同步代码块是可重入锁synchronized同步方法是可重入锁
Lock锁是可重入锁
java
public class Test01 {
public static void main(String[] args) {
//Task01 task = new Task01();
//Task02 task = new Task02();
Task03 task = new Task03();
Thread t = new Thread(task);
t.start();
}
}
java
public class Task01 implements Runnable{
//同步代码块
@Override
public void run() {
//this -->Task01 task = new Task01()里面的task
synchronized (this) {
System.out.println("获取到锁对象:" + this);
//不是上第二次锁,而是在对象上做标记,哪个线程在用,如果还是这个线程就继续用
synchronized (this) {
System.out.println("获取到锁对象:" + this);
System.out.println("释放锁对象:" + this);
}
System.out.println("释放锁对象:" + this);
}
}
}
方法
java
public class Task02 implements Runnable{
@Override
public void run() {
method1();
}
//方法
public synchronized void method1(){
System.out.println("获取到锁对象:" + this);
method2();
System.out.println("释放锁对象:" + this);
}
public synchronized void method2(){
System.out.println("获取到锁对象:" + this);
System.out.println("释放锁对象:" + this);
}
}
lock锁
java
public class Task03 implements Runnable{
private Lock lock = new ReentrantLock();
@Override
public void run() {
lock.lock();
System.out.println("获取到锁对象:" + this);
lock.lock();
System.out.println("获取到锁对象:" + this);
lock.unlock();
System.out.println("释放锁对象:" + this);
lock.unlock();
System.out.println("释放锁对象:" + this);
}
public synchronized void method1(){
System.out.println("获取到锁对象:" + this);
method2();
System.out.println("释放锁对象:" + this);
}
public synchronized void method2(){
System.out.println("获取到锁对象:" + this);
System.out.println("释放锁对象:" + this);
}
}
8.生产者消费者模型
最终目的:生产一个消费一个
分析:产品类 -- Phone(brand、price)
生产者线程 -- Produce
消费者线程 -- Consumer
为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为
消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为
Object类的等待和唤醒方法wait():导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
notify():唤醒正在等待对象监视器的单个线程
notifyAll():唤醒正在等待对象监视器的所有线程
8.1 一个生产者一个消费者的情况
注意:一个生产者一个消费者的情况
步骤:1.生产者线程、消费者线程不断的操作同一个产品对象(死循环)
脏数据:
null -- 0.0
华为 -- 0.0
2.两个产品之间来回切换(目的:放大步骤1的问题)
脏数据:
null -- 0.0
华为 -- 0.0
小米 -- 3999.0
华为 -- 1999.0
脏数据出现原因:设置完品牌后,还没来得及设置价格,就被消费者线程抢到CPU资源
解决方案:生产者线程设置完品牌+价格后,消费者线程才能执行自己的代码 -- 加锁(这两个要互斥)
互斥:两个事件不能同时发生
(生产者,消费者都加锁,互斥住)
3.生产一个消费一个
产品类加一个库存的属性 -- boolean store = false
生产者线程在每次生产之前都得判断是否有库存,有就等待(等待消费者线程消费后再生产),没有就生产
消费者线程在每次消费之前都得判断是否有库存,有就消费,没有就等待(等待生产者线程生产后再消费)
生产者线程和消费者线程互相唤醒
wait()等待:1.释放锁资源
2.在对象监视器(phone)中记录当前线程被等待(阻塞)
3.当前线程进入到阻塞状态
notify()唤醒:唤醒对象监视器中任意一个线程
java
public class Phone {
private String brand;
private double price;
private boolean store;//库存
//无参构造,有参构造,get,set方法省略
@Override
public String toString() {
return "Phone [brand=" + brand + ", price=" + price + "]";
}
}
测试类
java
public class Test01 {
public static void main(String[] args) {
Phone phone = new Phone();
Produce p = new Produce(phone);
Consumer c = new Consumer(phone);
p.start();
c.start();
}
}
生产者线程
第一次进入生产者线程,没有生产出产品,走不了上面的if循环;就只能走对象监视器,去生产一个产品;
第二次进入生产者线程,就有了产品,进入上面的if循环,就wait等待了,当前线程进入到阻塞状态,然后走消费者线程,有库存就唤醒对象监视器中任意一个线程,解锁
然后就是生产者线程,消费者线程去抢资源
java
//生产者线程
public class Produce extends Thread{
private Phone phone;
public Produce(Phone phone) {
this.phone = phone;
}
@Override
public void run() {
boolean flag = true;
while(true){
synchronized(phone){
if(phone.isStore()){//true就等待,没有就生产
try {
/**
* 等待:
* 1.释放锁资源
* 2.在对象监视器(phone)中记录当前线程被等待(阻塞)
* 3.当前线程进入到阻塞状态
*/
phone.wait();//phone记录生产者线程阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//对象监视器
if(flag){
phone.setBrand("华为");
phone.setPrice(3999);
}else{
phone.setBrand("小米");
phone.setPrice(1999);
}
flag = !flag;
phone.setStore(true);
phone.notify();//唤醒:唤醒对象监视器中任意一个线程
}
}
}
}
消费者线程
java
//消费者线程
public class Consumer extends Thread{
private Phone phone;
public Consumer(Phone phone) {
this.phone = phone;
}
@Override
public void run() {
while(true){
synchronized(phone){
if(!phone.isStore()){ //有库存就不进入,没有库存就等待
try {
/**
* 等待:
* 1.释放锁资源
* 2.在对象监视器(phone)中记录当前线程被等待(阻塞)
* 3.当前线程进入到阻塞状态
*/
phone.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(phone.getBrand() + " -- " + phone.getPrice());
phone.setStore(false);
phone.notify();//唤醒:唤醒对象监视器中任意一个线程
}
}
}
}
多个线程去操作同一资源,多个线程的功能是一样的,就使用买票的案例去套
多个线程去操作同一资源,多个线程的功能是不一样的,就使用生产者消费者模型或仓储模型去套
8.2 多个生产者多个消费者的情况
注意:多个生产者多个消费者的情况
多个的情况下,不用if判断,而是用while判断
notifyAll():唤醒:唤醒对象监视器中所有线程
java
public class Test01 {
public static void main(String[] args) {
Phone phone = new Phone();
Produce p1 = new Produce(phone);
Produce p2 = new Produce(phone);
Consumer c1 = new Consumer(phone);
Consumer c2 = new Consumer(phone);
p1.start();
p2.start();
c1.start();
c2.start();
}
}
手机类
java
public class Phone {
private String brand;
private double price;
private boolean store;//库存
//无参构造,有参构造,get,set方法省略
@Override
public String toString() {
return "Phone [brand=" + brand + ", price=" + price + "]";
}
}
生产者线程
java
//生产者线程
public class Produce extends Thread{
private Phone phone;
public Produce(Phone phone) {
this.phone = phone;
}
private static boolean flag = true;
@Override
public void run() {
//多个的情况下,不用if判断,而是用while判断
while(true){
synchronized(phone){
while(phone.isStore()){
try {
/**
* 等待:
* 1.释放锁资源
* 2.在对象监视器(phone)中记录当前线程被等待(阻塞)
* 3.当前线程进入到阻塞状态
*/
phone.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(flag){
phone.setBrand("华为");
phone.setPrice(3999);
}else{
phone.setBrand("小米");
phone.setPrice(1999);
}
flag = !flag;
phone.setStore(true);
phone.notifyAll();//唤醒:唤醒对象监视器中所有线程
}
}
}
}
消费者线程
java
//消费者线程
public class Consumer extends Thread{
private Phone phone;
public Consumer(Phone phone) {
this.phone = phone;
}
@Override
public void run() {
while(true){
synchronized(phone){
while(!phone.isStore()){//有库存就不进入
try {
/**
* 等待:
* 1.释放锁资源
* 2.在对象监视器(phone)中记录当前线程被等待(阻塞)
* 3.当前线程进入到阻塞状态
*/
phone.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(phone.getBrand() + " -- " + phone.getPrice());
phone.setStore(false);
phone.notifyAll();//唤醒:唤醒对象监视器中所有线程
}
}
}
}
9.仓储模型
需求:
生产者线程不断的生产蛋糕(将蛋糕对象添加到仓库)
消费者线程不断的消费蛋糕(将蛋糕对象从仓库中取出)
先生产的蛋糕,先卖出
分析:1.蛋糕类、仓库类、生产者线程、消费者线程
2.仓库类里有一个存放蛋糕对象的集合 -- LinkedList(队列模式--先进先出,removeFirst遍历)
仓储模型与消费者模型不同的地方:生产者消费者模型:重点加锁的逻辑放在线程中
仓储模型:重点加锁的逻辑是放在线程外
9.1 一个生产者线程一个消费者线程的情况
注意:一个生产者线程一个消费者线程的情况
意识:多个线程抢资源势必会出现线程安全问题;多个线程使用同一资源,必须加锁!!!锁对象是谁,对象监视器就可以用谁
java
public class Test01 {
public static void main(String[] args) {
Store store = new Store(20);
Produce p = new Produce(store);
Consumer c = new Consumer(store);
p.start();
c.start();
}
}
蛋糕类
java
public class Cake {
private String brand;
private String datetime;
//无参构造,有参构造,get,set方法省略
@Override
public String toString() {
return "Cake [brand=" + brand + ", datetime=" + datetime + "]";
}
}
仓库类
java
//仓库类
public class Store {
private int curCapacity;//当前容量
private int maxCapacity;//最大容量
private LinkedList<Cake> list;//蛋糕容器
public Store(int maxCapacity) {
this.maxCapacity = maxCapacity;
list = new LinkedList<>();//初始化蛋糕容器
}
//入库(此方法被生产者线程不断的调用)
public synchronized void push(Cake cake){
if(curCapacity >= maxCapacity){//当前容量>最大容量
try {
//锁对象是谁,对象监视器就可以用谁
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add(cake);
curCapacity++;
System.out.println("入库 - 当前容量为:" + curCapacity);
this.notify();
}
//出库(此方法被消费者线程不断的调用)
public synchronized Cake pop(){//删除
if(curCapacity <= 0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Cake cake = list.removeFirst();
curCapacity--;
System.out.println("出库 - 当前容量为:" + curCapacity + ",卖出的蛋糕为:" + cake);
this.notify();
return cake;
}
}
生产者线程
java
public class Produce extends Thread{
private Store store;
public Produce(Store store) {
this.store = store;
}
@Override
public void run() {//生产
while(true){
Cake cake = new Cake("桃李", LocalDateTime.now().toString());
store.push(cake);
}
}
}
消费者线程
java
public class Consumer extends Thread{
private Store store;
public Consumer(Store store) {
this.store = store;
}
@Override
public void run() {
while(true){
store.pop();
}
}
}
9.2 多个生产者线程多个消费者线程的情况
注意:多个生产者线程多个消费者线程的情况
多个的情况下,不用if判断,而是把其中的if判断换成while判断和使用notifyAll()
java
public class Test01 {
public static void main(String[] args) {
Store store = new Store(20);
Produce p1 = new Produce(store);
Produce p2 = new Produce(store);
Consumer c1 = new Consumer(store);
Consumer c2 = new Consumer(store);
p1.start();
p2.start();
c1.start();
c2.start();
}
}
蛋糕类
java
public class Cake {
private String brand;
private String datetime;
//无参构造,有参构造,get,set方法省略
@Override
public String toString() {
return "Cake [brand=" + brand + ", datetime=" + datetime + "]";
}
}
仓库类
java
//仓库类
public class Store {
private int curCapacity;//当前容量
private int maxCapacity;//最大容量
private LinkedList<Cake> list;//蛋糕容器
public Store(int maxCapacity) {
this.maxCapacity = maxCapacity;
list = new LinkedList<>();
}
//入库(此方法被生产者线程不断的调用)
public synchronized void push(Cake cake){
while(curCapacity >= maxCapacity){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add(cake);
curCapacity++;
System.out.println("入库 - 当前容量为:" + curCapacity);
this.notifyAll();
}
//出库(此方法被消费者线程不断的调用)
public synchronized Cake pop(){
while(curCapacity <= 0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Cake cake = list.removeFirst();
curCapacity--;
System.out.println("出库 - 当前容量为:" + curCapacity + ",卖出的蛋糕为:" + cake);
this.notifyAll();
return cake;
}
}
生产者线程
java
public class Produce extends Thread{
private Store store;
public Produce(Store store) {
this.store = store;
}
@Override
public void run() {
while(true){
Cake cake = new Cake("桃李", LocalDateTime.now().toString());
store.push(cake);
}
}
}
消费者线程
java
public class Consumer extends Thread{
private Store store;
public Consumer(Store store) {
this.store = store;
}
@Override
public void run() {
while(true){
store.pop();
}
}
}
加锁一般使用Lock,不用synchronized,因为效率低
简答题
volatile关键字的作用?
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1.保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2.禁止进行指令重排序。
volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的。
volatile仅能实现变量的修改可见性,并不能保证原子性;synchronized则可以保证变量的修改可
见性和原子性。
volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。
使用 Volatile 一般用于 状态标记量 和 单例模式的双检锁
2.notify()和notifyAll()有什么区别?notify可能会导致死锁,而notifyAll则不会任何时候只有一个线程可以获得锁,也就是说只有一个线程可以运行synchronized 中的代码 使用notifyall,可以唤醒 所有处于wait状态的线程,使其重新进入锁的争夺队列中,而notify只能唤醒一个。
notify() 是对notifyAll()的一个优化,但它有很精确的应用场景,并且要求正确使用。不然可能导致死锁。正确的场景应该是 WaitSet中等待的是相同的条件,唤醒任一个都能正确处理接下来的事项,如果唤醒的线程无法正确处理,务必确保继续notify()下一个线程,并且自身需要重新回到WaitSet中
3.为什么wait, notify和notifyAll这些方法不在thread类里面?明显的原因是JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。
4.为什么wait和notify方法要在同步块中调用?1.只有在调用线程拥有某个对象的独占锁时,才能够调用该对象的wait(),notify()和notifyAll()方法。
2.如果你不这么做,你的代码会抛出IllegalMonitorStateException异常。
3.还有一个原因是为了避免wait和notify之间产生竞态条件。
wait()方法强制当前线程释放对象锁。这意味着在调用某对象的wait()方法之前,当前线程必须已经获得该对象的锁。因此,线程必须在某个对象的同步方法或同步代码块中才能调用该对象的wait()方法。
在调用对象的notify()和notifyAll()方法之前,调用线程必须已经得到该对象的锁。因此,必须在某个对象的同步方法或同步代码块中才能调用该对象的notify()或notifyAll()方法。
调用wait()方法的原因通常是,调用线程希望某个特殊的状态(或变量)被设置之后再继续执行。调用 notify()或notifyAll()方法的原因通常是,调用线程希望告诉其他等待中的线程:"特殊状态已经被设置"。这个状态作为线程间通信的通道,它必须是一个可变的共享状态(或变量)。
5.乐观锁与悲观锁1.何谓悲观锁与乐观锁
悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这
样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后 再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中 synchronized 和 ReentrantLock 等独占锁就是悲观锁思想的实现。
乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多 读的应用类型,这样可以提高吞吐量 ,像数据库提供的类似于write_condition机制,其实都是提供的 乐观锁。在Java中 java.util.concurrent.atomic 包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
2.两种锁的使用场景
从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比 较少的情况下(多读场景) ,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。
总结
1.线程安全 --- 单例模式
懒汉式
饿汉式
枚举饿汉式
双重检测单例模式 --- (项目中使用,注意volatile的含义)
2.线程安全 --- ArrayListVector --- synchronized
Collections.synchronizedList() --- synchronized
CopyOnWriteArrayList --- Lock
3.可重入锁
4.死锁5.生产者消费者模型 -- wait()/notify()/notifyAll()
6.仓储模型 -- wait()/notify()/notifyAll()
作业:1.知识点梳理文档
2.代码3遍
3.生产者消费者模型、仓储模型默写一遍(拿一张纸来写,笔试考过)
4.提升作业(至少选择两道):
可重入锁和不可重入锁的md文档(概念、代码)
乐观锁和悲观锁的md文档(概念)
公平锁和非公平锁的md文档(概念)
互斥和共享锁的md文档(概念)
synchronized锁的升级和膨胀的md(概念、代码)
CAS和CAS的ABA问题(概念、代码)
AQS的md文档(概念、代码)