线程和锁自整理面试题

1.线程的实现方式

常见的有3种:
Thread:继承Thread类,start启动线程,重写run方法执行。
Runnable:实现Runnable接口,start启动线程,重写run方法执行。
Callable:实现Callable接口,start启动线程,并实现其call()方法。能返回指定的参数。
接口的start方法都是通过Thread调用。

2.线程的执行流程。

新建:创建了线程。
就绪:执行start(),进入队列,等待JVM的调度。
运行:得到CPU时间片开始执行run方法,可以变为阻塞,就绪,死亡状态。
阻塞:
1)等待阻塞:wait()方法
2)同步阻塞:访问资源被锁synchronized
3)其他阻塞:sleep() join()
死亡:线程执行完任务或其他中止条件。

3.导致阻塞的原因。

Sleep()进入休眠不会放弃锁资源。
Wait()放弃锁资源挂起,线程等到 notify() 或 notifyAll() 线程通讯消息来唤醒线程。
等待某个IO完成,访问资源被锁。

4.中断线程的方法

设置退出标记,一般在run()方法执行完后。
interrupt() 方法中断线程。
stop 方法强行终止线程(会造成数据错乱丢失)。

5.线程安全。

多个线程访问同一资源时会有数据污染和不同步的问题。解决方案:
1)互斥/阻塞同步/悲观锁:
共享数据同一时间只能被1个线程使用。可以通过synchronized 和 java.util.concurrent包中的重入锁(ReentrantLock)来实现同步。都有线程的可重入性,ReentrantLock表现为 API 层面的互斥锁(lock() 和 unlock() 方法配合 try/finally 语句块来完成),synchronized为原生语法层面的互斥锁。不过,相比 synchronized,ReentrantLock 增加了一些高级功能,主要有以下 3 项:等待可中断、可实现公平锁,以及锁可以绑定多个条件。
等待可中断是指当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情,可中断特性对处理执行时间非常长的同步块很有帮助。
公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁;而非公平锁则不保证这一点。synchronized 中的锁是非公平的,ReentrantLock 默认情况下也是非公平的,但可以通过带布尔值的构造函数要求使用公平锁。

2)非阻塞同步/乐观锁:
和悲观锁的全部加锁不同,乐观锁基于冲突检测,当发生冲突后才加锁。
3)无同步方案:
不存在共享数据的时候就不存在线程安全的问题。像service一个线程一次请求这种代码天生就是线程安全的。
可重入代码 :这种代码也叫做纯代码,可以在代码执行的任何时刻中断它,转而去执行另外一段代码(包括递归调用它本身),而在控制权返回后,原来的程序不会出现任何错误。

6 . Synchronized和Lock

Synchronized:

  1. 范围
    修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
    修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
    修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
    2)原理
    monitorenter :每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权。
    如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
    如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

Lock:
原理:基于CAS和volatile 的乐观锁。
CAS:就是比较并更新,从某一内存上取值V,和预期值A进行比较,如果内存值V和预期值A的结果相等,那么我们就把新值B更新到内存,如果不相等,那么就重复上述操作直到成功为止。
volatile :被volatile修饰的变量保证对所有线程可见。通过禁止指令重排序保证了有序性。
可重入独占锁ReentrantLock和读写锁ReadWriteLock是最常用的,其实现都依赖 java.util.concurrent.AbstractQueuedSynchronizer 类。

区别:
synchronized修饰代码块,方法,类,Lock锁lock和unlock中间的代码段。
synchronized由JVM加锁解锁,Lock自己加锁解锁。
synchronized是悲观锁效率低,JDK1.6优化过。Lock乐观锁效率高。

7.java.util.concurrent

java.util.concurrent是专门Java并发设计编程包又叫线程并发库。
显示锁java.util.concurrent.locks
原子变量类 java.util.concurrent.atomic 包中的很多类使用很高效的机器级指令(而不是锁)来保证操作的原子性。
线程池相关 java.util.concurrent
并发容器类 java.util.concurrent 集合队列的线程安全类。
同步工具类 java.util.concurrent 计数器,计算信号量等等。

8.锁的一些概念

可重入锁:
指的是同一线程外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响,执行对象中所有同步方法不用再次获得锁。避免了频繁的持有释放操作,这样既提升了效率,又避免了死锁。
自旋锁:
如果资源被其他线程锁定,本线程自己不断尝试获取锁。但是长时间自旋就变成了“忙式等待”,忙式等待显然还不如阻塞锁。所以自旋的次数一般控制在一个范围内,例如10,100等,在超出这个范围后,自旋锁会升级为阻塞锁。
公平锁:
按等待获取锁的线程的等待时间进行获取,等待时间长的具有优先获取锁权利
读写锁:
对资源读取和写入的时候拆分为2部分处理,读的时候可以多线程一起读,写的时候必须同步地写。
独占锁:
是一种悲观锁,synchronized就是一种独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。
乐观锁:
每次不加锁,假设没有冲突去完成某项操作,如果因为冲突失败就重试,直到成功为止。

9.synchronized的优化JDK1.6

https://blog.csdn.net/sifanchao/article/details/84144141
到了JDK1.6,发生了变化,对synchronize加入了很多优化措施,有自适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在JDK1.6上synchronize的性能并不比Lock差。官方也表示,他们也更支持synchronized,在未来的版本中还有优化余地,所以还是提倡在synchronized能实现需求的情况下,优先考虑使用synchronized来进行同步。

偏向锁(无锁):
大多数情况下锁不仅不存在多线程竞争,而且总是由同一线程多次获得。偏向锁的目的是在某个线程获得锁之后(线程的id会记录在对象的Mark Word中),消除这个线程锁重入(CAS)的开销,看起来让这个线程得到了偏护。
轻量级锁(CAS):
轻量级锁是由偏向锁升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁;轻量级锁的意图是在没有多线程竞争的情况下,通过CAS操作尝试将MarkWord更新为指向LockRecord的指针,减少了使用重量级锁的系统互斥量产生的性能消耗。
重量级锁:
虚拟机使用CAS操作尝试将MarkWord更新为指向LockRecord的指针,如果更新成功表示线程就拥有该对象的锁;如果失败,会检查MarkWord是否指向当前线程的栈帧,如果是,表示当前线程已经拥有这个锁;如果不是,说明这个锁被其他线程抢占,此时膨胀为重量级锁。

10.ThreadLocal

Service层里不建议放成员变量,如果要用的话就用ThreadLocal线程本地变量。

11.线程池

Jdk官方提供了常见四个静态方法来创建常用的四种线程.
CachedThreadPool:可缓存
这种线程池内部没有核心线程,线程的数量是有限制的 最大是Integer最大值。
在创建任务时,若有空闲的线程时则复用空闲的线程,若没有则新建线程。
没有工作的线程(闲置状态)在超过了60S还不做事,就会销毁。
适用:执行很多短期异步的小程序或者负载较轻的服务器。
FixedThreadPool :固定长度
该线程池的最大线程数等于核心线程数,所以在默认情况下,该线程池的线程不会因为闲置状态超时而被销毁。
如果当前线程数小于核心线程数,并且也有闲置线程的时候提交了任务,这时也不会去复用之前的闲置线程,会创建新的线程去执行任务。如果当前执行任务数大于了核心线程数,大于的部分就会进入队列等待。等着有闲置的线程来执行这个任务。
适用:执行长期的任务,性能好很多。
SingleThreadPool:单个
有且仅有一个工作线程执行任务
所有任务按照指定顺序执行,即遵循队列的入队出队规则。
适用:一个任务一个任务执行的场景。
ScheduledThreadPool:可调度
这个线程池有点像是CachedThreadPool和FixedThreadPool 结合了一下。

12 自定义线程池ThreadPoolExecutor

方法参数:
corePoolSize:核心线程数(最小存活的工作线程数量)
maxPoolSize:最大线程数
keepAliveTime:线程存活时间,线程的空闲时间超过了keepAliveTime就会销毁
timeUnit:存活时间的时间单位
workQueue:阻塞队列,用来保存等待被执行的任务(
①synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务;
②LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
③ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小)
threadFactory:线程工厂,主要用来创建线程;
handler:表示当拒绝处理任务时的策略(
①AbortPolicy丢弃任务并抛出RejectedExecutionException异常;
②DiscardPolicy丢弃任务,但是不抛出异常;
③DiscardOldestPolicy丢弃队列最前面的任务,然后重新尝试执行任务;
④CallerRunsPolicy由调用线程处理该任务)

一般用自定义的线程池。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值