Day31
1.线程安全 – 单例模式(懒汉式)
理解:该类的对象在整个项目中只创建一次(只实例化一次)
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);
}
}
public class A {
private static A a;//私有化属性
private A(){}
public static A getIntance(){
if(a == null){
a = new A();
}
return a;
}
}
注意:单例模式(懒汉式)不是线程安全的
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.线程安全 – 枚举单例模式(饿汉式)
理解:该类的对象在整个项目中只创建一次(只实例化一次)
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);
}
}
public class A {
private static A a = new A();
private A(){}
public static A getIntance(){
return a;
}
public static void method(){
System.out.println("好好学习");
}
}
注意:枚举单例模式(饿汉式)是线程安全的
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();
}
}
缺点:如果只调用了类里的静态方法,没用到单例对象,就是浪费空间
public class Test03 {
public static void main(String[] args) {
A.method();
}
}
注意:在工作中,都不会使用懒汉式,饿汉式
3.线程安全 – 枚举单例模式(饿汉式)
理解:该类的对象在整个项目中只创建一次(只实例化一次)
把上面那个饿汉式的class改成enum
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);
}
}
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());
}
}
注意:枚举单例模式(饿汉式)是线程安全的
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();
}
}
缺点:如果只调用了枚举里的静态方法,没用到单例对象,就是浪费空间
public class Test03 {
public static void main(String[] args) {
A.method();
}
}
4.线程安全 – 双重检验锁的单例模式
理解:该类的对象在整个项目中只创建一次(只实例化一次)
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
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;
// }
}
注意:双重检验锁的单例模式是线程安全的
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.线程安全 – 死锁
注意:多个线程中的多个锁对象被互相占用
死锁只是有概率发生
解决方案:尽可能的不要使用锁嵌套
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锁是可重入锁
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();
}
}
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);
}
}
}
方法
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锁
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()唤醒:唤醒对象监视器中任意一个线程
public class Phone {
private String brand;
private double price;
private boolean store;//库存
//无参构造,有参构造,get,set方法省略
@Override
public String toString() {
return "Phone [brand=" + brand + ", price=" + price + "]";
}
}
测试类
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等待了,当前线程进入到阻塞状态,然后走消费者线程,有库存就唤醒对象监视器中任意一个线程,解锁然后就是生产者线程,消费者线程去抢资源
//生产者线程
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();//唤醒:唤醒对象监视器中任意一个线程
}
}
}
}
消费者线程
//消费者线程
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():唤醒:唤醒对象监视器中所有线程
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();
}
}
手机类
public class Phone {
private String brand;
private double price;
private boolean store;//库存
//无参构造,有参构造,get,set方法省略
@Override
public String toString() {
return "Phone [brand=" + brand + ", price=" + price + "]";
}
}
生产者线程
//生产者线程
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();//唤醒:唤醒对象监视器中所有线程
}
}
}
}
消费者线程
//消费者线程
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 一个生产者线程一个消费者线程的情况
注意:一个生产者线程一个消费者线程的情况
意识:多个线程抢资源势必会出现线程安全问题;多个线程使用同一资源,必须加锁!!!锁对象是谁,对象监视器就可以用谁
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();
}
}
蛋糕类
public class Cake {
private String brand;
private String datetime;
//无参构造,有参构造,get,set方法省略
@Override
public String toString() {
return "Cake [brand=" + brand + ", datetime=" + datetime + "]";
}
}
仓库类
//仓库类
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;
}
}
生产者线程
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);
}
}
}
消费者线程
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()
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();
}
}
蛋糕类
public class Cake {
private String brand;
private String datetime;
//无参构造,有参构造,get,set方法省略
@Override
public String toString() {
return "Cake [brand=" + brand + ", datetime=" + datetime + "]";
}
}
仓库类
//仓库类
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;
}
}
生产者线程
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);
}
}
}
消费者线程
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修饰之后,那么就具备了两层语义:
禁止进行指令重排序。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.synchronized 和 Lock 有什么区别?
synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。
synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。
通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
6.在 Java 程序中怎么保证多线程的运行安全?
一:使用安全类,比如 Java. util. concurrent 下的类。
二:使用自动锁 synchronized。
三:使用手动锁 Lock。
7.请说出与线程同步以及线程调度相关的方法。
(1) wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
(2)sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此 方法要处理InterruptedException 异常;
(3)notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且与 优先级无关;
(4)notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所 有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;
8.同步方法和同步块,哪个是更好的选择?
同步块是更好的选择,因为它不会锁住整个对象(当然你也可以让它锁住整个对象)。同步方法会锁住整个对象,哪怕这个类中有多个不相关联的同步块,这通常会导致他们停止执行并需要等待获得这个对象上的锁。
同步块更要符合开放调用的原则,只在需要锁住的代码块锁住相应的对象,这样从侧面来说也可以避免死锁。
请知道一条原则:同步的范围越小越好。
总结
1.线程安全 — 单例模式
懒汉式
饿汉式
枚举饿汉式
双重检测单例模式 — (项目中使用,注意volatile的含义)
2.线程安全 — ArrayList
Vector — synchronized
Collections.synchronizedList() — synchronized
CopyOnWriteArrayList — Lock3.可重入锁
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文档(概念、代码)