java多线程及线程安全相关知识回顾

多线程部分

1.创建一个线程有哪些方式?

  1. 继承Thread类,重写run()方法。
  2. 实现Runnable接口,重写run()方法。
  3. 实现callable接口,重写call方法(可以获取线程的返回值、参数一些信息)。

2.拒绝策略有哪些?

当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize时,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:

  1. ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
  2. ThreadPoolExecutor.DiscardPolicy:丢弃任务但并不抛出异常。
  3. ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。
  4. ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务。

3.线程的生命周期

启动一个线程的start()方法、新建、就绪、运行、阻塞、销毁

4.创建线程池的方式

  1. newSingleThreadExecutor():创建一个单一线程池(始终只有一个线程,实际情况下很少这么用)
  2. newCachedThreadPool():创建一个可缓存线程池(无限制线程的大小。弊端是init的最大值,会无限制创建线程,浪费资源,有系统崩溃的风险)
  3. newFixedThreadPool():创建一个定长线程池(自定义设置线程池的大小,在实际使用中稍微用的多一点)
  4. newSchedulThreadPool():创建一个定时线程池(可以指定时间跑任务,基本用不到)

5.线程池的底层原理

创建线程池的时候,开始一个线程都没有。随着任务的增加创建线程,当前线程数小于核心线程数的时候创建新线程,否则放入阻塞队列,阻塞队列未满继续放入。阻塞队列满了,判断最大线程,如果当前线程数小于最大线程,继续创建线程。如果当前线程数大于最大线程数,使用拒绝策略。

6.线程池参数有哪些?

  1. corePoolSize:线程池核心线程大小。
  2. maximumPoolSize:线程池最大线程数量。
  3. keepAliveTime:空闲线程存活时间。
  4. unit:空闲线程存活的时间单位。
  5. workQuene:工作队列。
  6. threadFactory:线程工厂。
  7. handler:拒绝策略。

7.如何设置线程池参数?

  1. CPU密集度:2*CPU核数(例如四核,核心线程数设置为8)
  2. IO密集度:3*CPU核数(线程池的大小设置的稍微大点,最大线程数必须大于核心线程数)

线程安全部分

1.解决线程安全的方式

  1. Synchronized(互斥锁。它是通过监视器jvm帮我们完成。在锁的代码片段加入monitorenter(进入)、monitorexit(退出)会把并发型线程改变为序列型线程,就是说拿到锁的会执行线程任务,其他线程只能等待,锁释放以后,其他线程挨个执行任务,性能比较低,本质上是一个悲观锁。锁的的对象,改变的是对象的对象头)
  2. Reentratlock(锁、AQS)
  3. atomic(通过CAS实现,是CPU的指令去完成,一般用不到)
  4. ThreadLocal(通过一个Map保存着各个线程的副本信息,每个共享的变量都在自己独立的工作空间运行)
  5. java还提供了一些线程安全的类。比如ConcurrentHashMap。
  6. volatile(1.保证各个线程的可见性 2.防止指令重排序,不能保证原子性,不能解决线程安全)

2.Synchronized的作用

  1. 原子性
  2. 可见性
  3. 可重入性
  4. 有序性

3.Synchronized的使用

  1. 修饰实例方法,对当前实例对象加锁
  2. 修饰静态方法,对当前的Class对象加锁
  3. 修饰代码块,对synchronized括号内的对象加锁

4.Synchronized底层原理

互斥锁。它是通过监视器jvm帮我们完成。在锁的代码片段加入monitorenter(进入)、monitorexit(退出)指令。在执行monitorenter指令时,首先要尝试获取对象锁。如果这个对象没被锁定或者当前线程已经拥有了那个对象锁,就会把锁的计算器加1。相应的,在执行monitorexit指令时,会将锁的计算器减1,当计算器为0时,锁就被释放了。如果获取对象锁失败,那当前线程就要阻塞,直到对象锁被另一线程释放为止。

5.Lock底层原理(AQS:AbstractQuenedSynchronizer)

  • lock的存储结构:一个int类型状态值(用于锁的状态的变更),一个双向链表(用于存储等待中的线程)
  • lock获取锁的过程:本质上是通过CAS来获取状态值修改,如果当场没获取到,会将该线程放在线程等待链表中。
  • lock释放锁的过程:修改状态值,调整等待链表。

6.ThreadLocal是什么?

ThreadLocal是线程局部变量。线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程之间共享,是java提供ThreadLocal类来支持线程局部变量,是一种实现线程安全的方式。(任何线程局部变量一旦在工作完成后没有释放,java应用就存在内存泄漏的风险。)

7.ThreadLocal底层原理

ThreadLocal类对ThreadLocalMap进行了封装,通过set()、get()、remove()【remove()方法可以处理value值强引用而引起的内存溢出问题(清除ThreadLocalMap中的Entry对象)】方法来实现对ThreadLocalMap操作。ThreadLocalMap中存放的键值对(key为Thread对象,value存放的Object[具体的数据]),这样当前的线程只能获取当前线程在ThreadLocalMap中对应key的value值,实现了多线程之间的数据隔离,从而实现了多线程安全。

8.Atomic底层原理

Atomic包中的类基本的特性就是在多线程环境下,当有多个线程对单个(包括基本类型及引用类型)变量进行操作时,具有排他性。即当多个线程同时对该变量的值进行更新时,仅有一个线程能成功,而未成功的线程可以像自旋锁一样,继续尝试,一直等到执行成功。

9.Synchronized和volatile的区别

  • volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要在主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞。
  • volatile仅能使用在变量级别;synchronized则可以使用在变量、方法和类级别。
  • volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性。
  • volatile不会造成线程的阻塞;synchronized可能会造成线程阻塞。

10.synchronized和lock的区别

  • synchronized是java内置关键字;在jvm层面,lock是个java类。
  • synchronized无法判断获取锁的状态;lock可以判断是否获取到锁。
  • synchrnized会自动释放锁(线程执行完同步代码或执行过程中发生议程都会释放锁);lock需要在finally中手工释放锁(unlock()方法),否则容易造成线程死锁。
  • 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2等待。如果线程1阻塞,线程2则会一直等待下去;lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了。
  • synchronized的锁可重入、不可中断、非公平;lock锁可重入、可判断、可公平。
  • synchronized锁适合代码少量的同步问题;lock锁适合大量同步的代码的同步问题。

11.AQS

AQS(AbstractQueneSynchronizer)抽象的队列式同步器。除了java自带的synchronized关键字之外的锁机制。
AQS基于CLH队列,用volatile修饰共享变量(state),线程通过CAS去改变状态符,成功则获取锁成功,失败则进入等待队列,等待被唤醒。

12.CAS的含义

CAS是compare and swap的缩写,即我们说的比较交换。
CAS是一种基于锁的操作,而且是乐观锁。(在java中分为乐观锁和悲观锁。悲观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问,而乐观锁采取了一种宽泛的态度,通过某种方式不加锁来处理资源,比如通过给记录加version来获取数据,性能较悲观锁有很大的提高。)
CAS包含三个操作数——内存位置(V)、预期原值(A)、新值(B)。如果内存地址里面的值和A的值是一样的,那么就将内存地址里面的值更新成B。
CAS是通过无限循环来获取数据。如果在第一轮循环中,a线程获取地址里面的值被b线程修改了,那么a线程需要自旋,到下次循环才有可能有机会执行。

13.CAS的问题

  1. 容易造成ABA问题。一个线程a将数值改成了b,接着又改成了a,此时CAS认为是没有变化,其实是已经变化过。解决方法是使用版本号标识,每次操作version加1。在java5中,已经提供了AtomicStamapedReference来解决问题。
  2. CAS造成CPU利用率增增加。CAS里面是一个循环判断的过程,如果一直没有获取到状态,CPU资源就会被一直占用。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值