JUC并发编程 学习记录

b站:狂神说:JUC并发编程

JUC并发编程

1、 什么是JUC

JUC就是java.util .concurrent工具包的简称。

2、 回顾多线程

2.1 线程、进程

一个进程往往可以包含多个线程。进程是程序的一次执行过程。

线程是操作系统执行和调度的最小基本单位。

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

java默认有两个线程:main线程、GC线程。

  • java真的能开启线程吗?

    不能,调用了底层c++的本地方法,java是运行在虚拟机上的,不能直接调用本地方法。

    // 源码
    	public synchronized void start() {
            if (threadStatus != 0)
                throw new IllegalThreadStateException();
    
            /* Notify the group that this thread is about to be started
             * so that it can be added to the group's list of threads
             * and the group's unstarted count can be decremented. */
            group.add(this);
    
            boolean started = false;
            try {
                start0();
                started = true;
            } finally {
                try {
                    if (!started) {
                        group.threadStartFailed(this);
                    }
                } catch (Throwable ignore) {
                    /* do nothing. If start0 threw a Throwable then
                      it will be passed up the call stack */
                }
            }
        }
    
        private native void start0();
    	...
    

2.2 并发、并行

  1. 解释一:并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
  2. 解释二:并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。
  3. 解释三:并行是在多台处理器上同时处理多个任务。如 hadoop 分布式集群,并发是在一台处理器上“同时”处理多个任务。

所以并发编程的目标是充分的利用处理器的每一个核,以达到最高的处理性能。

并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行。所以无论从微观还是从宏观来看,二者都是一起执行的。

并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。

// 查看cpu核数
public class Test01 extends Thread{
    public static void main(String[] args) {
        System.out.println(Runtime.getRuntime().availableProcessors());
    }
}

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

2.3 线程五态

    //源码
	public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
       // 新生
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        // 运行
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        // 阻塞
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called {@code Object.wait()}
         * on an object is waiting for another thread to call
         * {@code Object.notify()} or {@code Object.notifyAll()} on
         * that object. A thread that has called {@code Thread.join()}
         * is waiting for a specified thread to terminate.
         */
        // 等待
        WAITING,

        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        // 超时等待
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        // 终止
        TERMINATED;
    }

2.4 wait和sleep区别

1、来自不同的类

wait => Object

sleep => Thread

2、关于锁的释放

wait会释放锁,sleep不会释放锁

3、使用的范围是不同的

wait 必须在同步代码块中

sleep 可以在任何地方睡

4、是否需要捕获异常

sleep必须捕获异常,wait也需要捕获异常

3、Lock锁(重点)

3.1 传统synchronize锁

package com.ghc;

/**
 * 多线程开发,要降低耦合性
 * 线程就是一个单独的资源类,没有任何附属操作
 * 1.属性、方法
 */
public class SaleTicketDemo01 {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(() -> {
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        }, "A").start();

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

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

class Ticket {
    // 属性 方法
    private int number = 30;

    // 买票的方式
    // synchronized 本质:锁、队列
    public synchronized void sale() {
        if (number > 0) {
            System.out.println(Thread.currentThread().getName() + "卖出了" + number-- + "票,剩余:" + number);
        }
    }
}

3.2 Lock锁

在这里插入图片描述

在这里插入图片描述

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

非公平锁:可以插队(默认)

public class SaleTicketDemo02 {
    public static void main(String[] args) {
        Ticket2 ticket2 = new Ticket2();

        new Thread(()-> {for (int i = 0; i < 40; i++) ticket2.sale();},"A").start();
        new Thread(()-> {for (int i = 0; i < 40; i++) ticket2.sale();},"B").start();
        new Thread(()-> {for (int i = 0; i < 40; i++) ticket2.sale();},"C").start();

    }
}

// lock
// 1.new ReentrantLock  2.加锁    3.解锁
class Ticket2 {
    private int number = 30;
    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(); // 解锁
        }

    }
}

3.3 Synchronize和Lock区别

1、Synchronize 是内置的java关键字,Lock 是一个java类

2、Synchronize 无法判断获取锁的状态,Lock 可以判断是否获取到了锁

3、Synchronize 会自动释放锁,Lock 需要手动释放锁,如果不释放锁,会死锁

4、Synchronize 线程1(获取锁,阻塞) 线程2(等待);Lock 不一定会等待,有tryLock() 方法

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

6、Synchronize 适合锁少了的代码同步问题,Lock 适合锁大量的同步代码

4、生产者/消费者问题

4.1 Synchronize 版

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

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

    }
}

// 判断等待,业务,通知
class Data { // 数字,资源类
    private int number = 0;

    // +1
    public synchronized void increment() throws InterruptedException {
        if (number != 0) {
            // 等待
            this.wait();

        }
        number++;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        // 通知其他线程,+1完毕
        this.notifyAll();

    }

    // -1
    public synchronized void decrement() throws InterruptedException {
        if (number == 0) {
            // 等待
            this.wait();

        }
        number--;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        // 通知其他线程,-1完毕
        this.notifyAll();
    }
}
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0

问题存在:两个线程可能没问题,当有多个线程的时候,就会存在问题。

添加C、D线程,四个线程运行结果:
在这里插入图片描述

虚假唤醒

wait() 源码
在这里插入图片描述

解决:把 if 改为 while 判断!因为 两个 +1 线程争抢,if 只会判断一次

4.2 JUC版生产者/消费者

在这里插入图片描述

通过Lock找到Condition

在这里插入图片描述

代码实现:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockPC {
    public static void main(String[] args) {
        Data2 data2 = new Data2();

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

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

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

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

    }
}

// 判断等待,业务,通知
class Data2 { // 数字,资源类

    private int number = 0;

    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    // condition.await(); // 等待
    // condition.signalAll(); // 唤醒全部

    // +1
    public void increment() throws InterruptedException {
        lock.lock();
        try {
            while (number != 0) {
                // 等待
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            // 通知其他线程,+1完毕
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    // -1
    public void decrement() throws InterruptedException {
        lock.lock();
        try {
            while (number == 0) {
                // 等待
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            // 通知其他线程,-1完毕
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

运行结果有问题!状态随机!

使用condition精准唤醒!

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * A 执行完调用 B,B 执行完调用 C,C 执行完调用 A
 */
public class ConPC {
    public static void main(String[] args) {
        Data3 data3 = new Data3();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                data3.printA();
            }
        }, "A").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                data3.printB();
            }
        }, "B").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                data3.printC();
            }
        }, "C").start();
    }
}

class Data3 {
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private int number = 1; // 1 A唤醒,2 B唤醒,3 C唤醒

    public void printA() {
        lock.lock();
        try {
            // 业务、判断->执行->通知
            while (number != 1) {
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName() + "=>AAA");
            number = 2;
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printB() {
        lock.lock();
        try {
            // 业务、判断->执行->通知
            while (number != 2) {
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName() + "=>BBB");
            number = 3;
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printC() {
        lock.lock();
        try {
            // 业务、判断->执行->通知
            while (number != 3) {
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName() + "=>CCC");
            number = 1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

5、集合类不安全

5.1 CopyOnWriteArrayList

import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;

public class ListTest {
    public static void main(String[] args) {
        /**
         * 并发下,ArrayList不安全
         * ConcurrentModificationException 并发修改异常!
         * 解决方案:
         *  1. 换成Vector List<String> list = new Vector<>();
         *  2. List<String> list = Collections.synchronizedList(new ArrayList<>());
         *  3. List<String> list = new CopyOnWriteArrayList<>();
         */
        // List<String> list = new ArrayList<>();
        // List<String> list = Collections.synchronizedList(new ArrayList<>());

        /**
         * CopyOnWrite 写入时复制, COW,计算机程序设计领域的一种优化策略
         * 多个线程调用的时候,list是唯一的,读取的时候是固定的,写入的时候存在(覆盖),就可以用COW解决
         * 在写入的时候,避免覆盖,造成数据问题
         * COW 比 vector 好在哪?
         *      1.vector的add方法有synchronized方法,效率较低,COW用的lock锁
         */

        List<String> list = new CopyOnWriteArrayList<>();
        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();
        }
    }
}

5.2 CopyOnWriteArraySet

HashSet 底层是什么?

在这里插入图片描述

HashSet 的 add()方法
在这里插入图片描述

import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * 同理:ConcurrentModificationException异常
 * 解决方案:
 * 1.Set<String> set = Collections.synchronizedSet(new HashSet<>());
 * 2.Set<String> set = new CopyOnWriteArraySet<>();
 */
public class SetTest {
    public static void main(String[] args) {
        //Set<String> set = new HashSet<>();
        //Set<String> set = Collections.synchronizedSet(new HashSet<>());
        Set<String> set = new CopyOnWriteArraySet<>();
        for (int i = 1; i <= 100; i++) {
            new Thread(() -> {
                set.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(set);
            }, String.valueOf(i)).start();
        }
    }
}

5.3 ConcurrentHashMap

/**
 * 同理:ConcurrentModificationException异常
 */

public class MapTest {
    public static void main(String[] args) {
        /**
         * HashMap 有个加载因子和初始化容量
         *  new HashMap<>(16,0.75)
         */

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

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

6、Callable

在这里插入图片描述

1、可以有返回值

2、可以抛出异常

3、方法不同,run()/call()

如何实现

Callable()接口里有FutureTask实现类,FutureTask可以调佣Callable

在这里插入图片描述

在这里插入图片描述

代码测试

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        MyThread thread = new MyThread();
        FutureTask futureTask = new FutureTask(thread); // 适配类
        new Thread(futureTask,"A").start();
        new Thread(futureTask,"B").start(); // 只会打印一个call(),结果会被缓存,效率高

        // 获取Callable返回结果
        final String o = (String) futureTask.get();// 这个get方法可能会阻塞
        System.out.println(o);

    }
}

class MyThread implements Callable<String> {

    @Override
    public String call() {
        System.out.println("call()");
        return "1024";
    }
}

细节:

1、有缓存

2、结果可能需要等待,如果耗时很长,get可能会阻塞

7、常用辅助类

7.1 CountDownLatch

在这里插入图片描述

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {

        // 给定计数6
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"Get Out");
                countDownLatch.countDown(); // 数量 -1
            },String.valueOf(i)).start();
        }
        countDownLatch.await(); // 等待计数器归零,再执行下面的操作
        System.out.println("Close the door");
    }
}

原理:

每次有线程调用 countDown(),数量-1,假设计数器归零,countDownLatch.await() 就会被唤醒

7.2 CyclicBarrier

public class CyclicBarrier
extends Object

允许一组线程全部等待彼此达到共同屏障点的同步辅助。循环阻塞在涉及固定大小的线程方的程序中很有用,这些线程必须偶尔等待彼此。屏障被称为循环 ,因为它可以在等待的线程被释放之后重新使用。

A CyclicBarrier支持一个可选的Runnable命令,每个屏障点运行一次,在派对中的最后一个线程到达之后,但在任何线程释放之前。 在任何一方继续进行之前,此屏障操作对更新共享状态很有用。

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
            System.out.println("召唤神龙!");
        });

        for (int i = 1; i <= 7; i++) {
            int temp = i;
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "收集" + temp + "颗龙珠");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

7.3 Semaphore

Semaphore:信号量

  • 一个计数信号量。 在概念上,信号量维持一组许可证。 如果有必要,每个acquire()都会阻塞,直到许可证可用,然后才能使用它。 每个release()添加许可证,潜在地释放阻塞获取方。 但是,没有使用实际的许可证对象; Semaphore只保留可用数量的计数,并相应地执行。
  • 在获得项目之前,每个线程必须从信号量获取许可证,以确保某个项目可用。 当线程完成该项目后,它将返回到池中,并将许可证返回到信号量,允许另一个线程获取该项目。 请注意,当调用acquire()时,不会保持同步锁定,因为这将阻止某个项目返回到池中。 信号量封装了限制对池的访问所需的同步,与保持池本身一致性所需的任何同步分开。
  • 信号量被初始化为一个,并且被使用,使得它只有至多一个允许可用,可以用作互斥锁。 这通常被称为二进制信号量 ,因为它只有两个状态:一个许可证可用,或零个许可证可用。 当以这种方式使用时,二进制信号量具有属性(与许多Lock实现不同),“锁”可以由除所有者之外的线程释放(因为信号量没有所有权概念)。 这在某些专门的上下文中是有用的,例如死锁恢复。
  • 此类的构造函数可选择接受公平参数。
  • 通常,用于控制资源访问的信号量应该被公平地初始化,以确保线程没有被访问资源。 当使用信号量进行其他类型的同步控制时,非正常排序的吞吐量优势往往超过公平性。
public class SemaphoreDemo {
    public static void main(String[] args) {
        // 例子:抢车位
        // 线程数量:3:停车位
        // semaphore 限流使用的比较多
        Semaphore semaphore = new Semaphore(3);
        for (int i = 1; i < 6; i++) {
            new Thread(()->{
                try {
                    semaphore.acquire(); // 得到
                    System.out.println(Thread.currentThread().getName()+"抢到车位");
                    TimeUnit.SECONDS.sleep(2); // 停车
                    System.out.println(Thread.currentThread().getName()+"离开车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release(); // 释放
                }
            },String.valueOf(i)).start();
        }
    }
}

原理:

acquire():获得,假如已经满了,等待被释放

release():释放,会将当前的信号量释放+1,然后唤醒等待线程

作用:多个共享资源互斥的使用。并发限流,控制最大的线程数。

8、读写锁

ReadWriteLock

在这里插入图片描述

/**
 * 独占锁(写锁) 一次只能一个线程占有
 * 共享锁(读锁) 多个线程可以同时占有
 */
public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCacheLock myCache = new MyCacheLock();
        // 写入
        for (int i = 1; i <= 5; i++) {
            final int temp = i;
            new Thread(() -> {
                myCache.put(temp + "", temp + "");
            }, String.valueOf(i)).start();
        }
        // 读取
        for (int i = 1; i <= 5; i++) {
            final int temp = i;
            new Thread(() -> {
                myCache.get(temp + "");
            }, String.valueOf(i)).start();
        }

    }
}

// 自定义缓存,有问题,会被插队
class MyCache {
    private volatile Map<String, Object> map = new HashMap<>();

    // 存,写
    public void put(String key, Object value) {
        System.out.println(Thread.currentThread().getName() + "写入" + key);
        map.put(key, value);
        System.out.println(Thread.currentThread().getName() + "写入成功");
    }

    // 读,取
    public void get(String key) {
        System.out.println(Thread.currentThread().getName() + "读取" + key);
        map.get(key);
        System.out.println(Thread.currentThread().getName() + "读取成功");
    }
}

// 加锁的
class MyCacheLock {
    private volatile Map<String, Object> map = new HashMap<>();
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public void put(String key, Object value) {
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "写入" + key);
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "写入成功");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }

    public void get(String key) {
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "读取" + key);
            map.get(key);
            System.out.println(Thread.currentThread().getName() + "读取成功");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }

    }
}

9、阻塞队列

在这里插入图片描述

阻塞队列:
在这里插入图片描述

什么情况下会使用 阻塞队列:多线程并发处理,线程池!

在这里插入图片描述

9.1 四组API

方式抛出异常不会抛出异常阻塞 等待超时等待
添加add()offer()put()offer(…)
移除remove()poll()take()poll(…)
判断队列首element()peek()--
// 抛出异常
public class Test {
    public static void main(String[] args) {
        test1();
    }
    public static void test1(){
        // 队列的大小
        ArrayBlockingQueue  blockingQueue = new ArrayBlockingQueue<>(3);
        System.out.println(blockingQueue.add("a"));
        System.out.println(blockingQueue.add("b"));
        System.out.println(blockingQueue.add("c"));

/*        IllegalStateException: Queue full  抛出异常
        System.out.println(blockingQueue.add("d"));*/

        System.out.println(blockingQueue.element());

        
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());

/*        Exception in thread "main" java.util.NoSuchElementException
        System.out.println(blockingQueue.remove());*/
    }
}
// 有返回值,不抛异常
public static void test2(){
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);
        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("b"));
        System.out.println(blockingQueue.offer("c"));
        System.out.println(blockingQueue.peek());

        System.out.println(blockingQueue.offer("d")); // false

        blockingQueue.poll();
        blockingQueue.poll();
        blockingQueue.poll();

         blockingQueue.poll();  // null
    }
    // 等待、阻塞
    public static void test3() throws InterruptedException {
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);
        blockingQueue.put("a");
        blockingQueue.put("b");
        blockingQueue.put("c");

        // blockingQueue.put("d"); 程序一直阻塞

        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());

        // System.out.println(blockingQueue.take()); 一直阻塞
    }
    // 超时等待
    public static void test4() throws InterruptedException {
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);
        blockingQueue.offer("a");
        blockingQueue.offer("b");
        blockingQueue.offer("c");

        // 阻塞两秒后,超时退出
        blockingQueue.offer("d",2, TimeUnit.SECONDS);

        blockingQueue.poll();
        blockingQueue.poll();
        blockingQueue.poll();
        blockingQueue.poll(2,TimeUnit.SECONDS);
    }

10、同步队列SynchronousQueue

/**
 * 同步队列,
 * 和其他的BlockingQueue不一样,SynchronousQueue 不存储元素
 * SynchronousQueue 实际上就是一个容量为0的阻塞队列
 * put了一个元素,必须从里面先take出来,否则就不能继续put
 */
public class SynchronousQueueDemo {
    public static void main(String[] args) {
        BlockingQueue<String> blockingQueue = new SynchronousQueue<>(); // 同步队列

        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName()+" put 1");
                blockingQueue.put("1");
                TimeUnit.SECONDS.sleep(1);
                System.out.println(Thread.currentThread().getName()+" put 2");
                blockingQueue.put("2");
                TimeUnit.SECONDS.sleep(1);
                System.out.println(Thread.currentThread().getName()+" put 3");
                blockingQueue.put("3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"T1").start();

        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(1);
                System.out.println(Thread.currentThread().getName()+"-"+blockingQueue.take());
                TimeUnit.SECONDS.sleep(1);
                System.out.println(Thread.currentThread().getName()+"-"+blockingQueue.take());
                TimeUnit.SECONDS.sleep(1);
                System.out.println(Thread.currentThread().getName()+"-"+blockingQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"T2").start();
    }
}

11、线程池(重点)

11.1 池化技术

程序的运行会占用系统的资源。优化资源的使用=>池化技术

比如:线程池,连接池,内存池,对象池…

池化技术

提前保存大量的资源,以备不时之需以及重复使用。池化技术应用广泛,如内存池,线程池,连接池等等。内存池相关的内容,建议看看 Apache、Nginx 等开源 web 服务器的内存池实现。

由于在实际应用当做,分配内存、创建进程、线程都会设计到一些系统调用,系统调用需要导致程序从用户态切换到内核态,是非常耗时的操作。因此,当程序中需要频繁的进行内存申请释放,进程、线程创建销毁等操作时,通常会使用内存池、进程池、线程池技术来提升程序的性能。

对连接或线程的复用,并对复用的数量、时间等进行控制,从而使得系统的性能和资源消耗达到最优状态。

线程池好处

1、降低资源消耗

2、提高响应速度

3、方便管理

线程复用、可以控制最大并发数、管理线程。

11.2 线程池三大方法

Excutors 工具类、3大方法

/**
 * Executors 工具类,三大方法
 */
public class Demo01 {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newSingleThreadExecutor();// 单一线程池
//        ExecutorService threadPool = Executors.newFixedThreadPool(5); // 创建一个固定大小的线程池
//        ExecutorService threadPool = Executors.newCachedThreadPool(); // 可伸缩的线程池

        try {
            for (int i = 0; i < 10; i++) {
                // 使用线程池来创建线程
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 线程池用完,程序结束,要关闭线程池
            threadPool.shutdown();
        }
    }
}

11.3 、七大参数

源码分析:

newSingleThreadExecutor

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1, // 核心,最大线程数是1
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

newFixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads, // 核心 最大是传入的参数
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

newCachedThreadPool

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE, // 默认是0个,最大是约等于21亿
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

本质:开启线程只是调用 ThreadPoolExecutor

    public ThreadPoolExecutor(int corePoolSize, // 核心线程池大小
                              int maximumPoolSize, // 最大线程池大小
                              long keepAliveTime, // 存活时间
                              TimeUnit unit, // 超时单位
                              BlockingQueue<Runnable> workQueue, // 阻塞队列
                              ThreadFactory threadFactory, // 线程工场,创建线程的,一般不用动
                              RejectedExecutionHandler handler ) { //拒绝策略
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

在这里插入图片描述

手动创建一个线程池

public class Demo01 {
    public static void main(String[] args) {
        // 自定义线程池
        ExecutorService threadPool = new ThreadPoolExecutor(2,5,
                3,TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                /**
                 * new ThreadPoolExecutor.AbortPolicy()  // 等待区 满了,还有线程进来,就不处理这个线程,直接抛出异常
                 * new ThreadPoolExecutor.CallerRunsPolicy()  // 哪来的回哪去
                 * new ThreadPoolExecutor.DiscardPolicy()  // 队列满了,丢掉任务,不会抛出异常
                 * new ThreadPoolExecutor.DiscardOldestPolicy()  // 队列满了,尝试去和最早的竞争,如果最早的要结束了就执行,没结束就丢掉不会抛出异常
                 */
                new ThreadPoolExecutor.DiscardOldestPolicy()  // 队列满了,尝试去和最早的竞争,如果最早的要结束了就执行,没结束就丢掉不会抛出异常
        );

        // 最大承载:Deque + max
        try {
            for (int i = 0; i < 8; i++) {
                // 使用线程池来创建线程
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 线程池用完,程序结束,要关闭线程池
            threadPool.shutdown();
        }
    }
}

11.4 四种拒绝策略

在这里插入图片描述

AbortPolicy() // 等待区 满了,还有线程进来,就不处理这个线程,直接抛出异常

CallerRunsPolicy() // 哪来的回哪去

DiscardPolicy() // 队列满了,丢掉任务,不会抛出异常

DiscardOldestPolicy() // 队列满了,尝试去和最早的竞争,如果最早的要结束了就执行,没结束就丢掉不会抛出异常

11.5 CPU密集型和IO密集型

最大线程到底应该如何定义?

1、CPU密集型:几核的cpu,就定义几个线程,可以保持cpu的效率最高

Runtime.getRuntime().availableProcessors();

2、IO密集型:判断程序中十分耗IO的线程。

12、四大函数式接口(掌握)

新时代程序员:lambda表达式、链式编程、函数式接口、Stream流式计算

函数式接口:只有一个方法的接口

// Runable接口
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

// @FunctionalInterface
// 简化开发模型,在新版本的框架底层大量应用

在这里插入图片描述

代码测试

Function函数式接口

  • 表示接受一个参数并产生结果的函数。
    在这里插入图片描述
public class Demo01 {
    public static void main(String[] args) {
/*        Function function = new Function<String,String>() {
            @Override
            public String apply(String str) {
                return str;
            }
        };*/

        // lambda 表达式
        Function<String,String> function = (str) -> {
            return str;
        };

        System.out.println(function.apply("abc"));
    }
}

Predicate
在这里插入图片描述

/**
 * 断定型接口:有一个输入参数,返回值只能是布尔值
 */
public class Demo02 {
    public static void main(String[] args) {
/*        Predicate<String> predicate = new Predicate<String>() {
            @Override
            public boolean test(String str) {
                return str.isEmpty();
            }
        };*/

        Predicate<String> predicate = (str)->{
            return str.isEmpty();
        };
        System.out.println(predicate.test("aaa"));
    }
}

Consumer 消费型接口

在这里插入图片描述

/**
 * Consumer 消费型接口只有输入没有返回值
 */
public class Demo03 {
    public static void main(String[] args) {
            /*    Consumer<String> consumer = new Consumer<String>() {
            @Override
            public void accept(String str) {
                System.out.println(str);
            }
        };*/
        Consumer<String> consumer = (str)->{
            System.out.println(str);
        };
        consumer.accept("111");
    }
}

Supplier 供给型接口
在这里插入图片描述

public class Demo04 {
    public static void main(String[] args) {
/*        Supplier<Integer> supplier = new Supplier<Integer>() {
            @Override
            public Integer get() {
                return 1024;
            }
        };*/

        Supplier<Integer> supplier = ()->{
            return 1024;
        };
        System.out.println(supplier.get());
    }
}

13、Stream流式计算

什么是Stream流式计算?

大数据:存储+计算

集合、MySQL本质就是存储

计算都应该交给流来操作

在这里插入图片描述

/**
 * 要求:一行代码实现
 * 现有五个用户,筛选:
 *  1.ID 必须是偶数
 *  2.年龄必须大于23岁
 *  3.用户名转为大写字母
 *  4.用户名字母倒着排序
 *  5.只输出一个用户
 */
public class Test {
    public static void main(String[] args) {
        User u1 = new User(1,"a",21);
        User u2 = new User(2,"b",22);
        User u3 = new User(3,"c",23);
        User u4 = new User(4,"d",24);
        User u5 = new User(5,"e",25);
        // 集合就是存储
        List<User> users = Arrays.asList(u1,u2,u3,u4,u5);

        // 计算交给流
        // lambda表达式,链式编程,函数式接口,Stream流式计算
        users.stream()
                .filter(u->{return u.getId() % 2 == 0;})
                .filter(u->{return u.getAge() > 23;})
                .map(u->{return u.getName().toUpperCase();})
                .sorted((uu1,uu2)->{return uu2.compareTo(uu1);})
                .limit(1)
                .forEach(System.out :: println);
    }
}

14、ForkJoin

ForkJoin 在Jdk1.7,并行执行任务。提高效率,大数据量

大数据:Map Reduce(把大任务拆分为小任务)

ForkJoin特点:工作窃取

这个里面维护的都是双端队列

在这里插入图片描述

ForkJoin

在这里插入图片描述

测试类:

/**
 * 求和计算任务!
 * 如何使用ForkJoin
 * 1.forkJoinPool 通过它来执行
 * 2.计算任务 forkJoinPoll.execute(ForkJoinTask task)
 * 3.计算类要继承ForkJoinTask
 */
public class ForkJoinDemo extends RecursiveTask<Long> {
    private Long start; // 1
    private Long end;


    // 临界值
    private Long temp = 10000L;

    public ForkJoinDemo(Long start, Long end) {
        this.start = start;
        this.end = end;
    }


    @Override
    protected Long compute() {
        long middle = (start + end) / 2; // 中间值
        if ((end - start) < temp) {
            Long sum = 0L;
            for (Long i = start; i <= end; i++) {
                sum += i;
            }
            return sum;
        } else { // 分支合并计算 forkJoin 递归
            ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
            task1.fork(); // 拆分任务,把任务压入线程队列
            ForkJoinDemo task2 = new ForkJoinDemo(middle + 1, end);
            task2.fork(); // 拆分任务,把任务压入线程队列
            return task1.join() + task2.join();
        }
    }
}

测试:

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // test01(); // sum=500000000500000000 时间7629
        // test02(); // sum=500000000500000000 时间4982
        test03(); // sum=500000000500000000 时间202
    }

    // 普通写法
    public static void test01() {
        Long sum = 0L;
        long start = System.currentTimeMillis();
        for (Long i = 1L; i <= 10_0000_0000; i++) {
            sum += i;
        }
        long end = System.currentTimeMillis();
        System.out.println("sum=" + sum + " 时间" + (end - start));
    }

    // ForkJoin
    public static void test02() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();

        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Long> task = new ForkJoinDemo(0L, 10_0000_0000L);
        ForkJoinTask<Long> submit = forkJoinPool.submit(task); // 提交任务
        Long sum = submit.get();

        long end = System.currentTimeMillis();
        System.out.println("sum=" + sum + " 时间" + (end - start));
    }

    // Stream 并行流
    public static void test03() {
        long start = System.currentTimeMillis();
        // (]   parallel()并行计算    reduce()取出结果
        final long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
        long end = System.currentTimeMillis();
        System.out.println("sum=" + sum + " 时间" + (end - start));
    }
}

15、异步回调

Future 设计的初衷:对将来的某个事件的结果进行建模

在这里插入图片描述

16、JMM

JMM:java内存模型,不存在的东西,是一个概念/模型

关于JMM的一些同步的约定

1、线程解锁前,必须把共享变量立刻刷回主存

2、线程加锁前,必须读取主存中的最新值到工作内存中

3、加锁和解锁是同一把锁

8种操作

在这里插入图片描述

存在问题:

在这里插入图片描述

public class JMMDemo {
    private static int num = 0;

    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            while (num == 0){

            }
        }).start();

        TimeUnit.SECONDS.sleep(1);

        num = 1;
        System.out.println(num);
    }
}

// 此时结果输出1,但是程序没有停止

在这里插入图片描述

17、Volatile

Volatile是Java虚拟机提供轻量级的同步机制

1、保证可见性

2、不保证原子性

3、禁止指令重排

17.1 保证可见性

public class JMMDemo {
    // 不加 volatile 程序就会死循环
    // 加 volatile 可以保证可见性
    private volatile static int num = 0;

    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            while (num == 0){

            }
        }).start();

        TimeUnit.SECONDS.sleep(1);

        num = 1;
        System.out.println(num);
    }
}

17.2 不保证原子性

原子性:不可分割

public class VDDemo2 {
    private volatile static int num = 0;

    public static void add() {
        num++; // 不是一个原子性操作
    }

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }
        while (Thread.activeCount() > 2) {
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName() + " " + num);
    }
}

// 结果始终<=20000

在这里插入图片描述

如果不加 lock 或者 synchronized,怎么保证原子性?

使用原子类

在这里插入图片描述

public class VDDemo2 {
    private volatile static AtomicInteger num =new AtomicInteger();

    public static void add() {
        // num++;
        num.getAndIncrement(); // AtomicInteger +1 方法 底层是CAS
    }

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }
        while (Thread.activeCount() > 2) {
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName() + " " + num);
    }
}
// main 20000

这些类的底层都是直接和操作系统挂钩,在内存中修改值!Unsafe类是一个很特殊的存在!

17.3 禁止指令重排

指令重排:你写的代码很可能根本没按你期望的顺序执行,因为编译器和CPU 会尝试重排指令使得代码更快地运行,因为CPU的运行速度大于内存的读写速度。

源代码 --> 编译器优化的重排 -->指令并行也可能会重排 -->内存系统也会重排

加了volatile可以避免指令重排

内存屏障、CPU指令。作用:

1、保证特定的操作的执行顺序

2、可以保证某些变量的内存可见性(利用这些特性,volatile实现了可见性)在这里插入图片描述

Volatile是可以保证可见性,不能保证原子性,由于内存屏障,可以避免指令重排的现象。

18、单例模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

注意:

  • 1、单例类只能有一个实例。
  • 2、单例类必须自己创建自己的唯一实例。
  • 3、单例类必须给所有其他对象提供这一实例。
/**
 * 饿汉式单例
 * 饿汉式:一上来就加载对象
 * 浪费内存!
 */
public class Hungry {
    private Hungry(){

    }

    private final static Hungry HUNGRY = new Hungry();

    public static Hungry getInstance(){
        return HUNGRY;
    }
}
/**
 * 懒汉式单例模式:需要的时候再加载对象
 */
public class LazyMan {
    private LazyMan() {

    }

    private volatile static LazyMan lazyMan;

    private LazyMan getInstance() {  // 多线程情况下不安全!
       /*if (lazyMan == null){
            lazyMan = new LazyMan();
        }*/
        // 双重检测锁模式的懒汉式模式,DCL懒汉式
        if (lazyMan == null) {   // 优化,加锁判断
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    lazyMan = new LazyMan();
                    /**
                     * 不是原子性操作,在极端情况下会有问题。
                     * 1.分配内存空间
                     * 2.执行构造方法,初始化对象
                     * 3.把这个对象指向这个空间
                     * 可能会发生指令重排现象
                     * 所以要加volatile   private volatile static LazyMan lazyMan;
                     */
                }
            }
        }
        return lazyMan;
    }
}

19、理解CAS

什么是CAS?

https://www.cnblogs.com/coloz/p/12159652.html

CAS(Compare And Swap)比较并替换,是线程并发运行时用到的一种技术;

CAS需要三个指令,分别是内存位置(JAVA中的内存地址,V),旧的预期值(A)和新值(B)。CAS执行时,当且仅当V符合预期A的时候,新值更新V的值,否则不执行更新,但是最终都会返回V的旧值,上述的处理过程就是一个原子操作。

  • CAS(Compare And Swap)比较并替换,是线程并发运行时用到的一种技术
  • CAS是原子操作,保证并发安全,而不能保证并发同步
  • CAS是CPU的一个指令(需要JNI调用Native方法,才能调用CPU的指令)
  • CAS是非阻塞的、轻量级的乐观锁
public class CASDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(1);

        System.out.println(atomicInteger.compareAndSet(1, 2)); // true
        System.out.println(atomicInteger.get()); // 2

        System.out.println(atomicInteger.compareAndSet(1, 3)); // false
        System.out.println(atomicInteger.get()); // 2
    }
}

在这里插入图片描述

Unsafe 类
在这里插入图片描述

20、原子引用解决ABA问题

20.1 CAS的ABA问题

ABA问题描述:
1.进程P1在共享变量中读到值为A
2.P1被抢占了,进程P2执行
3.P2把共享变量里的值从A改成了B,再改回到A,此时被P1抢占。
4.P1回来看到共享变量里的值没有被改变,于是继续执行。

public class CASDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(1);

        //========== 捣乱的线程 ==========
        System.out.println(atomicInteger.compareAndSet(1, 2)); // true
        System.out.println(atomicInteger.get()); // 2
        System.out.println(atomicInteger.compareAndSet(2, 1)); // true
        System.out.println(atomicInteger.get()); // 1

        //========== 期望的线程 ==========
        System.out.println(atomicInteger.compareAndSet(1, 3)); // true
        System.out.println(atomicInteger.get()); // 3
    }
}

20.2 原子引用

带版本号的原子操作!

注意

Interger 使用了对象缓存机制,默认范围时 -128 - 127,推荐使用静态工厂方法 valueOf() 获得对象实例,而不是new,因为 valueOf()使用缓存,而new一定会创建新的对象分配新的内存空间。

在这里插入图片描述

public class ABADemo {
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1);

    public static void main(String[] args) {
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp(); //获得版本号
            System.out.println("a1=>" + stamp);

            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(1, 2,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println("a2=>" + atomicStampedReference.getStamp());

            System.out.println(atomicStampedReference.compareAndSet(2, 1,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));

            System.out.println("a3=>"+ atomicStampedReference.getStamp());
        }, "a").start();

        // 乐观锁的原理相同
        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();
            System.out.println("b1=>"+stamp);

            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(atomicStampedReference.compareAndSet(1,3,stamp,stamp+1));
            System.out.println("b2=>"+atomicStampedReference.getStamp());
        },"b").start();
    }

}

21、各种锁的理解

21.1 公平锁、非公平锁

公平锁:非常公平,不能够插队,必须先来后到

非公平锁:非常不公平,可以插队(默认都是非公平)

Lock lock = new ReentrantLock(true);

21.2 可重入锁

可重入锁(递归锁):

​ 可重入锁,指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。

可重入锁的意义之一在于防止死锁。

可重入锁有

  • synchronized
  • ReentrantLock
// synchronized
public class Demo01 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            phone.sms();
        },"A").start();
        new Thread(()->{
            phone.sms();
        },"B").start();
    }
}
class Phone{
    public synchronized void sms(){
        System.out.println(Thread.currentThread().getName()+"sms");
        call();
    }
    public synchronized void call(){
        System.out.println(Thread.currentThread().getName()+"call");
    }
}
/**
 * Asms
 * Acall
 * Bsms
 * Bcall
 */
// Lock
public class Demo02 {
    public static void main(String[] args) {
        Phone2 phone = new Phone2();
        new Thread(() -> {
            phone.sms();
        }, "A").start();
        new Thread(() -> {
            phone.sms();
        }, "B").start();
    }
}

class Phone2 {
    Lock lock = new ReentrantLock();


    public void sms() {
        lock.lock(); // 细节问题,lock锁必须配对,否则就会死在里面
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "sms");
            call();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
            lock.unlock();
        }

    }

    public void call() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "call");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
/**
 * Asms
 * Acall
 * Bsms
 * Bcall
 */

21.3 自旋锁

自旋锁是专为防止多处理器并发而引入的一种锁,它在内核中大量应用于中断处理等部分(对于单处理器来说,防止中断处理中的并发可简单采用关闭中断的方式,即在标志寄存器中关闭/打开中断标志位,不需要自旋锁)。

在这里插入图片描述

// 自定义一个锁 
public class SpinlockDemo {
    // Thread == null
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void myLock() {
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "=>MyLock");

        // 自旋锁
        while (!atomicReference.compareAndSet(null, thread)) {

        }
    }

    // 解锁,加锁
    public void myUnLock() {
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "=>MyUnLock");
        atomicReference.compareAndSet(thread, null);
    }
}
// 测试
import java.util.concurrent.TimeUnit;

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

        new Thread(() -> {
            lock.myLock();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.myUnLock();
            }
        },"a").start();

        new Thread(() -> {
            lock.myLock();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.myUnLock();
            }
        },"b").start();

    }
}

21.4 死锁

死锁是什么?

在多道程序环境中,多个进程可以竞争有限数量的资源。当一个进程申请资源时,如果这时没有可用资源,那么这个进程进入等待状态。有时,如果所申请的资源被其他等待进程占有,那么该等待进程有可能再也无法改变状态。这种情况称为死锁

产生死锁的四个条件?

  • 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
  • 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
  • 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
  • 环路等待条件:在发生死锁时,必然存在一个进程–资源的环形链。

预防死锁

  • 资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
  • 只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)
  • 可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
  • 资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值