多线程三

87 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

死锁

让我们先来看看死锁的代码是怎么样的。
synchronized public void add(String a){
    synchronized(this){
        System.out.println(a);
    }
}
这个代码怎么理解呢?
第一步,进入外层锁,这个是正常的。
第二步,要进入内层锁,这个是不行的,为什么呢?
因为这个是同一把锁,里面要能锁,必须是外面释放锁才可以,可是外面释放锁必须是里面的内容执行完,才可以释放。
所以这样就造成了死锁
JVM对此就研发了一种叫可重复锁的锁,来解决这种两个锁锁一个的问题。synchronize就是这种可重复锁。
当然这也会带来效率下降。

死锁的四个条件

1.互斥使用: 一个锁被一个线程占用之后,其他线程占用不了
2.不可占用: 一个锁被一个线程占用之后,其他线程不能把这个锁抢走
3.请求和保持: 当一个线程占用多把锁之后,除非显示的释放,否则这些锁始终都是被该线程占用着。
4.环路等待: 等待关系连成了一个环

volatile

是禁止编译器优化,保证内存可见性。并不保证事务的原子性

JMM

JMM也叫java内存模型分为工作内存(CPU 寄存器)和主内存(内存)。

wait 和 notify

wait 和 notify 都是Object对象的方法
调用wait方法的线程,就会陷入阻塞
阻塞到其他线程通过notify来通知
来看代码
public class Demo15 {
    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        System.out.println("before wait");
        object.wait();
        System.out.println("after wait");
    }
}
这个会报非法的锁状态错误这个是为什么呢?
原来wait内部会做三件事情
1.先释放锁
2.等待通知
3.收到通知之后,重新获取锁,并继续执行下去
所以wait要搭配synchronized
public class Demo15 {
    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        synchronized(object){    //wait哪个对象。就要针对哪个对象加锁
            System.out.println("before wait");
            object.wait();
            System.out.println("after wait");
        }
    }
}
我们可以看到。没有notify代码就会一直执行着,那接下来我们看看他们两个联手会怎么样。
public class Demo16 {
    public static Object object = new Object();
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            synchronized (object){
                try {
                    object.wait();
                    System.out.println("wait");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t1.start();
        Thread t2 = new Thread(()->{
            synchronized (object){
                object.notify();
                System.out.println("notify");
            }
        });
        t2.start();
    }
}
但是当我们有很多的wait,这个时候notify,唤醒的是哪个wait是不知道的。
因为线程的争抢式执行,导致最后唤醒的是哪个线程就不知道了
notifyAll是一下子把所有wait唤醒

单例模式

要求代码中的某个类只能有一个实例,不能有多个
例如 JDBC DataSource,这样的对象,就应该是单例的

懒汉模式

//懒汉模式
//这个实现是比较重要的,要牢记。
class Singleton2{
    //这个就是和饿汉模式的不同,只有用到的时候才会初始化。
    // volatile 是为了内存可见性
    private volatile static Singleton2 instance = null;
    private Singleton2(){}
    public static Singleton2 getInstance(){
    //这个if是为了提高效率,如果没有这个的话,那么每一次来都会创建一个锁
        if(instance == null){                    
        //创建锁是因为这边的事务不是原子性的,多线程的时候保证安全。
            synchronized (Singleton2.class){     
                if(instance == null){            //这个就是单纯的看看是不是null
                    instance = new Singleton2();
                }
            }
        }
        return instance;
    }
}
public class Demo18 {
    public static void main(String[] args) {
        Singleton2 singleton2 = Singleton2.getInstance();
    }
}

两个if的作用是不一样的。

饿汉模式

//饿汉模式
class Singleton{
    //使用static创建一个实例,并且立马对其实例化
    private static Singleton instance = new Singleton();
    //让构造方法private是为了不让别人构造,这样我们就有了唯一实例。
    private Singleton(){}
    //提供方法,获得这个唯一的实例
    public static Singleton getInstance(){
        return instance;
    }
}
public class Demo17 {
    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
    }
}

为什么叫它饿汉呢?是因为创建实例比较着急,直接创建。不管你要不要,我都先创建一个。