1、什么是JUC

说白了就是三个包

image-20200530135011084

业务:普通的线程代码 Thread

Runnable接口 没有返回值 效率相比于Callable相对较低

2、进程与线程

线程、进程

一个进程往往可以包含多个线程,至少包含一个!

Java默认有几个线程?两个:main线程,GC线程(垃圾回收)

对于Java而言:Thread、Runnable、Callable

Java真的可以开启线程吗? 开不了的

Java实际上调用了本地方法,底层的C++,Java无法操作硬件,因为Java是运行在虚拟机上的

private native void start0();

并行、并发

并发(多个线程操作同一个资源,微观上是依次执行的)

并行(多个线程真正的同时执行);线程池

并发编程的本质:充分利用CPU的资源

线程有几个状态?6个

public enum State {
    //新生
    NEW,

    //运行
    RUNNABLE,

    //阻塞
    BLOCKED,

    //等待(阻塞)
    WAITING,

    //超时等待
    TIMED_WAITING,

    //终止
    TERMINATED;
}

wait/sleep 区别

1、来自不同的类

wait => Object

sleep => Thread

企业开发不使用sleep,使用的是TimeUnit

2、关于锁的释放

wait方法会释放锁

sleep不会释放锁

3、使用的范围是不同的

wait必须在同步代码块中才能使用

sleep可以在任何地方执行(睡)

4、是否需要捕获异常

wait不需要捕获异常(会抛出中断异常)

sleep必须要捕获异常

3、Lock锁(重点)

传统 Synchronized 锁

//基本的卖票例子

/**
 * 真正的多线程开发(企业开发)不会在资源类去实现Runnable接口
 * 线程就是一个单独的资源类,它没有任何的附属操作
 * 1、属性 2、方法
 */
public class SaleTicketDemo01 {
    public static void main(String[] args) {
        //并发,多个线程操作同一个资源类
        Ticket ticket = new Ticket();
        //怎么操作资源类:把资源丢入线程
        new Thread(()->{
            for (int i = 1; i < 60; i++) {
                ticket.sale();
            }
        },"A").start();

        new Thread(()->{
            for (int i = 1; i < 60; i++) {
                ticket.sale();
            }
        },"B").start();

        new Thread(()->{
            for (int i = 1; i < 60; i++) {
                ticket.sale();
            }
        },"C").start();

    }
}

//资源类,OOP编程(降低耦合性)
class Ticket{
    //属性
    private int number = 50;
    //方法
    //synchronized 本质:队列 、锁
    public synchronized void sale(){
        if (number>0){
            System.out.println(Thread.currentThread().getName()+"卖出了第"+number--+"张票,剩余"+number);
        }
    }
}

JUC 版 Lock锁

image-20200530145145278

公平锁:十分公平,必须先来后到

非公平锁:十分不公,可以插队(默认)因为正常情况下,线程存在优先级

public class SaleTicketDemo02 {
    public static void main(String[] args) {
        Ticket2 ticket = new Ticket2();
        new Thread(()->{for (int i = 1; i < 60; i++)ticket.sale();},"A").start();
        new Thread(()->{for (int i = 1; i < 60; i++)ticket.sale();},"B").start();
        new Thread(()->{for (int i = 1; i < 60; i++)ticket.sale();},"C").start();
    }
}

//Lock锁
//1、new ReentrantLock();
//2、lock.lock();//加锁
//3、finally -> lock.unlock();//解锁
class Ticket2{
    private int number = 50;
    Lock lock = new ReentrantLock();
    public void sale(){
        lock.lock();//加锁
        try {
            //业务代码
            if (number>0){
                System.out.println(Thread.currentThread().getName()+"卖出了第"+number--+"张票,剩余"+number);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();//解锁
        }
    }
}

Synchronized 和 Lock 的区别

1、Synchronized 内置的关键字;Lock是以一个Java类

2、Synchronized 无法判断获取锁的状态;Lock 可以判断是否获得到了锁

3、Synchronized 会自动释放锁;Lock必须要手动释放锁!如果不释放锁就会 死锁

4、Synchronized 线程1(获得锁 -> 阻塞)=> 线程2(等待 -> 一直等待);Lock就不一定会等待下去

5、Synchronized 可重入锁,不可以中断,非公平;Lock,可重入锁,可以判断锁,可以设置公平/非公平

6、Synchronized 适合锁少量的代码同步问题;Lock适合锁大量的代码同步问题

锁是什么?如何判断锁的是谁?

4、生产者消费者问题

业务代码:判断->执行->通知

Synchronized版

注意 waitnotifyAll 前别忘了加this

/**
 * 线程之间的通信问题:生产者和消费者问题
 * 线程交替执行 A B 操作统一个变量 A+1,B-1
 * 等待wait 通知notifyAll(别忘了加this)
 */
public class A {
    public static void main(String[] args) {
        Date date = new Date();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    date.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    date.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
    }
}

class Date{
    private int number = 0;
    //+1
    public synchronized void increment() throws InterruptedException {
        if (number!=0){
            this.wait();//等待
        }
        number++;//业务
        System.out.println(number);
        this.notifyAll();//通知其他线程(唤醒其他线程)
    }
    //-1
    public synchronized void decrement() throws InterruptedException {
        if (number==0){
            this.wait();
        }
        number--;
        System.out.println(number);
        this.notifyAll();
    }
}

出现问题!四个线程同时操作时,出现同时加1,结果为2、3情况;虚假唤醒问题

问题出现原因:使用的if判断只会判断一次

if (number!=0){
    this.wait();//等待
}

解决方案:改为 while 方法

解决原理:因为在减1之后会唤醒所有线程(不止一个),而这里有两个生产者,所以这两个生产者都被唤醒后就会直接执行 if 语句后的+1,而用 while 的话会重新判断是否需要等待。

JUC版 生产者消费者问题

通过Lock找到Condition

public class B {
    public static void main(String[] args) {
        Date2 date = new Date2();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    date.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    date.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    date.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    date.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}

class Date2{
    private int number = 0;

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

    public void increment() throws InterruptedException {
        lock.lock();
        try {
            //业务代码
            while (number!=0){
                condition.await();//等待
            }
            number++;
            System.out.println(Thread.currentThread().getName()+number);
            condition.signalAll();//通知其他线程(唤醒其他线程)
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    public void decrement() throws InterruptedException {
        lock.lock();
        try {
            while (number==0){
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName()+number);
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

Condition 精准的通知和唤醒线程

public class C {
    public static void main(String[] args) {//要求三个线程顺序执行:A->B->C->A
        Date3 date3 = new Date3();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                date3.printA();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                date3.printB();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                date3.printC();
            }
        },"C").start();
    }
}

class Date3{
    private Lock lock = new ReentrantLock();
    Condition conditionA = lock.newCondition();
    Condition conditionB = lock.newCondition();
    Condition conditionC = lock.newCondition();
    private int number = 1; //1->A 2->B 3->C

    public void printA(){
        lock.lock();
        try {
            //业务代码:判断-执行-通知
            while (number!=1){
                conditionA.await();//A等待
            }
            System.out.println(Thread.currentThread().getName());
            number = 2;
            conditionB.signal();//唤醒B
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printB(){
        lock.lock();
        try {
            while (number!=2){
                conditionB.await();
            }
            System.out.println(Thread.currentThread().getName());
            number = 3;
            conditionC.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printC(){
        lock.lock();
        try {
            while (number!=3){
                conditionC.await();
            }
            System.out.println(Thread.currentThread().getName());
            number = 1;
            conditionA.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

5、八锁问题

锁到底锁的是谁?

两个东西:new 出来的对象(有多个),Class模版(唯一的)

1、标准状况下,谁先执行完?

发短信

原因:因为加了synchronized锁,锁的是对象,两个线程执行使用的是同一个对象,所以使用的只有一把锁。发短信先拿到锁,打电话就必须等发短信执行完释放锁。

public class Test01 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            phone.sendSms();
        }).start();

        try {
            TimeUnit.SECONDS.sleep(1);//睡一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone.call();
        }).start();
    }
}

class Phone{
    public synchronized void sendSms(){
        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
}
2、发短信业务代码延迟3秒,谁先执行完?

发短信

原因:同上!

public class Test01 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            phone.sendSms();
        }).start();

        try {
            TimeUnit.SECONDS.sleep(1);//睡一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone.call();
        }).start();
    }
}

class Phone{
    public synchronized void sendSms(){

        try {
            TimeUnit.SECONDS.sleep(3);//睡三秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
}
3、增加了一个普通方法,谁先执行完?

hello

原因:hello方法没有加锁,不受锁的影响,而又因为发短信需要等待3秒,hello方法只需等待1秒,故hello先执行完

public class Test02 {
    public static void main(String[] args) {
        Phone2 phone = new Phone2();
        new Thread(()->{
            phone.sendSms();
        }).start();

        try {
            TimeUnit.SECONDS.sleep(1);//睡一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone.hello();
        }).start();
    }
}

class Phone2{
    public synchronized void sendSms(){

        try {
            TimeUnit.SECONDS.sleep(3);//睡三秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
    
    public void hello(){
        System.out.println("hello");
    }
}
4、创建两个对象,两个同步方法,谁先执行完?

打电话

原因:现在有两个对象,即存在两把锁。两个线程使用的对象不同,即使用的锁也不同,执行互不影响。故谁先执行完只看执行需要的时间。打电话->1s 发短信->3s

public class Test02 {
    public static void main(String[] args) {
        Phone2 phone1 = new Phone2();
        Phone2 phone2 = new Phone2();
        new Thread(()->{
            phone1.sendSms();
        }).start();

        try {
            TimeUnit.SECONDS.sleep(1);//睡一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone2.hello();
        }).start();
    }
}

class Phone2{
    public synchronized void sendSms(){

        try {
            TimeUnit.SECONDS.sleep(3);//睡三秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }

    public void hello(){
        System.out.println("hello");
    }
}
5、两个静态方法,一个对象,谁先执行完?

发短信

原因:一旦静态方法加锁,被锁的是这个对象对应的class模版(唯一),即只有一把锁,两个线程共用这把锁。

public class Test03 {
    public static void main(String[] args) {
        Phone3 phone1 = new Phone3();
        new Thread(()->{
            phone1.sendSms();
        }).start();

        try {
            TimeUnit.SECONDS.sleep(1);//睡一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone1.call();
        }).start();
    }
}

class Phone3{
    public static synchronized void sendSms(){

        try {
            TimeUnit.SECONDS.sleep(3);//睡三秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("发短信");
    }
    public static synchronized void call(){
        System.out.println("打电话");
    }
}
6、两个静态方法,两个对象,谁先执行完?

发短信

原因:同上!还是因为只有一把锁

public class Test03 {
    public static void main(String[] args) {
        Phone3 phone1 = new Phone3();
        Phone3 phone2 = new Phone3();
        new Thread(()->{
            phone1.sendSms();
        }).start();

        try {
            TimeUnit.SECONDS.sleep(1);//睡一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone2.call();
        }).start();
    }
}

class Phone3{
    public static synchronized void sendSms(){

        try {
            TimeUnit.SECONDS.sleep(3);//睡三秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("发短信");
    }
    public static synchronized void call(){
        System.out.println("打电话");
    }
}
7、一个静态同步方法,一个普通同步方法,一个对象,谁先执行完?

打电话

原因:静态同步方法锁的是Class模版,普通同步方法锁的是对象。即存在两把锁,两个线程各使用一把,执行完先后顺序由执行所需时间决定。

public class Test04 {
    public static void main(String[] args) {
        Phone4 phone1 = new Phone4();
        new Thread(()->{
            phone1.sendSms();
        }).start();

        try {
            TimeUnit.SECONDS.sleep(1);//睡一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone1.call();
        }).start();
    }
}

class Phone4{
    public static synchronized void sendSms(){

        try {
            TimeUnit.SECONDS.sleep(3);//睡三秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
}
8、两个对象,谁先执行完?

打电话

原因:同上!

public class Test04 {
    public static void main(String[] args) {
        Phone4 phone1 = new Phone4();
        Phone4 phone2 = new Phone4();
        new Thread(()->{
            phone1.sendSms();
        }).start();

        try {
            TimeUnit.SECONDS.sleep(1);//睡一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone2.call();
        }).start();
    }
}

class Phone4{
    public static synchronized void sendSms(){

        try {
            TimeUnit.SECONDS.sleep(3);//睡三秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
}

小结

new 出来的对象 --> this(多个)

static --> Class(唯一)

6、集合类不安全

List不安全

//报错:Exception in thread "8" java.util.ConcurrentModificationException
//并发修改异常
public class ListTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 1; i < 10; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

并发下,List不安全

解决方案:

1、ArrayList<>()改为Vector<>(), Vector默认为安全的

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

2、使用Collections提供的同步方法,把一个List变得安全

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

image-20200530193908365

3、JUC解法:使用CopyOnWriteArrayList

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

==CopyOnWrite==:写入时复制;COW,计算机程序设计时的一种优化策略

多个线程调用时,读取则都读取固定的。

写入时为了避免覆盖,则先复制一份空间提供写入,写入完毕后再真正放回数组

**思考:**为什么用CopyOnWriteArrayList而不用Vector

因为 Vector 本质上使用的是 synchronized 进行同步,效率较低

CopyOnWriteArrayList 则是使用的Lock锁来完成同步

Set不安全

报错同List

//Exception in thread "16" Exception in thread "24" java.util.ConcurrentModificationException
public class SetTest {
    public static void main(String[] args) {
        Set<Object> set = new HashSet<>();
        for (int i = 1; i < 30; i++) {
            new Thread(()->{
                set.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(set);
            }, String.valueOf(i)).start();
        }
    }
}

解决方案:

1、使用Collections.synchronizedSet(new HashSet<>())

Set<Object> set = Collections.synchronizedSet(new HashSet<>());

2、使用CopyOnWriteArraySet<>()

Set<Object> set = new CopyOnWriteArraySet<>();

思考:

HashSet的底层是什么?

public HashSet() {
    map = new HashMap<>();
}

HashSet 的 add 方法又是什么?

//key是无法重复的!
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

Map不安全

基础:

map 工作中不用HashMap(存疑?)

默认等价于 new HashMap<>(16,0.75);

Map<String, Object> map = new HashMap<>();

同样的并发修改异常

Exception in thread "19" Exception in thread "10" java.util.ConcurrentModificationException

public class MapTest {
    public static void main(String[] args) {
        //map 工作中不用HashMap(存疑?)
        //默认等价于 new HashMap<>(16,0.75);
        Map<String, Object> map = new HashMap<>();
        for (int i = 1; i < 30; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            }, String.valueOf(i)).start();
        }
    }
}

解决方案:

1、使用Collections.synchronizedMap(new HashMap<>());

Map<String, Object> map = Collections.synchronizedMap(new HashMap<>());

2、使用ConcurrentHashMap<>();

Map<String, Object> map = new ConcurrentHashMap<>();