学习JUC高并发编程这一篇就够了(上篇)



一、什么是JUC

1.1、简介

  • JUC就是java.util.concurrent工具包的简称,这是一个处理线程的工具包,JDK1.5开始出现

1.2、线程和进程

  • 进程:计算机分配资源的基本单位,具有独立功能的程序,例如QQ运行起来就是一个进程
  • 线程:系统分配处理器时间资源的基本单元,或者说是进程之内独立执行的一个个单元执行流

1.3、线程的状态

  • NEW(新建)
  • RUNNABLE(准备就绪)
  • BLOCKED(阻塞)
  • WAITING(等待)
  • TIMED_WAITING(超时不等待)
  • TERMINATED(终结)

1.4、wait & sleep

  • wait:Object的方法,任何对象实例都能调用。如果当前线程占用锁执行wait会释放锁(即代码要在synchronized中)
  • sleep:Thread的静态方法,不会释放锁也不需要占用锁

1.5、并发与并行

  • 串行:按先后顺序执行
  • 并行:多个任务同时执行(边听歌边学习)
  • 并发:多个线程访问同一个资源,多个线程对一个点(抢票,秒杀)

1.6、管程

  • Monitor监视器(锁):是一种同步机制,保证同一时间只有一个线程访问被保护的数据或代码
    JVM同步基于管程对象,进入获取管程对象,退出释放管程对象

1.7、用户线程和守护线程

  • 用户线程:自定义线程
  • 守护线程:比如垃圾回收线程

主线程结束,用户线程还在运行,JVM存活
主线程结束,没有用户线程,JVM结束

1.8、线程不由java创建
在这里插入图片描述
由操作系统本地方法创建
在这里插入图片描述


二、Lock

2.1、 复习Synchronized

2.1.1、作用范围

类型作用范围作用对象
代码块{}调用这个方法的对象
方法整个方法调用这个方法的对象
静态方法整个静态方法该类的所有对象
括号括起来的部分该类所有对象

synchronized不属于方法定义的一部分,因此无法被继承,如果子类重写父类的同步方法,子类并不同步,需要再加synchronized关键字或者子类直接调用父类同步方法

2.1.2、多线程编程步骤
实现高内聚,低耦合

  • 创建资源类,创建属性和操作方法
  • 创建多线程调用资源类的方法

2.1.3、卖票例子

public class SaleTicket {
    public static void main(String[] args) {

        Ticket ticket = new Ticket();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    ticket.sale();
                }
            }
        }, "A").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    ticket.sale();
                }
            }
        }, "B").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    ticket.sale();
                }
            }
        }, "C").start();

    }
}

class Ticket {
    private int num = 1000;

    public synchronized void sale() {
        if (num > 0) {
            num--;
            System.out.println(Thread.currentThread().getName() + "卖出一张票:" + "余票" + num);
        }
    }
}

2.2、Lock使用与对比

  • Lock不是Java语言内置的,synchronized是Java的关键字,因此是内置特性
  • Lock是一个,通过这个类可以实现同步访问
  • Lock需要手动释放锁,synchronized代码块执行完后会字段释放锁

2.2.1、Lock接口

ReentrantLock 可重入锁(后文会说,这里直接演示)

import java.util.concurrent.locks.ReentrantLock;

public class LSaleTicket {
    public static void main(String[] args) {
        LTicket lTicket = new LTicket();

        new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                lTicket.sale();
            }
        }, "A").start();

        new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                lTicket.sale();
            }
        }, "B").start();

        new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                lTicket.sale();
            }
        }, "C").start();
    }

}

class LTicket {
    private int num = 1000;
	
	// 创建可重入锁
    private final ReentrantLock lock = new ReentrantLock();

    public void sale() {
        lock.lock();
        try {
            if (num > 0) {
                num--;
                System.out.println(Thread.currentThread().getName() + "卖出一张票,余票" + num);
            }
        // 必须放在finally块中解锁,防止死锁
        } finally {
            lock.unlock();
        }

    }
}

2.2.2、对比

  • Lock是一个接口。synchronized是Java关键字,synchronized是内置的语言实现
  • synchronized发生异常时会自动释放线程占有的锁,因此不会导致死锁。
  • Lock发生异常时,如果没有主动通过unLock()释放,会导致死锁,因此使用Lock需要在finally块中释放锁
  • Lock可以让等待锁的线程响应中断,而synchronized却不行,等待的线程会一直等待下去,不能响应中断
  • Lock可以判断有没有成功获取锁,synchronized无法办到
  • Lock可以提高多个线程进行读操作的效率(读写锁)
  • 当竞争不激烈时两者性能差不多,当竞争非常激烈时,Lock的性能远大于synchronized


三、线程间通信

3.1、概念

  • 在多线程模式下进行工作,除了要考虑各个线程之间是否同步、如何竞争锁等问题,还要考虑这样一个问题:线程之间有的时候需要相互配合来共同完成一件事情。

  • 把一个大的任务拆分成多个不同的任务线,每个任务线中都有更小的执行步骤。各个线程之间需要彼此配合:A 线程执行一步唤醒 B 线程,自己等待;B 线程执行一步,唤醒 A 线程,自己等待……


3.2、核心语法

1、Object 类的 wait() 方法

  • wait() 方法会导致当前线程进入等待状态
  • 必须是另外一个线程调用 notify() 或 notifyAll() 方法来唤醒
  • “for this object” 表示还是要使用同一个对象分别调用 wait()、notify()、notifyAll() 这些方法

2、Object 类的 notify() 方法

  • notify() 方法只唤醒一个线程
  • 处于等待状态的线程会被存放在对象监视器中的一个数组中
  • 如果在这个对象的监视器中维护的处于等待状态的线程是多个,那么 notify() 方法会随机唤醒一个
  • notfiy() 方法无法精确唤醒一个指定的线程,这个需求可以通过 Lock + Condition 方式实现(定制化通信)

3、Object 类的 notifyAll() 方法

  • 唤醒当前对象监视器上等待的所有线程。

3.3、案例演示

3.3.1、需求分析

  • 设定一个成员变量,作为两个线程都要操作的共享数据,设置初始化值为 0
  • A 线程执行 +1 操作
  • B 线程执行 -1 操作
  • A、B 两个线程交替执行

3.3.2、synchronized代码实现

public class STest {
    public static void main(String[] args) {
        Share share = new Share();

        new Thread(() -> {
            for (int i = 0; i < 50; i++) {
                try {
                    share.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "AA").start();
        
        new Thread(() -> {
            for (int i = 0; i < 50; i++) {
                try {
                    share.decr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "BB").start();

    }
}

class Share {

    private int num = 0;

    public synchronized void incr() throws InterruptedException {
        if (num > 0) {
            this.wait();
        }
        num++;
        System.out.println(Thread.currentThread().getName() + "::" + num);
        this.notifyAll();
    }

    public synchronized void decr() throws InterruptedException {
        if (num <= 0) {
            this.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName() + "::" + num);
        this.notifyAll();
    }

}

3.3.3、虚假唤醒

问题描述:

当上面的例子中,线程数量从两个增加到四个,计算结果就会出错:
thread-a 线程:1
thread-c 线程:2
thread-a 线程:3
thread-b 线程:2
thread-d 线程:1

问题分析:

1、使用 if 的情况(仅判断一次)

假设C线程判断num<=0后,进行等待(在哪里等就在哪里醒),被别的线程唤醒后不会再进行num判断,会导致数值不符合预期

在这里插入图片描述
2、使用 while 解决问题
在这里插入图片描述
3、小结:

要解决虚假唤醒问题,就需要对线程间通信时的判断条件使用 while 循环结构来执行,而不是 if 分支判断。

在这里插入图片描述

3.3.4、Lock代码实现

public class Demo02 {
    public static void main(String[] args) {
        Share share = new Share();


        new Thread(() -> {
            for (int i = 0; i < 50; i++) {
                try {
                    share.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "AA").start();

        new Thread(() -> {
            for (int i = 0; i < 50; i++) {
                try {
                    share.decr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "BB").start();

        new Thread(() -> {
            for (int i = 0; i < 50; i++) {
                try {
                    share.decr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "CC").start();

    }
}

class Share {

    private int num = 0;

    private final Lock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();

    public void incr() throws InterruptedException {
        lock.lock();
        
        try {
            while (num > 0) {
                // 等待
                condition.await();
            }
            num++;
            System.out.println(Thread.currentThread().getName() + "::" + num);
            // 通知
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    public void decr() throws InterruptedException {
        lock.lock();

        try {
            while (num <= 0) {
                condition.await();
            }
            num--;
            System.out.println(Thread.currentThread().getName() + "::" + num);
            
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

3.4、定制化通信

1、可以通过可重入锁的多种情况来达成,每把钥匙都对应同一把重入锁

private Lock lock = new ReentrantLock();
//声明钥匙 A
private Condition conditionA = lock.newCondition();
//声明钥匙 B
private Condition conditionB = lock.newCondition();
//声明钥匙 C
private Condition conditionC = lock.newCondition();
conditionA.await();  //A等待
conditionA.signal(); //唤醒A

A 线程打印 5 次 A,B 线程打印 10 次 B,C 线程打印 15 次 C,按照此顺序循环 10 轮

2、代码演示

public class Laptoy {
    public static void main(String[] args) {
        Test test = new Test();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    test.printA(i);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "AA").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    test.printB(i);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "BB").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    test.printC(i);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "CC").start();


    }
}

class Test {
    //通信对象:0--打印 A 1---打印 B 2----打印 C
    private int number = 0;
    //声明锁
    private Lock lock = new ReentrantLock();
    //声明钥匙 A
    private Condition conditionA = lock.newCondition();
    //声明钥匙 B
    private Condition conditionB = lock.newCondition();
    //声明钥匙 C
    private Condition conditionC = lock.newCondition();

    /**
     * A 打印 5 次
     */
    public void printA(int j) {
        try {
            lock.lock();
            while (number != 0) {
                conditionA.await();
            }
            System.out.println(Thread.currentThread().getName() + "输出 A,第" + j + " 轮开始");
            //输出 5 次 A
            for (int i = 0; i < 5; i++) {
                System.out.println("A");
            }
            //开始打印 B
            number = 1;
            //唤醒 B
            conditionB.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    /**
     * B 打印 10 次
     */
    public void printB(int j) {
        try {
            lock.lock();
            while (number != 1) {
                conditionB.await();
            }
            System.out.println(Thread.currentThread().getName() + "输出 B,第" + j + " 轮开始");
            //输出 10 次 B
            for (int i = 0; i < 10; i++) {
                System.out.println("B");
            }
            //开始打印 C
            number = 2;
            //唤醒 C
            conditionC.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    /**
     * C 打印 15 次
     */
    public void printC(int j) {
        try {
            lock.lock();
            while (number != 2) {
                conditionC.await();
            }
            System.out.println(Thread.currentThread().getName() + "输出 C,第" + j + " 轮开始");
            //输出 15 次 C
            for (int i = 0; i < 15; i++) {
                System.out.println("C");
            }
            System.out.println("-----------------------------------------");
            //开始打印 A
            number = 0;
            //唤醒 A
            conditionA.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}


四、集合的线程安全

4.1、ArrayList集合线程不安全演示

ArrayList 并发情况下会出现ConcurrentModificationException并发修改异常

public class MySafe {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();

        for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 8));

                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }
}

4.2、解决方案

4.2.1、Vector

1、解决

List<String> list = new Vector<>();

2、源码

// Vector类添加元素方法为同步方法
public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
	public synchronized boolean add(E e) {
	   modCount++;
	   ensureCapacityHelper(elementCount + 1);
	   elementData[elementCount++] = e;
	   return true;
	}	
}

3、说明

不会抛异常,线程安全,但是这个类太古老

Vector 是矢量队列,它是 JDK1.0 版本添加的类。继承于 AbstractList,实现了 List, RandomAccess, Cloneable 这些接口。 Vector 继承了 AbstractList,实现了 List;所以,它是一个队列,支持相关的添加、删除、修改、遍历等功能。 Vector 实现了 RandmoAccess 接口,即提供了随机访问功能。RandmoAccess 是 java 中用来被 List 实现,为 List 提供快速访问功能的。在Vector 中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。 Vector 实现了 Cloneable 接口,即实现 clone()函数。它能被克隆

4.2.2、 Collections

1、解决

List<String> list = Collections.synchronizedList(new ArrayList<>());

2、源码

public class Collections {
	public static <T> List<T> synchronizedList(List<T> list) {
		return (list instanceof RandomAccess ?
	            new SynchronizedRandomAccessList<>(list) :
	            new SynchronizedList<>(list));
	}
}

3、说明

不会抛异常,但是锁定范围大,性能低

4.2.3、CopyOnWriteArrayList(写时复制)

1、解决

List<String> list = new CopyOnWriteArrayList<>();

2、写时复制

兼顾并发读和独立写写前先上可重入锁,写数据时复制一份旧数据再写,写完用新数据覆盖旧数据

在这里插入图片描述

  • 使用写时复制技术要向集合对象中写入数据时:先把整个集合数组复制一份
  • 将新数据写入复制得到的新集合数组
  • 再让指向集合数组的变量指向新复制的集合数组

3、源码

public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
	public boolean add(E e) {
	    final ReentrantLock lock = this.lock;
	    lock.lock();
	    try {
	        Object[] elements = getArray();
	        int len = elements.length;
	        // 复制旧数组并扩容一个单位
	        Object[] newElements = Arrays.copyOf(elements, len + 1);
	        // 将数据放入扩容的单位
	        newElements[len] = e;
	        setArray(newElements);
	        return true;
	    } finally {
	        lock.unlock();
	    }
	}
}
  • 优点:兼顾了性能和线程安全,允许同时进行读写操作
  • 缺点:由于需要把集合对象整体复制一份,所以对内存的消耗很大

4.3、解决HashSet线程不安全

1、直接使用CopyOnWriteArraySet代替

public class MySafe {
    public static void main(String[] args) {
        Set<String> set = new CopyOnWriteArraySet<>();

        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();
        }

    }
}

2、源码:

所在类:java.util.concurrent.CopyOnWriteArraySet

public boolean add(E e) {
     return al.addIfAbsent(e);
}

所在类:java.util.concurrent.CopyOnWriteArrayList

private boolean addIfAbsent(E e, Object[] snapshot) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] current = getArray();
        int len = current.length;
        if (snapshot != current) {
            // Optimize for lost race to another addXXX operation
            int common = Math.min(snapshot.length, len);
            for (int i = 0; i < common; i++)
                if (current[i] != snapshot[i] && eq(e, current[i]))
                    return false;
            if (indexOf(e, current, common, len) >= 0)
                    return false;
        }
        Object[] newElements = Arrays.copyOf(current, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

4.4、 解决HashMap线程不安全

1、直接使用ConcurrentHashMap解决

public class MySafe {
    public static void main(String[] args) {
        // 1、创建集合对象
        Map<String, String> map = new ConcurrentHashMap<>();

        // 2、创建多个线程执行读写操作
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                for (int j = 0; j < 5; j++) {
                    String key = UUID.randomUUID().toString().replace("-", "").substring(0, 5);
                    String value = UUID.randomUUID().toString().replace("-", "").substring(0, 5);
                    map.put(key, value);
                    System.out.println("map = " + map);
                }

            }, "thread" + i).start();
        }

    }
}

2、说明

ConcurrentHashMap 底层使用的是『锁分段』技术。它的典型应用就是用来实现微服务的注册中心。

  • 微服务名称:作为 Map 的 key
  • 微服务对象:作为 Map 的 value
  • 使用 ConcurrentHashMap 实现并发读写
  • 发现服务:读操作
  • 注册服务:写操作


五、八锁现象

  • 对于普通同步方法,锁是当前实例对象。
  • 对于静态同步方法,锁是当前类的 Class 对象。
  • 对于同步方法块,锁是 Synchonized 括号里配置的对象

5.1、对象层

普通 synchronized 锁的对象是方法的调用者

1、两个同步方法,同一个对象演示

public class Lock_8 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();

        new Thread(() -> {
            try {
                phone.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "A").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(() -> {
            try {
                phone.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "B").start();
    }
}

class Phone {
    public synchronized void sendEmail() throws Exception {
        System.out.println("---sendEmail---");
    }

    public synchronized void sendSMS() throws Exception {
        System.out.println("---sendSMS---");
    }
}

结果:同一把锁,所以先sendEmail

---sendEmail---
---sendSMS---

2、 两个同步方法,同一个对象,停 4 秒在短信方法内

class Phone {
    public synchronized void sendEmail() throws Exception {
        TimeUnit.SECONDS.sleep(3);
        System.out.println("---sendEmail---");
    }

    public synchronized void sendSMS() throws Exception {
        System.out.println("---sendSMS---");
    }
}

结果:同一把锁,依然是先sendEmail

---sendEmail---
---sendSMS---

3、两个对象,两个同步方法(注意睡眠时间)

public class Lock_8 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();

        new Thread(() -> {
            try {
                phone1.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "A").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(() -> {
            try {
                phone2.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "B").start();

    }
}

class Phone {
    public synchronized void sendEmail() throws Exception {
        TimeUnit.SECONDS.sleep(2);
        System.out.println("---sendEmail---");
    }

    public synchronized void sendSMS() throws Exception {
        System.out.println("---sendSMS---");
    }
}

结果:不同锁,先sendSMS,不同对象在普通同步方法分配的是不同的锁

---sendSMS---
---sendEmail---

5.2、普通方法层

普通方法没有锁!不是同步方法,就不受锁的影响,正常执行

4、 一个对象,新增普通的 hello 方法

public class Lock_8 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();

        new Thread(() -> {
            try {
                phone.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "A").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(() -> {
            try {
                phone.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "B").start();

        new Thread(() -> {
            try {
                phone.hello();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "C").start();

    }
}

class Phone {
    public synchronized void sendEmail() throws Exception {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("---sendEmail---");
    }

    public synchronized void sendSMS() throws Exception {
        System.out.println("---sendSMS---");
    }

    public void hello() {
        System.out.println("---hello---");
    }
}

结果:先hello,不受锁的影响

---hello---
---sendEmail---
---sendSMS---

5.3、类层

不同实例对象的Class类模板只有一个,static静态的同步方法,锁的是Class

5、两个静态的同步方法,一个对象

public class Lock_8 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();

        new Thread(() -> {
            try {
                phone.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "A").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(() -> {
            try {
                phone.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "B").start();

    }
}

class Phone {
    public static synchronized void sendEmail() throws Exception {
        TimeUnit.SECONDS.sleep(2);
        System.out.println("---sendEmail---");
    }

    public static synchronized void sendSMS() throws Exception {
        System.out.println("---sendSMS---");
    }
}

结果:同一把锁,先sendEmail

---sendEmail---
---sendSMS---

6、两个对象!增加两个静态的同步方法

public class Lock_8 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();

        new Thread(() -> {
            try {
                phone1.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "A").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(() -> {
            try {
                phone2.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "B").start();

    }
}

class Phone {
    public static synchronized void sendEmail() throws Exception {
        TimeUnit.SECONDS.sleep(2);
        System.out.println("---sendEmail---");
    }

    public static synchronized void sendSMS() throws Exception {
        System.out.println("---sendSMS---");
    }
}

结果:同一把锁,锁的是类,依然是先sendEmail

---sendEmail---
---sendSMS---

5.4、混合层

静态同步方法锁的是Class类模板,普通同步方法锁的是实例化的对象,锁的对象不同

7、1个静态的同步方法,1个普通的同步方法 ,一个对象(注意睡眠时间)

public class Lock_8 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();

        new Thread(() -> {
            try {
                phone.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "A").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(() -> {
            try {
                phone.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "B").start();

    }
}

class Phone {
    public synchronized void sendEmail() throws Exception {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("---sendEmail---");
    }

    public static synchronized void sendSMS() throws Exception {
        System.out.println("---sendSMS---");
    }
}

结果:不同锁,先sendSMS

---sendSMS---
---sendEmail---

8、1个静态的同步方法,1个普通的同步方法 ,两个对象(注意睡眠时间)

public class Lock_8 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();

        new Thread(() -> {
            try {
                phone1.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "A").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(() -> {
            try {
                phone2.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "B").start();

    }
}

class Phone {
    public synchronized void sendEmail() throws Exception {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("---sendEmail---");
    }

    public static synchronized void sendSMS() throws Exception {
        System.out.println("---sendSMS---");
    }

}

结果:不同锁,先sendSMS

---sendSMS---
---sendEmail---

六、多线程锁

6.1、公平锁与非公平锁

公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。

  • 优点:所有的线程都能得到资源,不会饿死在队列中。
  • 缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。

非公平锁:多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。

  • 优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必唤醒所有线程,会减少唤起线程的数量。

  • 缺点:可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。

创建可重入锁的时候可以设置公平或非公平锁(默认是非公平锁 )
在这里插入图片描述

6.2、可重入锁

  • synchronized:隐式,自动获取释放锁
  • Lock:显式,手动获取释放锁

6.2.1、synchronized演示可重入锁

1、拿到外层的锁就可以拿到内层的锁

public class SyncLock {
    public static void main(String[] args) {

        Object o = new Object();

        new Thread(() -> {
            synchronized (o) {
                System.out.println(Thread.currentThread().getName() + "外层锁");
                
                synchronized (o) {
                    System.out.println(Thread.currentThread().getName() + "中层锁");
                    
                    synchronized (o) {
                        System.out.println(Thread.currentThread().getName() + "内层锁");
                    }
                }
            }
        }, "laptoy-t").start();
    }

}

// laptoy-t外层锁
// laptoy-t中层锁
// laptoy-t内层锁

2、递归锁

public class SyncLock {
    public static void main(String[] args) {
        new SyncLock().add();
    }

    synchronized void add() {
        add();
    }
}

// 栈溢出异常,证明可重入锁可以递归调用

6.2.2、Lock演示可重入锁

public class SyncLock {
    public static void main(String[] args) {
        
        Lock lock = new ReentrantLock();

        new Thread(() -> {
            try {
                lock.lock();
                System.out.println(Thread.currentThread().getName() + "外层锁");

                try {
                    lock.lock();
                    System.out.println(Thread.currentThread().getName() + "内层锁");
                } finally {
                    lock.unlock();
                }

            } finally {
                lock.unlock();
            }
        }, "laptoy-t").start();
    }
}

// laptoy-t外层锁
// laptoy-t内层锁

6.3、死锁

6.3.1、简介

两个或两个以上进程执行过程中,因为争夺资源而造成的一种互相等待的现象,如果没有外力干涉,他们无法再执行下去

在这里插入图片描述

6.3.2、产生原因

  • 系统资源不足
  • 进程运行推荐顺序不合适
  • 资源分配不当

6.3.3、快速演示死锁代码

public class DeadLock {

    static Object a = new Object();
    static Object b = new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (a) {
                System.out.println(Thread.currentThread().getName() + "线程 持有锁a,试图获取锁b");
                synchronized (b) {
                    System.out.println(Thread.currentThread().getName() + "线程 获取锁b");
                }
            }
        }, "A").start();

        new Thread(() -> {
            synchronized (b) {
                System.out.println(Thread.currentThread().getName() + "线程 持有锁b,试图获取锁b");
                synchronized (a) {
                    System.out.println(Thread.currentThread().getName() + "线程 获取锁a");
                }
            }
        }, "B").start();
    }

}
// A线程 持有锁a,试图获取锁b
// B线程 持有锁b,试图获取锁b

6.3.4、验证死锁

执行jps查询正在执行的进程
在这里插入图片描述
执行jstack 13484追踪堆栈
在这里插入图片描述



  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Laptoy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值