【JUC并发编程】

1.线程和进程的区别

一个进程含有多个线程

2.java Thread 线程的6个状态

①创建 new
②运行 runnable
③阻塞 blocked
④等待 waiting
⑤超时等待 timed_waiting
⑥终止 terminated

3.wait和sleep的区别

①来自不同的类 wait来自Object sleep来自Thread

②锁的释放 wait会释放锁 sleep不会释放锁

③使用的范围 wait必须在同步代码块中使用 sleep可以在任何地方使用

④释放需要捕获异常 wait不需要捕获异常(现在好像也要了) sleep必须要捕获异常(可能会发生超时等待)

4.synchronized和lock

synchronized:同步锁,悲观锁,所有的线程在执行时都需要进行排队
使用方法:直接在资源类或者方法上加上synchronized关键字即可

lock:
使用方法:创建lock锁(使用多态new出其实现类) 在业务代码前调用lock()方法进行加锁,在业务代码结束后调用unlock()方法解锁(一般都是加在finally块中)
区别:

synchronized是java的关键字,lock是java对象,是一个java类

synchronized无法判断锁的状态,lock可以判断

synchronized会自动释放锁,lock必须得手动释放,如果不释放,会造成死锁

synchronized执行过程中出现阻塞,则后续线程会一直等待,而lock锁不一定会一直等待
synchronized可重入锁,不可以中断,非公平得,而lock锁可重入锁,可以判断锁,非公平(可以自己设置)

synchronized适合锁少量得同步代码,而lock锁适合锁大量得同步代码

lock锁更加灵活,多变,能够根据不同得业务场景变换不同的机制

5.生产者和消费者模式(线程间通信)

synchronized方式:线程间通信依托于wait和notify(notifyAll)去实现

lock方式:依托于lock锁的监视器condition类的await()和signal()(signalAll())方法去实现

6.线程的虚假唤醒

问题原因:

①JUC官方文档上有对虚假通信的描述,在多线程并发情况下,当调用一个对象中的某个方法时,if(num == 1){wait()} num++; (满足num==1时,进行等待,唤醒后进行+1),后续在唤醒该线程时,如果因为并发导致两个线程同时都在等待,则唤醒的时候,第一个线程唤醒后执行了num++,第二个线程不会再
重新去判断是否满足if条件了,因为线程的唤醒机制在唤醒一个线程后会直接去执行wait()方法之后的代码,所以num会加两次,导致数据有误

②解决思路:在线程被唤醒后去重新判断是否满足条件

③具体方案:将if关键字改成while 因为while机制会在执行完while中的代码后去重新再去判断,所以能够解决并发情况下导致的这类虚假唤醒问题

7.八锁现象结论

①在讨论线程的执行顺序问题的时候,需要先弄清楚锁的是什么东西

②同一个对象里面有多个同步方法的时候,它锁的是这个对象的调用者 new 出这个对象的时候,就相当于这个对象是被锁的对象,所以执行顺序一定是先执行前面的同步方法,再会去执行后面的同步方法

③同一个对象里面有多个同步方法的时候,如果有的同步方法是static修饰的,则锁的对象不同,不是static修饰的同步方法它锁的是调用者,而static修饰的同步方法,它锁的是class模板,因此它会出现两种不同的锁,所以谁先执行就看代码的执行时间顺序了

④同一个对象它只有一个class模板,所以new出两个相同的对象时,其用static修饰的同步方法也是相同的锁

④非同步方法是不受锁的限制的,其也可以相当于是对象原本的锁的另外一种锁,谁执行时间排在前面就执行谁

⑥所以综上所述,弄清楚锁的是什么东西很重要,锁的是不同的东西,就可以理解为两把不同的锁了,只有都是同一锁的时候,它才会遵循同步原则,一定会遵循先后顺序

8.多线程环境下的集合类都是不安全的

1.在多线程环境下去多同一个集合进行读写操作的时候是不安全的,并发一大的话就会出现异常 ConcurrentModificationException(多线程并发异常)

2.解决方式:

①将集合转成vector
②利用collections工具类将集合转成线程安全的集合
③使用JUC中的courrent包下的CopyWriteCollection去创建集合

3.三种解决方式的区别:

①前面两种都是利用synchronized去解决多并发环境下的数据同步问题,效率比较低
②第三中是利用lock锁去实现线程同步问题,并且在每次对集合进行写操作时,会copy一份新的集合数据出来,在新的上面去进行写操作,写完之后再替换掉原来的,这样效率是最高的,也是最安全的

9.callable接口

①为什么要使用到callable
因为原本的runnable接口进行线程的创建,它是没有具体返回值和异常处理的,直接就是重写runnable接口,而callable去创建线程的话,可以获取返回值,做异步处理,并且可以处理相关异常

②使用callable的原理:
所有的线程去执行都需要去 new Thread() 并且调用start()方法去执行,但是Thread的构造方法没有callable相关参数
但是thread的构造方法有跟futureTask挂钩的,futureTask的构造方法含有callable接口,因此futureTask可以被叫做Thread和callable之间扯上关系的桥梁,叫做适配器类

10.线程辅助类

①CountDownLatch(线程计数器-减)
②CyclicBarrier(线程计数器-加)
③Semaphore(线程信号量)

11.读写锁(copyWriteLock)

①对lock更小粒度的划分
②其包含一个读锁和一个写锁,读操作支持共享并行读取,写操作必须要同步,一次有一个线程进行写操作(保证了写操作的原子性)
具体实现:请参考 D://study-demo/lock-demo项目代码

12.阻塞队列

阻塞队列的相关概念:
①阻塞队列 (blockingQueue)–> 定长的集合,当长度到达峰值时会涉及到四种处理策略
②其继承与于 Queue(队列), Queue 与 List 和 Set 同级,共同继承于 Collection,Queue还有一个同级的兄弟队列叫做 Dueue(双端队列–两端可以同时存和取),Queue只能从从左边进行存,从右边取( FIFO 先进先出 )

③Queue又有子类 BlockingQueue,BlockingQueue有众多的实现类,其主要的两个实现类为:ArrayBlockingQueue(基于数组的阻塞队列),LinkedBlockingQueue(基于链表的阻塞队列)

②阻塞队列存取时超出队列长度或者队列为空时的四组操作策略

①add() remove() 直接抛出异常
②offer() poll() 不抛出异常,返回false和null
③put() take() 一直阻塞等待
④offer(time) poll(time) 自定义时间,延时等待,超时就返回false和null
具体实现:请参考 D://study-demo/lock-demo项目代码

13.同步队列

①概念:同步队列,也是 BlockingQueue的一个实现类,不存储元素(每次存入一个元素,必须等待元素被取出后,才能再进行存储)
②存取方法:存 :put() 取 :take()
具体实现:请参考 D://study-demo/lock-demo项目代码

14.线程池

①使用 executors 工具类创建线程池,不推荐使用 (阿里巴巴开发手册明确规定了不使用 executors 去创建线程池,不安全!)
②使用原生的 ThreadPoolExecutor创建线程池
①七大参数: 核心线程数 最大线程数 等待时间 时间单位 阻塞队列 线程工厂 拒绝策略
②工作原理:
1.任务数小或者等于核心线程数 2 时,就交由核心线程处理即可
* 2.并发任务数超过核心线程数时,会进入阻塞队列进行等待,先进先出,等前面核心线程有空闲时,队列中最先进入的会被弹出,去执行任务
* 3.如果队列达到了最大的大小 3,则会开启核心线程数以外的线程,但不能高于最大线程数 5
* 如果超出核心线程数的线程空闲时间超过了 keepAliveTime (3 s) ,则会释放该线程
* 4.如果任务队列已满,并且已经达到了最大线程数,则会触发设置的拒绝策略(总共有 4 种,详情可以去参考源码)
* AbortPolicy(); // 直接抛出异常
* CallerRunsPolicy(); // 将线程原封不动的返回,交由其原线程去处理
* DiscardPolicy(); //不进行任何处理,自生自灭
* DiscardOldestPolicy(); // 将该线程与队列中最先进去的线程竞争,谁赢了,谁执行
* 首先判断线程池是否刚好执行完前一个任务,如果是,则会尝试去执行该任务,如果不是,则直接丢弃
*
③注意事项:
* 1.最大线程数的设置:
* 如果是 CPU密集型 : 最大线程数就设置为电脑的CPU核数为最优
* 如果是 IO密集型: 任务中有多个需要大量IO资源的线程时,最大线程数设置为需要IO的线程数的 2倍为最优
* 2.任务执行完一定要记得关闭!shutDown();

15.四大函数式接口

①函数性接口 Function
②断定型接口 Predicate
③供给型接口 Supplier
⑤消费型接口 Consumer
注意: 平常遇到函数式接口,尽量都采用lambda表达式进行书写,详细写法可参考 D://study-demo/lock-demo项目代码

16.stream 流

①为什么会有stream流的产生: 集合只用来存储,涉及到计算的都采用stream流,stream流专为计算而生,用stream流可以极大的简化操作,并且不用顾及效率问题…
②stream流的用法: 可参考 D://study-demo/lock-demo项目代码 结合官方文档,多加练习

17.forkJoin

①为何使用forkjoin: 涉及到对大数据量,高并发的操作时,建议使用forkJoin方式去对任务进行拆分,和合并,提高了任务执行的效率
②原理: 将一个单一的任务,拆分为多个小人物,再放入线程池中异步执行,最终将结果再合并,返回最终的结果
③使用步骤,
①创建一个任务类,继承RecursiveTask类(RecursiveTask类继承于forkJoinTask类),然后重写compute()方法
②在具体使用时,创建一个ForkJoinPool(任务池),然后新建一个之前创建好的任务类,将任务类提交到任务池中执行,再获取返回结果即可
③具体代码,请参考 D://study-demo/lock-demo项目代码 (可适当的结合百度资料,进行多方位理解和学习)
④stream流有一个分支叫并行流 (LongStream DoubleStream…),用其去对相对应数值进行操作,效率是所有方式中最高的!具体可参考: D://study-demo/lock-demo项目代码

  • 20
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值