35.并行和并发有什么区别?
并发
的关键是你有处理多个任务的能力,但不是同时
。并行
的关键是你在同时
处理多个任务。
36.线程和进程的区别?
1.线程(Thread)与进程(Process)
进程定义的是应用程序与应用程序之间的边界,一个进程就代表一个应用程序。不同的进程之间不能共享代码和数据空间,而同一进程的不同线程可以共享代码和数据空间。
2.一个进程可以包括若干个线程,同时创建多个线程来完成某项任务,便是多线程。
实现线程的两种方式:继承Thread类,实现Runable接口
37.守护线程是什么?
Java线程分为用户线程和守护线程。
- 守护线程是程序运行的时候在后台提供一种通用服务的线程。所有用户线程停止,进程会停掉所有守护线程,退出程序。
- Java中把线程设置为守护线程的方法:在 start 线程之前调用线程的 setDaemon(true) 方法。
注意:
- setDaemon(true) 必须在 start() 之前设置,否则会抛出IllegalThreadStateException异常,该线程仍默认为用户线程,继续执行
- 守护线程创建的线程也是守护线程
- 守护线程不应该访问、写入持久化资源,如文件、数据库,因为它会在任何时间被停止,导致资源未释放、数据写入中断等问题
38.创建线程有哪几种方式?
第一种方式:继承Thread类
第二种方式:实现Runnable接口
第三种方式:使用内部类的方式
第四种方式:定时器
第五种方式:带返回值的线程实现方式
第六种方式:基于线程池的方式
Spring方式:使用Spring来实现多线程
39.说一下 runnable 和 callable 有什么区别?
相同点:
都是接口
都可以编写多线程程序
都采用Thread.start()启动线程
主要区别:
Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果
Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出异常,可以获取异常信息
注:Callalbe接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。
40.线程有哪些状态?
线程状态有 5 种,新建,就绪,运行,阻塞,死亡
1. 线程 start 方法执行后,并不表示该线程运行了,而是进入就绪状态,意思是随时准备运行,但是真正何时运行,是由操作系统决定的,代码并不能控制,
2. 同样的,从运行状态的线程,也可能由于失去了 CPU 资源,回到就绪状态,也是由操作系统决定的。这一步中,也可以由程序主动失去 CPU 资源,只需调用 yield 方法。
3. 线程运行完毕,或者运行了一半异常了,或者主动调用线程的 stop 方法,那么就进入死亡。死亡的线程不可逆转。
4. 下面几个行为,会引起线程阻塞。
- 主动调用 sleep 方法。时间到了会进入就绪状态
- 主动调用 suspend 方法。主动调用 resume 方法,会进入就绪状态
- 调用了阻塞式 IO 方法。调用完成后,会进入就绪状态。
- 试图获取锁。成功的获取锁之后,会进入就绪状态。
- 线程在等待某个通知。其它线程发出通知后,会进入就绪状态
41.sleep() 和 wait() 有什么区别?
1、sleep()方法是线程类Thread的静态方法,让调用线程进入睡眠状态,让出执行机会给其它线程,等到休眠时间结束后,线程进入就绪状态和其它线程一起竞争CPU执行时间。
因为sleep()是静态的(static修饰的)方法,它不能改变对象的机锁,当一个synchronized块中调用了sleep方法,线程虽然进入休眠状态,但是对象的机锁没有释放,其它线程无法访问这个对象。
因为一个类中的静态资源在类class被java虚拟机加载时就已经被初始化了,而非静态资源要在对象实例化的时候才会被初始化。sleep()方法是静态的,只依赖于类,不依赖于对象,而锁是对象锁,所以不能改变锁。
2、wait()是Object类的方法,当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池,同时释放对象的锁,使用其它线程能够访问,可以通过notify,和notifyAll方法来唤醒等待的线程。
wait()、notify()、notifyAll()只能在同步控制方法或者同步控制块里面使用,而sleep()可以在任何地方使用。
sleep()必须捕获异常,而wait()、notify()、notifyAll()不需要捕获异常。
42.notify()和 notifyAll()有什么区别?
- 如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
- 当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争
- 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
综上,所谓唤醒线程,另一种解释可以说是将线程由等待池移动到锁池,notifyAll调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。而notify只会唤醒一个线程。
有了这些理论基础,后面的notify可能会导致死锁,而notifyAll则不会的例子也就好解释了
43.线程的 run()和 start()有什么区别?
run()方法:
是在主线程中执行方法,和调用普通方法一样;(按顺序执行,同步执行)
start()方法:
是创建了新的线程,在新的线程中执行;(异步执行)
44.创建线程池有哪几种方式?
newCachedThreadPool 创建一个可缓存的线程池,如果线程池长度超过处理需求,可灵活回收空闲线程,若无可回收,则新建线程
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行
newSingleThreadExecutor 创建一个单线程化的线程池,它只会唯一的工作线程来执行任务,保证所有任务按照指定
顺序(FIFO,LIFO,优先级)执行
45.线程池都有哪些状态?
1.Running
状态说明:线程池处于Running状态时可以接受新的任务,以及对已添加的任务进行处理
状态切换:线程池的初始化状态是Running,换句话说,线程池一旦被创建,就处于Running状态,并且线程池中的任务数为0
2.SHUTDOWN
状态说明:此时线程池处于SHUtDOWN状态时,不在接受新任务,但能处理已添加的任务
状态切换:调用线程池SHUTDOWN()接口时,线程池由RUNNING->SHUTDOWN
3. STOP
状态说明:线程池处STOP的状态下,不再接受任务,吧并且会中断正在处理的任务
状态切换:调用线程池SHUTDOWNNow()接口时,线程池由Running或者SHUTDOWN->STOP
4 .TIDYING
状态说明:当所有的任务停止时,CTL记录的总数“任务量”为0,线程池就会变成TIDYING状态。当线程池变为TIDYING状态时,会执行子函数terminated(),terminated()在ThreadPoolExcutor类中为空,若是想在线程池变为TIDYING时,进行相应的处理,可以通过重载terminated()来实现
状态切换:当线程池哎SHUTDOWN状态下,阻塞队列为空并且线程池执行的任务也为空,就会由SHOWDOWN-》TIDYING当现在线程池为STOP状态下,线程池的执行为空时就会由STOP->TIDYING
4.TERMINATED
状态说明:线程池彻底终止,就变成了TERMINATED
状态切换:线程池处于TIDYING状态时,执行完terminated()以后就变成了TIDYING->TERMINATED
46.线程池中 submit()和 execute()方法有什么区别?
一:
submit()方法,可以提供Future < T > 类型的返回值。
executor()方法,无返回值。
execute无返回值
二:
excute方法会抛出异常。
sumbit方法不会抛出异常。除非你调用Future.get()。
三:
excute入参Runnable
submit入参可以为Callable,也可以为Runnable。
47.在 java 程序中怎么保证多线程的运行安全?
-
使用synchronied关键字,可以用于代码块,方法(静态方法,同步锁是当前字节码对象;实例方法,同步锁是实例对象)
-
使用volatile 关键字,防止指令重排,被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量
-
lock锁机制
-
使用线程安全的类,比如Vector、HashTable、StringBuffer
48.多线程锁的升级原理是什么?
JVM优化synchronized的运行机制,当JVM检测到不同的竞争状态时,就会根据需要自动切换到合适的锁,这种切换就是锁的升级。升级是不可逆的,也就是说只能从低到高,也就是偏向-->轻量级-->重量级,不能够降级
锁级别:无锁->偏向锁->轻量级锁->重量级锁
49.什么是死锁?
- 死锁:是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种相互等待的现象,若无外力作用,他们将无法推进下去;
50.怎么防止死锁?
产生死锁的必要条件:
1、互斥使用(资源独占)
一个资源每次只能给一个进程使用(比如写操作)
2、占有且等待:
进程在申请新的资源的同时,保持对原有资源的占有
3、不可抢占:
资源申请者不能强行从资源占有者手动夺取资源,资源只能由占有者自愿释放
4、循环等待:
A等待B占有的资源,B等待C占有的资源,C等待D占有的资源,..........N等待A的资源,形成一个线程等待回路
51.ThreadLocal 是什么?有哪些使用场景?
Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。
在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。
而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。
ThreadLocal在每个本地线程中创建了一个ThreadLocalMap对象,每个线程可以访问自己内部ThreadLocalMap对象里的value。通过这种方式,实现线程之间的数据隔离。
为每个线程分配一个JDBC连接的Connection。这样可以保证每个线程都在各自的Connection上进行数据库的操作,不会出现A线程关闭了线程B正在使用的Connection。
52.说一下 synchronized 底层实现原理?
53.synchronized 和 volatile 的区别是什么?
1、Java语言为了解决并发编程中存在的原子性、可见性和有序性问题,提供了一系列和并发处理相关的关键字,比如synchronized、volatile、final、concurren包等。
2、synchronized通过加锁的方式,使得其在需要原子性、可见性和有序性这三种特性的时候都可以作为其中一种解决方案,看起来是“万能”的。的确,大部分并发控制操作都能使用synchronized来完成。
3、volatile通过在volatile变量的操作前后插入内存屏障的方式,保证了变量在并发场景下的可见性和有序性。
4、volatile关键字是无法保证原子性的,而synchronized通过monitorenter和monitorexit两个指令,可以保证被synchronized修饰的代码在同一时间只能被一个线程访问,即可保证不会出现CPU时间片在多个线程间切换,即可保证原子性。
那么,我们知道,synchronized和volatile两个关键字是Java并发编程中经常用到的两个关键字,而且,通过前面的回顾,我们知道synchronized可以保证并发编程中不会出现原子性、可见性和有序性问题,而volatile只能保证可见性和有序性。
54.synchronized 和 Lock 有什么区别?
synchronized和lock的区别:
类别 | synchronized | lock |
存在层次 | java的关键字,在jvm层面上 | 是一个类 |
锁的释放 | 1、以获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁 | 在finally中必须释放锁,不然容易造成线程死锁 |
锁的获取 | 假设A线程获得锁,B线程等待, 如果A线程阻塞,B线程会一直等待 | 分情况而定,lock有多个锁获取的方法,可以尝试获得锁, 线程可以不用功一直等待 |
锁状态 | 无法判断 | 可以判断 |
锁类型 | 可以重入,不可以中断,非公平 | 可重入 可以判断 可公平 |
性能 | 少量同步 | 大量同步 |
55.synchronized 和 ReentrantLock 区别是什么?
- synchronized 竞争锁时会一直等待;ReentrantLock 可以尝试获取锁,并得到获取结果
- synchronized 获取锁无法设置超时;ReentrantLock 可以设置获取锁的超时时间
- synchronized 无法实现公平锁;ReentrantLock 可以满足公平锁,即先等待先获取到锁
- synchronized 控制等待和唤醒需要结合加锁对象的 wait() 和 notify()、notifyAll();ReentrantLock 控制等待和唤醒需要结合 Condition 的 await() 和 signal()、signalAll() 方法
- synchronized 是 JVM 层面实现的;ReentrantLock 是 JDK 代码层面实现
- synchronized 在加锁代码块执行完或者出现异常,自动释放锁;ReentrantLock 不会自动释放锁,需要在 finally{} 代码块显示释放
补充一个相同点:都可以做到同一线程,同一把锁,可重入代码块。
56.说一下 atomic 的原理?
大概实现原理是:通过CAS乐观锁保证原子性,通过自旋保证当次修改的最终修改成功,通过降低锁粒度(多段锁)增加并发性能。