- 1,讲一下线程的几种实现方式?启动方式?区分方式?
- 2,并行和并发有什么区别?
- 3,线程和进程的区别?
- 4,守护线程是什么?
- 5,创建线程有哪几种方式
- 6,说一下runnable和callable有什么区别?
- 7,线程有哪些状态?
- 8,sleep()和wait()有什么区别?
- 9,notify()和notifyAll()有什么区别?
- 10,线程的run()和start()有什么区别?
- 11,创建线程池有哪几种方式?
- 12,线程池都有哪些状态?
- 13,线程池中submit()和execute()方法有什么区别?
- 14,在java程序中怎么保证多线程的运行安全?
- 15,多线程中synchronized锁实现原理以及升级的原理是什么?
- 16,什么是死锁?
- 17,如何防止死锁?
- 18,ThreadLocal 是什么?有哪些使用场景?
- 19,说一下synchronize底层实现原理?
- 20,synchronize和volatile的区别是什么?
- 21,synchronize和lock有什么区别?
- 22,synchronize和reentrantlock(可重入锁)区别是什么?
- 23,synchronize加上方法上锁对象是什么,加上静态方法上锁对象是什么。。以及synchronize(this)和synchronize(user.class)的区别
- 24,悲观锁和乐观锁区别
- 25,说一下atomic(原子类)的原理?
- 26,int和integer以及atomicinteger的区别?
- 27,java线程池七个参数详解
- 28,常用的线程池有几种,分别是什么意思?
- 29,线程池的原理执行流程
- 30、为什么要使用连接池,数据库连接池的原理
- 31、在ThreadPoolExecutor类中几个重要的方法
1,讲一下线程的几种实现方式?启动方式?区分方式?
实现方式
通过继承Thread类实现一个线程
通过实现Runnable接口实现一个线程
通过callable和FutureTask创建线程
通过线程池创建
怎么启动?
怎么区分?
在一个系统中有很多线程,每个线程都会打印日志,我想区分是哪个线程打印的怎么办?thread,setname(“设置一个线程名称”);这是一种规范,在创建线程完成后,都需要设置名称
2,并行和并发有什么区别?
并行:多个处理器或多核处理器同时处理多个任务。
并发:多个任务在同一CPU
核上:按细分的时间片轮流(交替)执行,从逻辑上来看这些任务是同时执行
3,线程和进程的区别?
一个程序下至少有一个进程,一个进程下至少有一个线程,一个进程下也可以有多个线程来增加程序的执行速度。
java最少有两个线程,jvm会创建一个主线程main,垃圾回收站会创建一个线程(垃圾回收站的几种算法:标记清楚法(存储的对象进行标记),标记整理,分代,复制算法(分内存))
4,守护线程是什么?
守护线程是运行在后台的一种特殊进程。它独立于控制端并且周期性地执行某种任务或等待处理某些发送的事件。在java中垃圾回收线程就是特殊的守护线程。
5,创建线程有哪几种方式
创建线程有三种方式 继承thread重写run方法; 实现runnable接口;实现callable接口
6,说一下runnable和callable有什么区别?
runnable没有返回值,callable可以拿到有返回值,callable可以看作是runnable的补充
7,线程有哪些状态?
new :尚未启动
runnable:正在执行中
blocked:阻塞的
waiting:永远等待状态
timed_wating:等待指定的时间重新被唤醒的状态
terminated:执行完成
8,sleep()和wait()有什么区别?
类的不同:sleep()来自Thread,wait()来自object。
sleep()不释放锁 wait()释放锁
用法不同:sleep()时间到会自动回复;wait()可以使用notify()/notifyAll()函数比较多
sleep(1000):表示占用CPU,线程休眠1000毫秒 实际项目中使用sleep函数较多
wait(1000):表示不占用CPU,线程等待1000毫秒
9,notify()和notifyAll()有什么区别?
notifyAll()会唤醒所有线程。notify()之后唤醒一个线程。
notifyAll()调用后,会将全部线程有等待池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁释放后再次参与竞争。而notify()只会唤醒一个线程,具体唤醒哪一个线程由虚拟机控制。
10,线程的run()和start()有什么区别?
start()方法用于启动线程,run()方法用于执行线程的运行时代码。run()可以重复调用,而start()只能调用一次。
11,创建线程池有哪几种方式?
线程池创建有七中方式,最核心的是最后一种jdk1.5之后推出的
l ThreadPoolExecutor :线程池
l Executor: 线程池顶层接口,提供了execute执行线程任务的方法
l Execuors: 线程池的工具类,通常使用它来创建线程池
后很多 …….
12,线程池都有哪些状态?
running:这是最正常的状态,接受新的任务,处理等待队列中的任务。
shutdown:不接受新的任务提交,但是会继续处理等待队列中的任务。
stop:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。
tidying:所以的任务对销毁了,workCount为0,线程池的状态正在转换为tidying状态时,会执行钩子方法teminated().
13,线程池中submit()和execute()方法有什么区别?
execute():只能执行Runnable类型的任务。
submit():可以执行Runnable和Callable类型的任务。
Callable:类型的任务可以获取执行的放回值,而Runnable执行无返回值。
14,在java程序中怎么保证多线程的运行安全?
方法一:使用安全类,比如 java.util.concurrent下的类。
方法二:使用自动锁sychronized。
方法三:使用手动锁 Lock
15,多线程中synchronized锁实现原理以及升级的原理是什么?
synchronized 锁实现原理
简单理解:Synchronized基于JVM内置锁实现,通过内部对象Monitor(监视器锁,0释放锁、1该线程占有该锁)实现,通过进入与退出对象的Monitor来实现方法与代码块同步。对象的JDK1.5之后,Synchronized会从无锁升级为偏向锁,再升级为轻量级锁,最后升级为重量级锁。后续还会说到Synchronized的优化。
synchronized 锁升级的原理:
synchronized 锁升级原理:在锁对象的对象头里面有一个 threadid 字段,在第一次访问的时候 threadid 为空,jvm 让其持有偏向锁,并将 threadid 设置为其线程 id,再次进入的时候会先判断 threadid 是否与其线程 id 一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级。
锁的升级的目的:锁升级是为了减低了锁带来的性能消耗。在 Java 6 之后优化 synchronized 的实现方式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,从而减低了锁带来的性能消耗。
16,什么是死锁?
当线程A持有独占锁a,并尝试去获取独占锁b的同时,线程B持有独占锁b,并尝试获取独占锁a的情况下,就会发生AB 两个线程由于相互持有对方需要的锁,而发生的阻塞现象,我们称为死锁。
17,如何防止死锁?
尽量使用 tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、ReentrantReadWriteLock),设置超时时间,超时可以退出防止死锁。
尽量使用 Java. util. concurrent 并发类代替自己手写锁。
尽量降低锁的使用粒度,尽量不要几个功能用同一把锁。
尽量减少同步的代码块。
18,ThreadLocal 是什么?有哪些使用场景?
ThreadLocal 是线程本地存储,在每个线程中都创建了一个 ThreadLocalMap 对象,每个线程可以访问自己内部 ThreadLocalMap 对象内的 value。
19,说一下synchronize底层实现原理?
synchronize是由一对 monitorenter/monitorexit 指令实现的,monitor 对象是同步的基本实现单元。在 Java 6 之前,monitor 的实现完全是依靠操作系统内部的互斥锁,因为需要进行用户态到内核态的切换,所以同步操作是一个无差别的重量级操作,性能也很低。但是java6的时候,java虚拟机对此进行改进,提供三种不同的monitor实现,也就是常说的三种不同的锁,偏向锁,轻量级锁和重量级锁,大大改进了其性能。
20,synchronize和volatile的区别是什么?
volatile是变量修饰符;synchronize是修饰类,方法,代码段。
volatile仅能实现变量的修改可见性,不能保证原子性,而synchronize则可以保证变量的修饰可见性和原子性。
volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
21,synchronize和lock有什么区别?
synchronize可以给类,方法,代码块加锁,而lock只能给代码加锁。
synchronize不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而Lock需要自己加锁和释放锁,如果使用不懂没有nulock去释放锁就会造成死锁。
通过Lock可以知道有没有成功获取锁,而synchronize却无法办到。
22,synchronize和reentrantlock(可重入锁)区别是什么?
synchronize早期的实现比较低效,对比reentrantLocak,大多数场景性能都相差较大,但是在java6中synchronize进行了非常多的改进。
主要区别如下:
ReetrantLock:使用起来比较灵活,但是必须有释放锁的配合动作;
ReetrantLock:必须手动获取与释放锁,而synchronize不要手动释放和开启锁;
ReetrantLock:只适用于代码块锁,而synchronize可用于修饰方法,代码块等;
23,synchronize加上方法上锁对象是什么,加上静态方法上锁对象是什么。。以及synchronize(this)和synchronize(user.class)的区别
synchronize修饰不加static的方法,锁是加单个对象上,不同的对象没有竞争关系;修饰加了static的方法,锁是加载类上,这个类所有的对象竞争一把锁;
同步synchronize(.class)代码块的作用其实如synchronized static方法作用一样.class锁对类的所有对象实例起作用
24,悲观锁和乐观锁区别
悲观锁(pessimistic locak)
每次获取数据的时候,都会担心数据被修改,所以每次获取数据的时候都会进行加锁,确保在自己使用的过程中数据不会被别修改,使用完成后进行数据解锁。由于数据进行加锁期间对该数据进行读写的其他线程都会进行等待。
乐观锁 :不是锁 只是进行判断数据是否被修改
每次获取数据的时候,都不会担心数据被修改,所以每次获取数据的时候都不进行加锁,但是在更新数据的时候需要判断该数据是否被别人修改过,如果数据被其他线程修改,则不进行数据的更新,如果数据没有被其他线程修改,则进行数据更新。由于数据没有进行加锁,期间该数据可以被其他线程进行读写操作。
使用场景:
悲观锁:比较适合操作比较频繁的场景,如果出现大量的读取操作,每次读取的时候都会进行加锁,这样会增加大量的锁开销,降低了系统的吞吐量。
乐观锁:比较适合读取比较频繁的场景,如果出现大量的写入操作,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量的查询操作,降低了系统的吞吐量。
25,说一下atomic(原子类)的原理?
atomic主要利用CAS和volatile方法来保证原子操作,从而避免synchronize的高开销,执行效率大为提升。
26,int和integer以及atomicinteger的区别?
int是基本数据类型,Integer是引用数据类型,int和Integer之间可以自动拆装箱(虽然可以自动拆装箱,但写代码的时候也尽量避免不必要的拆装箱)。int默认值是0,Integer是null,这点可以根据需要自行定义使用。
Integer包装类是不可变的,使用泛型的时候填入的只能是包装类,不能是基本数据类型int.Integer的valueOf方法默认缓存值是-128到127之间。
AtomicInteger:涉及到计算时的线程安全问题就使用AtomicInteger。Atomin…是一套线程安全的类。
AtomicInteger这个类的存在是为了满足在高并发的情况下,原生的整形数值自增线程不安全的问题
27,java线程池七个参数详解
28,常用的线程池有几种,分别是什么意思?
jdk官方提供了常见四个静态方法来创建常用的四个线程,可以用过excutors创建
①、CachedThreadPool:可缓存可以无限制创建:
根据源码可以看出:
这种线程池内部没有核心线程,线程的数量是有限制的 最大是Integer最大值。
在创建任务时,若有空闲的线程时则复用空闲的线程(缓存线程),若没有则新建线程。
没有工作的线程(闲置状态)在超过了60S还不做事,就会销毁。
适用:执行很多短期异步的小程序或者负载较轻的服务器
②、FixedThreadPool :固定长度 定长线程池
根据源码可以看出:
该线程池的最大线程数等于核心线程数,所以在默认情况下,该线程池的线程不会因为闲置状态超时而被销毁。
如果当前线程数小于核心线程数,并且也有闲置线程的时候提交了任务,这时也不会去复用之前的闲置线程,会创建新的线程去执行任务(必须达到最大核心数才会复用线程)。 如果当前执行任务数大于了核心线程数,大于的部分就会进入队列等待。等着有闲置的线程来执行这个任务。
适用:执行长期的任务,性能好很多。
③、SingleThreadPool:单个
根据源码可以看出:
有且仅有一个工作线程执行任务
所有任务按照指定顺序执行,即遵循队列的入队出队规则。
适用:一个任务一个任务执行的场景。 如同队列
④、ScheduledThreadPool:可调度
根据源码可以看出:
DEFAULT_KEEPALIVE_MILLIS就是默认10L,这里就是10秒。这个线程池有点像是CachedThreadPool和FixedThreadPool结合了一下。
不仅设置了核心线程数,最大线程数也是Integer.MAX_VALUE。
这个线程池是上述4个中唯一一个有延迟执行和周期执行任务的线程池。
适用:周期性执行任务的场景(定期的同步数据)
Excutors这个工具类中,util是工具类,以后见到以s结尾也是工具类.Collections Arrays Paths等
29,线程池的原理执行流程
线程池执行流程 : 核心线程 => 等待队列 => 非核心线程 => 拒绝策略
第一、如果有空闲的核心线程直接使用,没有空闲的核心线程并且线程数量未达到corePoolSize(线程池核心线程大小),则用一个核心线程执行任务
第二、核心线程数量达到了corePoolSize,则将任务移入队,列等待空闲核心线程将其取出去执行(通过getTask方法从阻塞队列中获取等待的任务,如果队列中没有任务,getTask方法会被阻塞并挂起,不会占用cpu资源,整个getTask操作在自旋下完成),如果队列已满,新建普通线程(非核心线程)执行任务,空闲下来以后,非核心线程会按照时间策略进行销毁
第三、队列已满,核心线程、普通线程、队列总线程数又达到了maximumPoolSize(线程池最大线程数量) 最大线程数,就会执行任务拒绝策略。
30、为什么要使用连接池,数据库连接池的原理
1、为什么用线程池?
顾名思义, 就是装与数据库连接对象的容器,可以极大的提升咱们操作数据库的性能,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;
线程池可以看做是管理了N个线程的池子,和连接池类似。线程池的作用主要有:
l 控制并发数量:线程并发数量过多,抢占系统资源从而导致阻塞,线程池可以限制线程的数量。
l 线程的复用:创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处理效率和速度
l 管理线程的生命周期:对线程进行一些简单的管理,创建,销毁等
2、连接池的工作原理主要由三部分组成,分别为
连接池的建立、连接池中连接的使用管理、连接池的关闭
①、第一、连接池的建立。
一般在系统初始化时,连接池会根据系统配置建立,并在池中创建了几个连接对象,以便使用时能从连接池中获取。连接池中的连接不能随意创建和关闭,这样避免了连接随意建立和关闭造成的系统开销。
Java中提供了很多容器类可以方便的构建连接池,例如Vector、Stack等。
②、第二、连接池的管理。
连接池管理策略是连接池机制的核心,连接池内连接的分配和释放对系统的性能有很大的影响。其管理策略是:
当客户请求数据库连接时,
1)如果池中有空闲连接可用,返回该连接。
2)如果没有空闲连接,池中连接都已用完,创建一个新连接添加到池中。
3)如果池中连接已达到最大连接数,请求按设定的最大等待时间进入等待队列直到有空闲连接可用。
4)如果超出最大等待时间,则抛出异常给客户。
当客户释放数据库连接时,先判断该连接的引用次数是否超过了规定值,如果超过就从连接池中删除该连接,否则保留为其他客户服务。
该策略保证了数据库连接的有效复用,避免频繁的建立、释放连接所带来的系统资源开销。
如果连接长时间空闲,或检测到与服务器的连接已断开,连接池管理器也会将该连接从池中移除。
③、第三、连接池的关闭。
当应用程序退出时,关闭连接池中所有的连接,释放连接池相关的资源,该过程正好与创建相反。
**
1、并发问题
为了使连接管理服务具有最大的通用性,必须考虑多线程环境,即并发问题。synchronized(java) lock(C#)关键字即可确保线程是同步的。
2、事务处理,保证事务具有原子性
我们知道,,此时要求对数据库的操作符合“ALL-OR-NOTHING”原则,即对于一组SQL语句要么全做,要么全不做。
我们知道当2个线程共用一个连接Connection对象,而且各自都有自己的事务要处理时候,对于连接池是一个很头疼的问题,因为即使Connection类提供了相应的事务支持,可是我们仍然不能确定那个数据库操作是对应那个事务的,这是由于我们有2个线程都在进行事务操作而引起的。
为此我们可以使用每一个事务独占一个连接来实现,虽然这种方法有点浪费连接池资源但是可以大大降低事务管理的复杂性。
3、连接池的分配与释放
连接池的分配与释放,对系统的性能有很大的影响。合理的分配与释放,可以提高连接的复用度,从而降低建立新连接的开销,同时还可以加快用户的访问速度。
对于连接的管理可使用一个List。即把已经创建的连接都放入List中去统一管理。每当用户请求一个连接时,系统检查这个List中有没有可以分配的连接。如果有就把那个最合适的连接分配给他,如果没有就抛出一个异常给用户。
4、连接池的配置与维护
连接池中到底应该放置多少连接,才能使系统的性能最佳?
系统可采取设置最小连接数(minConnection)和最大连接数(maxConnection)等参数来控制连接池中的连接。
31、在ThreadPoolExecutor类中几个重要的方法
l Execute :方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。
l Submit :方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,实际上它还是调用的execute()方法,只不过它利用了Future来获取任务执行结果。
l Shutdown :不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务。
l shutdownNow :立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务。
l isTerminated:调用ExecutorService.shutdown方法的时候,线程池不再接收任何新任务,但此时线程池并不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。在调用shutdown方法后我们可以在一个死循环里面用isTerminated方法判断是否线程池中的所有线程已经执行完毕,如果子线程都结束了,我们就可以做关闭流等后续操作了。