0
点赞
收藏
分享

微信扫一扫

java 线程总结以及JUC编程

蚁族的乐土 2022-04-19 阅读 63
java

第一部分,线程知识回顾总结

一。为什么要用多线程?

第一,从CPU角度,现在CPU都是多核的,如果只用单线程那就会浪费CPU

第二,从JVM角度,JVM中内存分为三部分,堆、栈、非堆,而栈空间则是由线程瓜分的,所以如果说我们只用单线程编程,那么就浪费了大量的栈空间内存。

二。线程的生命周期:

新建--准备---运行---挂起(分为等待唤醒挂起和超时挂起)---死亡

三。线程的创建方式:

1.继承Thread,重写run方法,由于java是单继承,所以不推荐

2.实现Runnable接口,实现run方法。任何类都可以实现,但是没有返回值,推荐使用

3.实现Callable接口,实现call方法,需要配合FutureTask类使用,有返回值,推荐使用

4.使用Excutor线程池,配合Runnable接口使用,池化技术,首推使用

四。线程方法介绍

1.yield 线程礼让,这个方式是让当前线程释放CPU,让当前线程进入准备状态,和其他优先级大于等于此线程开始重新争夺CPU资源,但是这个线程不会释放锁

2.sleep 睡眠状态,释放CPU,但是不会释放锁,超时后自动醒过来

3.start 方法,启动线程,线程进入准备状态,开始争夺CPU资源

4.stop 方法,线程停止,已经废弃

5.interrupt 中断线程,线程停止方法,也不推荐

6.isAlive 判断当前线程时候还活着

7.setPriority 设置线程优先级,0-10级

8.getPritory 获取线程优先级

9.setName 设置线程名字 getName 获取线程名字

10.getThreadGroup 获取当前线程所在线程组

11.activeCount 获取当前线程所在线程组中活着线程的数量

12.jon 方法,线程插队,当前线程进入阻塞状态,等待插队线程执行超时或执行完后再继续执行

    join方法底层其实就是wait方法使当前线程进入阻塞状态,所以会释放锁

13.setDameon 设置当前线程为守护线程,一个进程中至少要有两个线程,一个是用户线程(除了守护线程外的线程都是用户线程),另一个是守护线程(也就是GC线程)

14.getState 获取当前线程状态,六种状态

15.object 中的两个方法

    wait 等待,释放CPU,释放锁,使当前线程(哪个线程正在获取CPU,哪个就是当前线程)进入阻塞状态,必须配合锁一起使用

   notify 唤醒,唤醒调用wait方法的对象阻塞的线程,这个方法是随机唤醒,无法做到精准唤醒

   notifyAll 唤醒所有,唤醒调用wait方法的对象阻塞的所有线程

第二部分,JUC编程总结

一。并发和并行区别

     并发:多个线程共同抢夺一个资源

     并行:多个线程共同执行,和并发的核心区别就是是否设计到争夺同一个资源

二。sleep和wait的区别

     1.sleep是线程类中方法,wait是object中方法

     2.sleep可以超时自动醒过来,wait必须要等待唤醒

     3.sleep 是睡觉,不会释放锁,wait是等待,会释放锁

     4.sleep可以在任意地方使用,wait方法必须要配合着锁使用

三。并发编程(生产者消费者模式)思想

     1.创建共享资源类

     2.上锁  等待   干活  唤醒  释放锁

     3.解决虚假唤醒问题

四。隐式锁和显示锁

     隐式锁:synchronized 方法就是隐式锁,就是我们不需要手动上锁解锁,由操作系统自动完成

     显示锁:lock 锁,上锁解锁由我们手动实现,如果忘记解锁则会出现死锁

五。悲观锁和乐观锁

    悲观锁:认为资源的所有操作都是由多个 线程同时并发进行,所以需要严格的进行同步,synchronized 锁就是悲观锁,悲观锁可以保证绝对线程安全,但是效率低

    乐观锁:认为并发一般情况下不会出现,只有在修改数据的时候才会出现问题,CAS机制就是乐观锁

六。重量级锁和轻量级锁

    重量级锁:synchronized 就是重量级锁,所有等待获取锁的线程都会处于等待队列中,并且会进入阻塞状态,释放CPU,因此效率低

    轻量级锁:CAS机制锁,又称为自旋锁,Atomic包下的所有类都是轻量级锁,所有等待线程不会释放CPU,而是不停的重新计算,直到回写数据时发现堆中数据和原始数据一致时才完成操作,效率高,但是如果自旋次数太高则会浪费CPU资源

七。公平锁和非公平锁

    公平锁:所有线程进入等待队列时,是按照先来后到的顺序进入

          有点:先来后到,大家获取到执行的权利均匀分布

          缺点:CPU频繁的切换线程、切换上下文,会降低执行效率

    非公平锁:等待线程不是按照先来后到的顺序加入等待队列的,而是根据一定的规则插队,所有锁的默认模式都是非公平模式

        有点:CPU不用频繁的切换线程,切换上线文,执行效率高

        缺点:线程之间获取到执行的权利不均匀,出现饿死线程问题

八。可重入锁

      锁可以存在嵌套使用,如果不论嵌套多少次,和只使用一次效果一样,那么这个锁就是可重入锁,例如synchronized 和lock都是可重入锁,相反的,非可重入锁,就是必须要先释放掉手上的锁,才可以获取到下一把锁,但是,所有的锁都必须要成对出现,有上锁就是解锁,否则会出现死锁

九。独享锁和共享锁

      独享锁:只能由一个线程获取到的锁,其他线程只能等待当前线程释放锁后才可以获取

      共享锁:可以同时由多个线程并行获取到的锁,可以并发执行

十。lock锁介绍

      1.ReentrantLock 偏向锁,分为公平锁和非公平锁

      2.ReadWriteLock 读写锁,分为ReadLock和WriteLock,读锁和写锁,其中读锁是共享锁,就是可以并发执行读操作,写锁是独享锁,只能由一个线程获取到。这里又涉及到锁降级,就是当写锁再次重入获取读锁时,是可以重入的,此时写锁会降级为读锁,但是仅仅是再可重入方面来看是锁降级了,如果此时其他线程访问读锁时,还是无法获取到的。锁只能降级,不可以升级。

十一。lock和synchronized 的区别

      1.synchronized  是关键字,lock是类

      2.synchronized 是隐式锁,上锁解锁都由系统负责,lock是显示锁,加锁解锁必须由开发人员自己控制,如果忘记解锁则会出现死锁问题

      3.synchronized 是重量级锁非公平锁,lock可以细化为公平锁和非公平锁,读写锁,都是可重入锁

      4.synchronized 中等待队列中线程必须阻塞等待,lock则不一定会一直等待下去,可以结束等待

十二。lock和synchronized 实现生产者消费者模式代码

         1.synchronized  需要配合wait和notifyAll来实现

           Object lock =new Object();

           synchronized (lock){

                      while(条件){

                              lock.wait();

                       }

                       //干活

                       lock。notifyAll();

            }

         缺点:可以实现线程间通信,但是无法实现精准线程间通信,比如说四个线程A B C D要实现A--B---C----D--A---B--C--D...依次执行,则无法实现

          2.lock 需要配合condition来实现,一个lock可以配合多个condition

             lock.lock();

             try{

                 while(条件){

                        condition.await();

                 }

                 //干活

                 condition.signal();

                //或者condition.signalAll();

             }finally{

                  lock.unlock();

            }

      有点:不仅仅可以实现线程间通信,还可以实现精准的线程间通信

十三。解决ArrayList线程不安全问题

       1.使用vector ,原理就是每个方法都是同步方法,效率低

       2.使用Collectons.synchronized()方法处理,原理也是使用使用同步代码块,效率低

       3.使用JUC包下的CopyOnWriteArrayList,写时复制技术,读取数据时不做任何操作,写的时候枷锁--复制一份---操作---写回--解锁,但是原数据要用volatile关键字修饰,保证可见性

十四。解决HashSet线程不安全问题

       1.使用Collectons.synchronized()方法处理,原理就是所有方法改成同步方法

       2.使用CopyOnWriteArraySet来解决,原理也是,元数据用volatile来修饰,保证数据的可见性,读取数据不做任何操作,修改数据枷锁--复制--修改---写回--解锁

 十五。解决map线程不安全问题

       1.使用HashTable,所有方法都是枷锁同步方法,效率低

       2.使用Collection.synchronized()方法处理,原理也是使用synchronized 同步代码块,效率低

       3.使用JUC包下的ConcurrentHashMap,原理和CopyOnWriteArrayList一样

十六。线程同步的三个辅助类

      1.CountDwonLatch 减法计数器,当计数器减小到0时,阻塞队列才会执行,可以实现N个线程等待N个线程执行完再执行效果,但是只能使用一次

      2.CycleBarrier 循环栅栏,加法计数器,当N个线程同时 执行完后,才会执行等待线程,并且可以多次使用

     3.SemapPhore 信号灯,主要解决的就是线程多、资源少问题,没有抢到的线程等待执行,项目上主要用来解决限流问题

十七。线程池

      1.为什么要用线程池?

       线程的创建和销毁是非常消耗CPU的,线程是珍贵的资源,因此不可以频繁的创建和销毁线程;没有节制的创建线程,会引发OOM问题,因此线程的使用是需要控制的。

      2.线程池 Executor ,具体实现类为ThreadPoolExecutor

       七个参数含义:

       1)核心线程数量,常驻内存

       2)最大线程数量,包括核心线程

       3)非核心线程空闲最大存活时长

       4)非核心线程存活时长单位

       5)阻塞队列

       6)线程工厂

       7)拒绝策略,四大拒绝策略

      线程池工作流程:

      当任务来了,先用核心线程处理,如果任务超过核心线程,则放入阻塞队列等待,如果阻塞队列满了,则将后面新任务放入非核心线程执行,如果所有线程和阻塞 队列都满了,此时再来新任务,则会触发拒绝策略

       五大拒绝策略:

       1)AbortPolicy 拒绝策略,并且跑出异常

       2)CallerRunsPolicy 拒绝,哪里来的哪里去,交给发送线程任务 线程处理

       3)DiscardPolicy 拒绝,并且不会跑出异常

       4)DiscardOldestPolicy 尝试和队列中等待时间最长的任务竞争,如果竞争失败了,则直接丢弃此任务,且不会跑出异常

       5)自定义拒绝 策略,上面四种拒绝策略其实在实际开发中用的可能比较少,因为任务来了,不论怎么拒绝都不符合需求,因此基本上都是自定义拒绝策略

      线程池最大线程的设置规则:

      1)cup密集型任务,就是任务不怎么耗时,理解为短而快的任务,此时应该根据CPU的个数来设置最大线程数,不至于cpu来回切换线程降低工作效率

      2)IO密集型,也就是任务都是耗时操作,此时最大线程数应大于任务数,保证耗时任务都可以处理,不至于卡顿

十八。Atomic 原子操作类

      Atomic原子操作类,底层是通过unsafe的CAS机制保证原子操作的,因此是轻量级锁。基本数据类型和引用数据类型都可以用,缺点就是只能同步一个共享变量。

十九。死锁问题

       死锁条件

      1.互斥条件,并发操作,且一次只能有一个线程访问资源

      2.请求与保持,一个线程获取到资源,需要保持资源不放

      3.不剥夺条件:已获得的资源线程,在未使用完前,不可以强制被剥夺

      4.循环等待条件:若干线程之间形成一种环形循环等待资源

     死锁问题解析:

      1.持有多个锁的情况下,持有锁的顺序不一致造成

      2.锁嵌套造成

     如何发现死锁:

     通过jstack拿到应用的堆转储文件,分析文件

第三部分。锁总结

使用锁的优先顺序如下:

1.如果只有一个共享变量,则优先考虑Atomic类,使用CAS机制实现同步

2.如果是多个共享遍历,则优先考虑lock锁

   1)使用读写锁

   2)使用非公平锁

   3)使用公平锁

3.使用synchronized 关键字同步

       

举报

相关推荐

0 条评论