1 为何使用并发编程
2 缺点
3 并发编程三要素是什么?在 Java 程序中怎么保证多线程的运行安全?
public class Person{
public static void main(String[] args) throws ClassNotFoundException {
Mydata mydata = new Mydata();
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
mydata.add();
}
},"t"+i).start();
}
while(Thread.activeCount() > 2){
}
System.out.println(mydata.getNum());
}
}
class Mydata {
volatile int num = 0;
public synchronized void add(){
num++;
}
public int getNum() {
return num;
}
}
public class Person{
public static void main(String[] args) throws ClassNotFoundException {
Mydata mydata = new Mydata();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + mydata.getNum());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
mydata.add();
},"AA").start();
while(mydata.getNum() == 0){
}
System.out.println("可见性被触发");
}
}
class Mydata {
volatile int num = 0;
public int getNum() {
return num;
}
public void add(){
num += 10;
}
}
4 并行和并发有什么区别?
5 什么是多线程,多线程的优劣?
多线程:多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务。
多线程的好处:
多线程的劣势:
6 什么是线程和进程?
6 进程和线程关系:
7 上下文切换
多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时会先保存自己的状态,在让给处于就绪状态让给其他线程使用,随后再次被分配到cpu资源加载之前保存信息的这个过程就属于一次上下文切换。
总之:从记录状态,让出cpu资源,到再加载的这个过程中就是上下文切换,这是非常耗费时间和资源的操作
8 守护线程和用户线程有什么区别呢?
比较明显的区别之一是用户线程结束,JVM 退出,不管这个时候有没有守护线程运行。而守护线程不会影响 JVM 的退出。
9 线程生命周期
参考:https://blog.csdn.net/wwwwwww31311/article/details/113367962
10 死锁
两个及以上进程,在执行过程中由于竞争关系或线程通信处于互相阻塞状态,如无外力作用将永远阻塞下去,因为他们都各自掌控着对方所需的资源但却不主动让步,后宫十分严重,这些永远在互相等待的进程(线程)称为死锁进程(线程)。
11 死锁参产生的基本条件
12 如何避免线程死锁
我们只要破坏产生死锁的四个条件中的其中一个就可以了。
13 创建线程有四种方式:
继承 Thread 类:
实现 Runnable 接口:
实现 Callable 接口:
14 说一下 runnable 和 callable 有什么区别?
相同点
不同点
15 线程的 run()和 start()有什么区别?
run()方法被称为线程体,是线程所需完成的任务,实质是线程里的一个函数,而不是多线程的。 如果直接调用run(),其实就相当于是调用了一个普通函数而已,执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用start()方法而不是run()方法。start()方法来启动一个线程,使线程有新建态 => 就绪态,在分配到cpu资源后即可运行,真正实现了多线程运行
16 为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?
这是另一个非常经典的 java 多线程面试问题,而且在面试中会经常被问到。很简单,但是很多人都会答不上来!
new 一个 Thread,线程进入了新建状态。调用 start() 方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。
而直接执行 run() 方法,会把 run 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。
总结: 调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行。
17 什么是 Callable 和 Future?
Callable 接口类似于 Runnable,从名字就可以看出来了,但是 Runnable 不会返回结果,并且无法抛出返回结果的异常,而 Callable 功能更强大一些,被线程执行后,可以返回值,这个返回值可以被 Future 拿到,也就是说,Future 可以拿到异步执行任务的返回值。
Future 接口表示异步任务,是一个可能还没有完成的异步任务的结果。所以说 Callable用于产生结果,Future 用于获取结果。
18 Java 中用到的线程调度算法是什么?
计算机通常只有一个 CPU,每个线程只有获得CPU 的使用权才能执行指令。所谓多线程的并发运行,其实是指从宏观上看,各个线程轮流获得 CPU 的使用权,分别执行各自的任务。在运行池中,会有多个处于就绪状态的线程在等待 CPU,JVM的一项任务就是负责线程的调度,线程调度是指按照特定机制为多个线程分配 CPU 的使用权。
有两种调度模型:分时调度模型和抢占式调度模型。
19 线程的调用策略
线程调度器选择优先级最高的线程运行,但是,如果发生以下情况,就会终止线程的运行
20 什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing )?
线程调度器是一个操作系统服务,它负责为 Runnable 状态的线程分配 CPU 时间。一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。
时间分片是指将可用的 CPU 时间分配给可用的 Runnable 线程的过程。分配 CPU 时间可以基于线程优先级或者线程等待的时间。
线程调度并不受到JVM控制,所以由应用程序来控制它是更好的选择(也就是说不要让你的程序依赖于线程的优先级)。
21请说出与线程同步以及线程调度相关的方法。
(1) wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
(2)sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理 InterruptedException 异常;
(3)notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且与优先级无关;
(4)notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;
23你是如何调用 wait() 方法的?使用 if 块还是循环?为什么?
// The standard idiom for using the wait method
synchronized (obj) {
while (condition does not hold)
obj.wait(); // (Releases lock, and reacquires on wakeup)
... // Perform action appropriate to condition
}
24 为什么线程通信的方法 wait(), notify()和 notifyAll()被定义在 Object 类里?
在Java中,任意对象都可以当作锁来使用,由于锁对象的任意性,所以这些通信方法需要被定义在Object类里。
25 为什么wait(), notify()和notifyAll()必须在同步方法或者同步块中被调用?
其目的在于确保等待线程从wait()返回时能够感知通知线程对共享变量所作出的修改。如果不在同步范围内使用,就会抛出java.lang.IllegalMonitorStateException的异常。
26 Thread 类中的 yield 方法有什么作用?
使得当期线程由运行态 => 就绪态,当前线程主动放弃cpu资源,让给与之同级或更高级的线程或者是它再次抢占到cpu资源
27 线程的 sleep()方法和 yield()方法有什么区别?
28 如何停止一个正在运行的线程?
在java中有以下3种方法可以终止正在运行的线程:
29 什么是阻塞式方法?
阻塞式方法是指程序会一直等待该方法完成期间不做其他事情,ServerSocket 的accept()方法就是一直等待客户端连接。这里的阻塞是指调用结果返回之前,当前线程会被挂起,直到得到结果之后才会返回。此外,还有异步和非阻塞式方法在任务完成前就返回。
30 线程安全的理解
31 如何实现线程安全?
31 ThreadLocal
底层结构:键值的泛型为
用途:为每一个线程管
31synchronized 和Lock区别
32 如何保证List线程安全
33 volidate
34JMM的原则
JMM中保证可见性所做的操作
多线程环境下JVM的内存分为线程共享的主内存(线程间是相互独立的,线程的数据共享与通信都是通过主内存完成)和线程独享的工作内存(虚拟机栈),线程首先要把主内存的数据读取到自己的工作内存,在自己的工作内存中进行数据操作,volidate即保证当一个线程在它的工作内存中对共享变量做了修改后要及时的写回到主内存并立刻被其它线程感知并及时更新直接的工作内存
35线程的三大特性
36 锁的理解
37 CAS
compare and swap:比较当前工作内存中的值和主内存的值,如果一致则进行交换,否则不交换,底层维护了Unsafe类,来通过native方法来调用内存,是调用c++来进行内存操作,本质是计算机的并发源语。juc下的 Atomic 类,都是通过 CAS 来实现的,CAS是操作系统底层并发原语,本身就不会被中断,不会产生线程安全问题。比如getAndIncrement方法就是通过CAS实现的
unsafe中的value属性是validate修饰的,因为unsafe本身直接修改的就是主内存,validate则是使被修改的主内存
内部使用的实际是unsafe的getAndAddInt,三个参数,当前对象,内存值,偏移量,
源码就是通过do-while循环,首先从当前内存中取值,
while的判断条件是将刚才取出的主内存的值同当前线程工作内存的值比较,结构一致就返回,否则继续循环(取值+比较)
直到主工作内存的值和当前线程工作内存值相同,最终返回运算值
缺点:
特点:
相比于重量级的synchronized,CAS的机制决定它不仅具有安全,还具有并发性,安全且高效
//在主工作内存创建,初始值为5
AtomicInteger atomicInteger = new AtomicInteger(5);
//比较当前操作数(线程工作区)的值和主内存中的值是否相等,相等的话才在线程工作内存中做修改并同步回主内存
atomicInteger.compareAndSet(5,2019);//布尔类型返回值,判断是否能够交换
atomicInteger.compareAndSet(5,2020);
ABA问题
是CAS的附带问题,多线程环境下,线程a在读取主内存值做修改再同步回主内存的这段时间内,其它线程其实已经对主内存做了n次修改,只是最终主内存的结果和线程a的期望值是一致的,所以线程a也的值也成功同步到了主内存,这个问题是由CAS本身机制所引起的,解决方案是使用版本号来判断它中间是否出现修改
38 线程池
特点:统一对线程进行创建和管理,使用时不是新建而是从连接池拿出连接,使用完不是直接close而是再还到线程池
优点:①没必要频繁的创建和删除连接线程,避免资源浪费,也可以提高响应速度②线程是稀缺,可以方便对线程进行管理
public static void main(String[] args) {
// 不推荐使用这种,因为ExecutorService默认构造方法中最大线程数是2的31次方,如果猛然间业务量剧增那就会给服务器端带了巨大的压力。
// ExecutorService threadPool = Executors.newFixedThreadPool(5);
// Executors.newCachedThreadPool();
// Executors.newSingleThreadExecutor();
// 推荐使用自定义线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
2, //默认线程池正常情况大小是2
5,// 最大允许的线程池为5,通常高并发情况下工作线程数会超出正常线程达到最大线程数
3,// 一段时间(3分钟)空闲时间会回收非核心线程外的线程
TimeUnit.MINUTES,
new LinkedBlockingDeque<>(3),//阻塞队列,最大线程还不够处理当前任务,就把任务放到请求队列中去
Executors.defaultThreadFactory(), //线程工厂对象
new ThreadPoolExecutor.AbortPolicy() //默认的拒绝策略
);
for (int i = 0; i < 15; i++) {
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "当前线程运行");
}
});
}
}
ExecutorService内部调用的是ThreadPoolExecutor(…),重点是7大参数,四种拒绝策略
阻塞队列的作用
线程池复用的原理
四大拒绝策略
AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
DiscardPolicy:如果线程队列已满,后续提交的任务都会被丢弃,且是静默丢弃,但是不抛出异常
DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。
CallerRunsPolicy:由调用线程处理该任务
39 关于wait的三个问题
(1) 为何wait要定义在Object中
因为线程中的锁是对象级别的,而不是线程级别的,我们希望通过对象锁来实现线程安全方法/代码块中的多线程互斥效果
(2) 为何wait要在同步方法中
不再里面会报监视异常,假设它不需要在synchronized中,正常情况下都是要循环判断执行完wait再调用notify,但多线程环境下存在cpu的抢夺问题,所以就容易出现先notify,在wait的情况,而通过synchronized就能保证先wait在notify
(3)为何wait需要循环判断等待
wait() 方法应该在循环调用,因为进程在刚获取CPU资源执行时时可能其它条件还未准备就绪,所以我们通过循环判断xxx条件是否满足,没有满足就会一直调用wait
40 threadLocal