并发编程

线程与进程

  • 线程就是一个指令流,将一条条指令以一定的顺序交给CPU运行,是操作系统进行运算调度的最小单位
  • 进程是正在运行程序的实例,进程包含了线程
  • 不同进程使用不同的内存空间,在当前进程下的所有线程可以共享内存空间。

并发与并行

  • 并发(多线程操作同一个资源,交替执行)
    CPU一核, 模拟出来多条线程,天下武功,唯快不破,快速交替
  • 并行(多个人一起行走, 同时进行)
    CPU多核,多个线程同时进行 ; 使用线程池操作

同步和异步

  • 同步:发出一个调用以后,一直等待,直到得到结果返回
  • 异步:调用在发出以后,不用等待返回结果,直接返回

线程创建的方式

  • 继承Thread类

    • 重写run方法,调用start方法开启线程
  • 实现runnable接口

    • 重写run方法,调用start方法开启线程
  • 实现Callabel接口

    • 重写call方法(有返回值,用Future/FutureTask的get方法获取),调用start方法启动线程
  • 线程池创建创建线程

runnable接口和callable接口的区别

  • runnable接口的run方法没有返回值,callable接口的call方法有返回值,可以通过Future/FutureTask的get方法获取
  • runnable的run方法不能抛出异常,只能捕获,callable的call方法可以抛出异常

run和start方法的区别

  • run方法封装了线程要执行的方法,可以被调用多次(普通方法调用)
  • start方法用来启动线程,通过该方法执行run方法,只能被调用一次

线程的状态

public enum State {
	// 创建
	NEW,
	// 可运行
	RUNNABLE,
	// 阻塞
	BLOCKED,
	// 等待,死死地等
	WAITING,
	// 超时等待
	TIMED_WAITING,
	// 终止
	TERMINATED;
}

  • 新建
    • 当一个线程对象被创建,但还未调用 start 方法时处于新建状态
    • 此时未与操作系统底层线程关联
  • 可运行
    • 调用了 start 方法,就会由新建进入可运行
    • 此时与底层线程关联,由操作系统调度执行
  • 终结
    • 线程内代码已经执行完毕,由可运行进入终结
    • 此时会取消与底层线程关联
  • 阻塞
    • 当获取锁失败后,由可运行进入 Monitor 的阻塞队列阻塞,此时不占用 cpu 时间
    • 当持锁线程释放锁时,会按照一定规则唤醒阻塞队列中的阻塞线程,唤醒后的线程进入可运行状态
  • 等待
    • 当获取锁成功后,但由于条件不满足,调用了 wait() 方法,此时从可运行状态释放锁进入 Monitor 等待集合等待,同样不占用 cpu 时间
    • 当其它持锁线程调用 notify() 或 notifyAll() 方法,会按照一定规则唤醒等待集合中的等待线程,恢复为可运行状态
  • 有时限等待
    • 当获取锁成功后,但由于条件不满足,调用了 wait(long) 方法,此时从可运行状态释放锁进入 Monitor 等待集合进行有时限等待,同样不占用 cpu 时间
    • 当其它持锁线程调用 notify() 或 notifyAll() 方法,会按照一定规则唤醒等待集合中的有时限等待线程,恢复为可运行状态,并重新去竞争锁
    • 如果等待超时,也会从有时限等待状态恢复为可运行状态,并重新去竞争锁
    • 还有一种情况是调用 sleep(long) 方法也会从可运行状态进入有时限等待状态,但与 Monitor 无关,不需要主动唤醒,超时时间到自然恢复为可运行状态

死锁

多个线程被阻塞,他们中的一个或多个线程都在等待某个资源被释放,从而造成无限期的阻塞

预防死锁
  • 破坏请求并保持条件:一次性申请所有的资源
  • 破坏不剥夺条件:占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
  • 破坏循环等待条件:靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。
避免死锁

银行家算法

wait 和 sleep 方法的不同

  • wait是Object的方法,sleep是Thread的方法
  • wait会释放锁,sleep不会释放锁
  • wait必须在同步代码块中使用,sleep可以在任何地方使用
  • wait不需要捕获异常,sleep必须要捕获异常

sleep与yield方法的不同

  • sleep方法给其他线程运行机会时不会考虑线程优先级,yield方法只会给相同或更高优先级的线程运行机会
  • 线程执行sleep方法后进入超时等待状态,执行yield方法后转入就绪状态
  • sleep方法声明会抛出异常,yield方法声明不会抛出异常
  • sleep方法中需要指定时间参数,yield方法不需要,受jvm控制

synchronized关键字

作用:
  • 原子性:一个或多个操作要么全部执行成功,要么全部执行失败。synchronized关键字可以保证只有一个线程拿到锁,访问共享资源。
  • 可见性:当一个线程对共享变量进行修改后,其他线程可以立刻看到。执行synchronized时,会对应执行 lockunlock原子操作,保证可见性。
  • 有序性:程序的执行顺序会按照代码的先后顺序执行
用法:
  • 修饰普通方法
  • 修饰静态方法
  • 指定对象,修饰代码块
特点
  • 阻塞未获的锁、竞争同一个对象锁的线程
  • 获取锁无法设置超时
  • 非公平锁、悲观锁、可重入锁、独占锁
  • 控制等待和唤醒需要结合加锁对象的wait(), notify(),notifyAll()方法
  • 锁的功能是JVM层面实现的
  • 加锁代码块执行完毕或执行过程中出现异常会自动释放锁
原理
  • 同步方法通过加ACC_SYNCHRONIZED标识实现线程执行权的控制
  • 同步代码块是通过monitorenter和monitorexit指令获取线程的执行权

BlockingQueue

父类:Collection

实现类:ArrayBlockingQueue、LinkedBlockingQueue

img

BlockingQueue

父类:Collection

实现类:ArrayBlockingQueue、LinkedBlockingQueue

img

public class BlockingQueueTest2 {
    public static void main(String[] args) {
        // 创建一个容量为1的BlockingQueue
        BlockingQueue<String> bq = new ArrayBlockingQueue<>(1);
        // 启动3条生产者线程
        new Producer(bq).start();
        new Producer(bq).start();
        new Producer(bq).start();
        // 启动一条消费者线程
        new Consumer(bq).start();
    }
}

class Producer extends Thread {
    private BlockingQueue<String> bq;
    public Producer(BlockingQueue<String> bq) {
        this.bq = bq;
    }
    public void run() {
        String[] strArr = new String[]
                {
                        "Java",
                        "Struts",
                        "Spring"
                };
        for (int i = 0; i < 999999999; i++) {
            System.out.println(getName() + "生产者准备生产集合元素!");
            try {
                Thread.sleep(200);
                // 尝试放入元素,如果队列已满,线程被阻塞
                bq.put(strArr[i % 3]);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            System.out.println(getName() + "生产完成:" + bq);
        }
    }
}

class Consumer extends Thread {
    private BlockingQueue<String> bq;

    public Consumer(BlockingQueue<String> bq) {
        this.bq = bq;
    }
    public void run() {
        while (true) {
            System.out.println(getName() + "消费者准备消费集合元素!");
            try {
                Thread.sleep(200);
                // 尝试取出元素,如果队列已空,线程被阻塞
                bq.take();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            System.out.println(getName() + "消费完成:" + bq);
        }
    }
}

CountdownLaunch

  • countdown( )
public class CountDownLatchTest {
    public static void main(String[] args) {
        CountDownLatch latch = new CountDownLatch(3);
        Increment increment = new Increment(latch);
        Decrement decrement = new Decrement(latch);
        new Thread(increment).start();
        new Thread(decrement).start();
    }

}

//拼车的人
class Decrement implements Runnable {

    CountDownLatch countDownLatch;

    public Decrement(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
        try {

            for (long i = countDownLatch.getCount(); i > 0; i--) {
                Thread.sleep(1000);
                System.out.println("新增一人参与拼车");
                this.countDownLatch.countDown();
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

//车主
class Increment implements Runnable {

    CountDownLatch countDownLatch;

    public Increment(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
        try {
            System.out.println("等待人满发车");
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("三人已满,准备发车");
    }
}

Semphore

  • acquire
  • release
public class SemaphoreTest {
    //一个食堂窗口最多容纳10个人打饭
    private static Semaphore semaphore = new Semaphore(10);

    public static void main(String[] args) {
        // 模拟50个学生来到打饭窗口
        for (int i = 0; i < 50; i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println("=====" + Thread.currentThread().getName() + "学生来到打饭窗口");
                        if (semaphore.availablePermits() == 0) {
                            // 没有人手打饭了
                            System.out.println("人手不足,请耐心排队," + Thread.currentThread().getName() + "学生正在等待");
                        }
                        // 排到队了,准备打饭
                        semaphore.acquire();
                        System.out.println(Thread.currentThread().getName() + "成功轮到打饭");
                        //模拟打饭时间
                        Thread.sleep(1000);
                        //离开打饭窗口
                        semaphore.release();
                        System.out.println(Thread.currentThread().getName() + "离开窗口");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            thread.start();
        }

    }
}

线程池

三大方法
  • newSingleThreadExecutor单线程化线程池
  • newFixedThreadPool定长线程池
  • newCachedThreadPool可缓存线程池

三大方法虽然方便,但不建议使用,建议使用ThreadPoolExecutor

七大参数
  • 核心线程数corePoolSize

  • 最大线程数maxinumPoolSize=核心线程数+工作队列大小

  • 生存时间keepAliveTime

  • 时间单位unit

  • 工作队列workQueue

  • 线程工厂threadFactory

  • 拒绝策略handler

四大拒绝策略

如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任务时,就会采用拒绝策略

1.AbortPolicy:直接抛出异常,默认策略;

2.CallerRunsPolicy:调用execute的线程本身运行该任务;

3.DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;

4.DiscardPolicy:直接丢弃任务;

public class ThreadPoolDemo {
    private static final int CORE_POOL_SIZE=5;
    private static final int MAX_POOL_SIZE = 10;
    private static final int QUEUE_CAPACITY = 100;
    private static final Long KEEP_ALIVE_TIME = 1L;

    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS, new ArrayBlockingQueue<>(QUEUE_CAPACITY), new ThreadPoolExecutor.CallerRunsPolicy());
        for (int i = 0; i < 10; i++) {
            MyRunnable worker = new MyRunnable(i);
            executor.execute(worker);
        }
    }
}
class MyRunnable implements Runnable{
    int name;
    MyRunnable(int name){
        this.name=name;
    }
    @Override
        public void run() {
        System.out.println(Thread.currentThread().getName()+"-start-"+new Date());
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread().getName()+"-end-"+new Date());
    }
}

想要获取结果可用Callable+FutureTask.

确定线程池的核心线程数

  • IO密集型任务

一般来说:文件读写、DB读写、网络请求等

推荐:核心线程数大小设置为2N+1 (N为计算机的CPU核数)

  • CPU密集型任务

一般来说:计算型代码、Bitmap转换、Gson转换等

推荐:核心线程数大小设置为N+1 (N为计算机的CPU核数)

//获取CPU核数
Runtime().getRuntime().availableProcessors();

四大函数式接口

函数式接口:接口中只有一个抽象方法

使用Lamda表达式进行简化

  • Function(函数型接口)有传入值,有返回值

    Function<String,String> function=i->{return i};
    function.apply("hello");
    
  • Predicate(判断型接口) 有传入值,返回一个bool类型

Predicate<String> predicate=i->{return i.empty();};
predicate.test("hi");
  • Consumer(消费型接口)有传入值,没有返回值
Consumer<String> consumer=i->{System.out.println(i);};
consumer.accept("a");                                                 
  • Supplier(供给型接口)无传入值,有返回值

    Supplier<String> supplier=()->{return "hi"}
    supplier.get();
    

JMM(Java内存模型)

是jvm规范中所定义的一种内存模型。围绕着原子性、可见性、有序性三个特征建立起来的。

关于JMM的一些同步的约定:
1、线程解锁前,必须把共享变量立刻刷回主存。
2、线程加锁前,必须读取主存中的最新值到工作内存中!
3、加锁和解锁是同一把锁

八种内存交互操作:

  • lock(锁定),作用于主内存
  • read(读取),作用于主内存
  • load(加载),作用于工作内存
  • use(使用),作用于工作内存
  • assign(赋值),作用于工作内存
  • store(存储),作用于工作内存
  • write(写入),作用于主内存
  • unlock(解锁),作用于主内存

JMM对八种内存交互操作的规定:

  • 不允许read、load、store、write操作之一单独出现,也就是read操作后必须load,store操作后必须write。
  • 不允许线程丢弃他最近的assign操作,即工作内存中的变量数据改变了之后,必须告知主存。
  • 不允许线程将没有assign的数据从工作内存同步到主内存。
  • 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过load和assign操作。
  • 一个变量同一时间只能有一个线程对其进行lock操作。多次lock之后,必须执行相同次数unlock才可以解锁。
  • 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值。在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值。
  • 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量。
  • 一个线程对一个变量进行unlock操作之前,必须先把此变量同步回主内存。

volatile

  • 保证可见性
  • 不保证原子性
  • 禁止CPU进行指令重排序

CAS

  • compareandSet,是一种乐观锁的思想,在不加锁的情况下可以保证线程操作共享资源的原子性,利用了自旋锁
  • 底层依赖于Unsafe类,直接去操作内存。Unsafe类中都是native修饰的方法。
  • CAS操作包含三个操作数,内存值(V),期望值(A),新值(B)。若内存中的值与期望值一样,则将内存中的值更新为新值B。
  • 缺点:导致ABA问题(狸猫换太子)
    • ABA:如果一个线程t1正修改共享变量的值A,但还没修改,此时另一个线程t2获取到CPU时间片,将共享变量的值A修改为B,然后又修改为A,此时线程t1检查发现共享变量的值没有发生变化,但是实际上却变化了。
    • 解决办法:AutomicStampedReference,CompareAndSet方法会判断当前的引用是否等于预期引用,再判断当前版本号是否跟原来的一致,全部相等,才更新值。

自旋锁

循环加锁 -> 等待的机制

优点:减少上下文切换的消耗

缺点:循环占有,浪费CPU资源

乐观锁与悲观锁

  • 乐观锁:不怕别的线程来修改共享变量,如:CAS,适合读操作多的场景
  • 悲观锁:防止其他线程来修改共享变量,有上锁、解锁的操作,如:synchronized,适合写操作多的场景

独享锁与共享锁

  • 独享锁一次只能被一个线程持有,如Synchronized,ReentrantLock
  • 共享锁一次可以被多个线程持有,如ReadWriteLock中返回的ReadLock

AQS

  • AQS是一个Java线程同步的框架,是JDK中多锁工具的核心实现框架
  • AQS中维护了一个信号量state和一个由线程组成的双向链表队列。
  • 在可重入的这个场景下,state用来表示加锁的次数,0表示无锁,每加一次锁state就加1,释放锁就减1;

可重入锁ReentrantLock

可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出死锁

  • 相较于synchronized 特点
    • 可以设置超时时间
    • 可中断
    • 可设置公平锁
  • 实现原理:CAS+AQS

synchronized与ReentrantLock的区别

  • synchronized是非公平锁,ReentrantLock可以设置公平锁
  • ReentrantLock可以设置超时时间
  • synchronized竞争锁时会一直等待,ReentrantLock可以尝试获取锁,并得到获取结果
  • 锁等待和唤醒的方式不同,synchronized使用wait,notify,notifyAll方法,ReentrantLock使用Condition的await,signal,signalAll方法
  • synchronized是JVM层面实现的,ReentrantLock是基于JDK代码层面实现的
  • synchronized的加锁、释放锁是自动的,而ReentrantLock需要手动设置

单例模式

  • 饿汉式
/**
 * 饿汉式单例
 */
public class Hungry {

    /**
     * 可能会浪费空间
     
    private byte[] data1=new byte[1024*1024];
    private byte[] data2=new byte[1024*1024];
    private byte[] data3=new byte[1024*1024];
    private byte[] data4=new byte[1024*1024];

    private Hungry(){

    }
    private final static Hungry hungry = new Hungry();

    public static Hungry getInstance(){
        return hungry;
    }

}

  • 懒汉式DCL
//懒汉式单例模式
public class LazyMan {

    private static boolean key = false;

    private LazyMan(){
        synchronized (LazyMan.class){
            if (key==false){
                key=true;
            }
            else{
                throw new RuntimeException("不要试图使用反射破坏异常");
            }
        }
        System.out.println(Thread.currentThread().getName()+" ok");
    }
    private volatile static LazyMan lazyMan;

    //双重检测锁模式 简称DCL懒汉式
    public static LazyMan getInstance(){
        //需要加锁
        if(lazyMan==null){
            synchronized (LazyMan.class){
                if(lazyMan==null){
                    lazyMan=new LazyMan();
                    /**
                     * 1、分配内存空间
                     * 2、执行构造方法,初始化对象
                     * 3、把这个对象指向这个空间
                     *
                     *  就有可能出现指令重排问题
                     *  比如执行的顺序是1 3 2 等
                     *  我们就可以添加volatile保证指令重排问题
                     */
                }
            }
        }
        return lazyMan;
    }
    //单线程下 是ok的
    //但是如果是并发的
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        //Java中有反射
//        LazyMan instance = LazyMan.getInstance();
        Field key = LazyMan.class.getDeclaredField("key");
        key.setAccessible(true);
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true); //无视了私有的构造器
        LazyMan lazyMan1 = declaredConstructor.newInstance();
        key.set(lazyMan1,false);
        LazyMan instance = declaredConstructor.newInstance();

        System.out.println(instance);
        System.out.println(lazyMan1);
        System.out.println(instance == lazyMan1);
    }
}

  • 静态内部类
//静态内部类
public class Holder {
    private Holder(){

    }
    public static Holder getInstance(){
        return InnerClass.holder;
    }
    public static class InnerClass{
        private static final Holder holder = new Holder();
    }
}

反射导致单例不安全

  • 枚举
//enum 是什么? enum本身就是一个Class 类
public enum EnumSingle {
    INSTANCE;
    public EnumSingle getInstance(){
        return INSTANCE;
    }
}

class Test{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        EnumSingle instance1 = EnumSingle.INSTANCE;
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        //java.lang.NoSuchMethodException: com.ogj.single.EnumSingle.<init>()

        EnumSingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}

使用枚举,我们就可以防止反射破坏了。

反编译源码

//enum 是什么? enum本身就是一个Class 类
public enum EnumSingle {
    INSTANCE;
    public EnumSingle getInstance(){
        return INSTANCE;
    }
}

class Test{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        EnumSingle instance1 = EnumSingle.INSTANCE;
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        //java.lang.NoSuchMethodException: com.ogj.single.EnumSingle.<init>()

        EnumSingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}

ConcurrentHashMap

  • 1.7中底层采用分段的数组+链表
  • 1.8中底层采用数组+链表/红黑树,CAS+Synchronized来保证并发安全

ThreadLocal

ThreadLocal为每个线程都分配一个独立的线程副本从而解决了变量并发访问冲突的问题。ThreadLocal 同时实现了线程内的资源共享。

三个主要方法:

  • set(value) 设置值
  • get() 获取值
  • remove() 清除值

在使用ThreadLocal的时候,强烈建议:务必手动remove

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

容与0801

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

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

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

打赏作者

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

抵扣说明:

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

余额充值