1.线程和进程的关系?
一个进程可以有多个线程,但必须有一个线程,一个线程只能在一个进程的范围内活动。
2.并发和并行的区别?
并发是指有公共资源的争夺,并行是没有公共资源的争夺。
3.实现多线程的方式?
1>.继承Thred类,重写run接口。
2>.实现Runnable接口,实现run方法。
3>.通过Callable接口,实现call方法。
4.什么是守护线程?
专门用于服务其他的线程,如果其他的线程(即用户自定义线程)都执行完毕,连main线程也执行完毕,那么jvm就会退出(即停止运行)——此时,连jvm都停止运行了,守护线程当然也就停止执行了
5. synchoried原理?
这个原本是个重量级锁,但是在后来更新中进行了优化,引入了偏向锁和轻量级锁。通过锁膨胀一步步升级为重量级锁。
以前的文章:https://blog.csdn.net/huaixiaohai_1/article/details/92607179
还是说原理吧:JVM基于进入和退出Monitor对象来实现同步和代码块同步,同步代码块是通过monitorenter和monitorexit实现的。同步方法是通过另一种方式实现的(JVM规范中并没有说明)。但是还是可以使用这种方法,monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit插入在结束或者异常的位置。必须对应。
6.volicate原理?
volicate主要是通过Lock前缀指令来实现的。
lock前缀指令会引起处理器缓存写回到内存中。当一个处理器的缓存写回到内存中会导致其他处理器的缓存无效。
还有一个作用是他会禁止指令重排序。
7.禁止指令重排序是什么原理?
通过内存屏障
8.原子类实现原理?
以AutomicInteger为例进行源码分析:
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
上面这段代码的意思是:JVM可以实现Java对象的布局,也就是在内存里Java对象的各个部分放在哪里,包括对象的实例字段和一些元数据之类。jdk内部使用的工具类sun.misc.Unsafe提供的方法objectFieldOffset()用于获取某个字段相对Java对象的“起始地址”的偏移量,可以使用这个偏移量调用getInt、getLong、getObject等方法来获取某个Java对象的某个字段。
还可以看到value通过volatile保证可见性:
//两个构造方法
public AtomicInteger(int initialValue) {
value = initialValue;
}
public AtomicInteger() {
}
//get和set方法
public final int get() {
return value;
}
public final void set(int newValue) {
value = newValue;
}
看一下关键方法:更新操作
public final int getAndUpdate(IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return prev;
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
他首先备份了一份原来的值,然后通过cas的方式一直while直到成功。
9. 信号量机制?
信号量机制是为了保护共享变量,使得一个线程只有一个时刻只有一个进程访问资源。
主要通过P(申请资源)V(释放资源)操作来实现信号量,对信号量的操作都是原子性的。
信号量S:用来记录资源数量,看是否能满足申请需要。
P(S) 若 S > 0:则S-1;线程继续执行,若S<0则挂起线程。
V(S) 若S>0;则S+1;若S<0,则唤醒一个线程。
10.AQS是什么,讲讲?
抽象的队列同步器,是除了java自带的synchronized关键字之外的锁机制。
如果请求的资源空闲,那么将当前请求资源设置为有效的工作线程,并将共享资源设置为锁定状态。如果请求的资源锁定,那么就进入CLH阻塞队列。
CLH阻塞队列:是一个虚拟双向队列,不存在实际的队列,仅记录节点之间的关联关系。
实际上AQS就是基于CLH队列,用volicate修饰共享变量state,线符程通过CAS去修改状态符。成功则获取锁成功,失败则进入等待队列。
看这个图就清除多了。
11.为什么要用线程池?
当并发数量多的时候,频繁的创建和销毁线程费时间,所以采用线程池,让每个线程可以多次使用,减少消耗。
12.线程池的种类以及自定义线程池?
java提供了构建线程池,使线程池使用变得简单起来了。
public ThreadPoolExecutor(
//初始线程数
int corePoolSize,
//最大线程个数
int maximumPoolSize,
//生存时间,超过初始线程个数以后的线程池使用后的销毁时间
long keepAliveTime,
//时间单位
TimeUnit unit,
//队列内部的使用数据结构
BlockingQueue<Runnable> workQueue)
1>.单线程池(newSingleThreadExecutor)
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
这个线程池可以清楚的看到初始线和最大线程都只有1个线程。
2>.定长线程池(newFixedThreadPool)
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
可以看到最大和初始化的线程数一样。适合任务量比较固定且时间消耗比较长的
3>.可缓存线程池(newCachedThreadPool)
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
这个线程数量基本可以无限加,但是没用后60s之后就消失了。比较以用于任务量大但是消耗时间比较小的。
4>.大小无限制的线程池(newScheduledThreadPool)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
这个线程池也时固定大小的,构造方法里边指定,最大线程数也是无限加,非核心线程创建完以后就销毁。但是这个线程池使用的DelayedWorkQueue,意味着他会把任务排序,执行完以后循环。适用于定时任务和固定周期的任务。
自定义线程池根据业务需求使用ThreadPoolExecutor去创建,
这些可能也不全,而且理解也不够透彻,并发编程的水太深了。