Java多线程和并发知识点总结

研二快要找工作了,自己整理一点常见面试题…
参考书目:《Java多线程编程核心技术》、《Java并发编程的艺术》

1.进程和线程

进程是操作系统结构的基础,是一次程序的执行,是系统进行资源分配和调度的一个独立单位。
线程是操作系统调度的最小单元,可理解为在进程中独立运行的子任务,被称为轻量级进程。一个进程至少有一个线程。

2.使用多线程

一是继承Thread类,二是实现Runnable接口。由于Java不支持多继承,所以为了改变这种限制,可以使用Runnable接口的方式实现多线程技术。

3.线程安全和非线程安全

线程安全就是以获得的实例变量的值是经过同步处理的,不会出现脏读的现象。
非线程安全就是多个线程对同一个对象中的实例变量进行并发访问时发生,产生的结果就是“脏读”,也就是取到的数据其实是被改过的。(多个线程对同一个对象中的同一个实例变量进行操作时,会出现值被更改、值不同步的情况)。

4.synchronized关键字

重量级锁,可修饰方法或以同步块的形式进行使用,主要确保多个线程在同一时刻,只能有一个线程处于方法或者同步块中,保证了线程对变量访问的可见性和排他性。Java中每一个对象都可以作为锁,synchronized用的锁是存在Java对象头里的。
synchronized关键字加到static静态方法上是给Class类上锁;加到非static静态方法上是给对象上锁;对于同步方法块,锁是synchronized括号里配置的对象。

5.volatile关键字

如果一个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的。可用来修饰字段(成员变量),就是告知程序任何对该变量的访问均需要从共享内存中获取,对它的改变必须同步刷新回共享内存,它能保证所有线程对变量访问的可见性。最致命的缺点是不支持原子性。

6.锁状态

锁一共四种状态,级别从低到高为:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。锁可以升级但不能降级。

1.偏向锁:加锁和解锁不需要额外消耗;如果线程间存在锁竞争,会带来额外的锁撤销消耗;适用于只有一个线程访问同步块场景。
2. 轻量级锁:竞争的线程不会阻塞;如果始终得不到锁竞争的线程,使用自旋会消耗CPU;适用于追求响应时间场合。
3. 重量级锁:线程竞争不会使用自旋,不会消耗CPU;线程阻塞,响应时间缓慢;适用于追求吞吐量场合。

7.线程的状态

  1. NEW:初试状态,线程构建,但是还没有调用start()方法;
  2. RUNNABLE:运行状态;
  3. BLOCKED:阻塞状态,表示线程阻塞于锁;
  4. WAITING:等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或中断);
  5. TIME_WAITING:超时等待状态,可在指定的时间自行返回;
  6. TERMINATED:终止状态,表示线程已经执行完毕。
    在这里插入图片描述

8.等待/通知机制

一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。

9.Lock接口提供synchronized不具备的主要特性

  1. 尝试非阻塞地获取锁:当前线程尝试获取锁,如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁;
  2. 能被中断地获取锁:与synchronized不同,获取到锁的线程能过响应中断,当获取到锁的线程被中断时,中断异常将被抛出,同时锁会被释放;
  3. 超时获取锁:在指定的截止时间之前获取锁,如果截止时间到了仍旧无法获取锁,则返回。

10.ReentrantLock重入锁

该锁能够支持一个线程对资源的重复加锁,还支持获取锁时的公平和非公平性选择。
公平锁:在绝对时间上,先对锁进行获取的请求一定先被满足,也就是等待时间最长的线程最优先获取锁。公平锁能减少“饥饿”发生的概率,效率没有非公平锁高。
非公平锁:获取锁的顺序不是按照请求顺序。虽然可能造成线程“饥饿”,但极少的线程切换,保证了更大的吞吐量。

11.ReentrantReadWriteLock读写锁

读写锁维护了一对锁,一个读锁和一个写锁。当写锁被获取时,后续的读写操作都会被阻塞,写锁释放后,所有操作继续执行。读写锁能够提供比排它锁更好的并发性和吞吐量。
锁降级指写锁降级称为读锁,把持住(当前拥有的)写锁,再获取到读锁,随后释放先前拥有的写锁。

12.LockSupport工具

LockSupport定义了一组的公共静态方法,这些方法提供了最基本的线程阻塞和唤醒功能。
定义了一组以park开头的方法用来阻塞当前线程,以及unpark方法来唤醒一个被阻塞的线程。

13.Condition接口

获取一个Condition必须通过Lock的newCondition()方法。

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();

调用await()方法后,当前线程会释放锁并在此等待,而其他线程调用Condition对象的signal()方法,通知当前线程后,当前线程才从await()方法返回,并且返回前已经获取了锁。
可以通过Condition接口实现有界队列。有界队列:当队列为空是,队列的获取操作将会阻塞获取线程,直到队列中有新增元素,当队列已满时,队列的插入操作将会阻塞插入线程,直到队列出现“空位”。

14.ConcurrentHashMap

ConcurrentHashMap是线程安全且高效的HashMap。并发编程中使用HashMap可能导致程序死循环,而使用线程安全的HashTable效率又非常低下(所有访问HashTable的线程必须竞争同一把锁)。
ConcurrentHashMap锁分段技术:首先将数据分成一段一段地存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个数据段的时候,其他段的数据段也能被其他线程访问。
ConcurrentHashMap是由Segment数组和HashEntry数组构成。Segment是一种可重入锁,在ConcurrentHashMap里扮演锁的角色;HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,一个Segment数组包含一个HashEntry数组。Segment是一种数组和链表结构,HashEntry是一个链表结构的元素。每个Segment守护着一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得与它对应的Segment锁。

15.阻塞队列

7个阻塞队列

  1. ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
  2. LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
  3. PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
  4. DelayQueue:一个使用优先级队列实现的无界阻塞队列。
  5. SynchronousQueue:一个不存储元素的阻塞队列。
  6. LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
  7. LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。

16.Fork/Join框架

把一个大任务分割成若干个小任务,最终汇总每个小任务结果得到大任务结果的框架。
工作窃取算法:某个线程从其他队列里窃取任务来执行。优点:充分利用线程进行并行计算,减少了线程间的竞争。 缺点:某些情况下还是存在竞争,比如双端队列只有一个任务时;算法会消耗更多的系统资源。
Fork/Join使用两个类来完成分割任务和执行任务并合并结果。
ForkJoinTask:RecursiveAction用于没有返回结果的任务,RecursiveTask用于有返回结果的任务。
ForkJoinPool:ForkJoinTask需要通过ForkJoinPool来执行

17.线程池

线程池好处:降低资源消耗;提高响应速度;提高线程的可管理性。
线程池处理流程:

  1. 线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程。
  2. 线程池判断工作队列是否已满。如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。
  3. 线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。

创建线程池:

new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,handler)

corePoolSize:线程池的基本大小;
maximumPoolSize:线程池最大数量,线程池允许创建的最大线程数;
keepAliveTime:线程池的工作线程空闲后,保持存活的时间;
unit:keepAliveTime的单位;
workQueue:任务队列,用于保存等待执行的任务的阻塞队列;
handler:拒绝策略,队列和线程池都满了,必须采取一种策略处理提交的新任务。
四种策略:
AbortPolicy:直接抛出异常;
CallerRunsPolicy:只用调用者所在的线程来运行任务;
DiscardOlderPolicy:丢弃队列里最近的一个任务,并执行当前任务;
DiscardPolicy:不处理,丢弃掉。
可使用两个方法向线程池提交任务,execute()用于提交不需要返回值的任务,submit()用于提交需要返回值的任务。

18.3种类型的ThreadPoolExecutor

FixedThreadPool:可重用固定线程数的线程池。
SingleThreadExecutor:使用单个worker线程的Executor。
CacheThreadPool:一个会根据需要创建新线程的线程池。corePoolSize为0,maximumPoolSize设置为Integer.MAX_VALUE;

19.死锁

不同的线程都在等待根本不可能被释放的锁,从而导致所有的任务都无法继续完成。
可使用JDK自带的工具检测是否有死锁。首先进入cmd工具,再进入JDK的安装文件夹中的bin目录,执行jps命令,得到线程id,再执行jstack命令查看结果。

20.并发工具类

CountDownLatch:允许一个或多个线程等待其他线程完成操作。
CyclicBarrier:让一组线程到达一个同步点时被阻塞,直到最后一个线程到达时,屏障才会开门,所有被屏障拦截的线程才会继续运行。
CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置。
Semaphore:用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。
Exchanger:用于进行线程间的数据交换。如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange()方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。

21.ThreadLocal

线程变量,是一个以TheadLocal对象为键、任意对象为值的存储结构。即一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。

22.sleep()、join()、wait()、yield()

join()作用是等待线程对象销毁,内部使用wait()实现,方法执行会释放锁;
wait()执行后当前线程的锁被释放,是Object类中的方法,只能在同步方法或同步块中使用;
sleep()方法作用是在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),是Thread类的方法,sleep()方法不释放锁;
yield()方法作用是放弃当前的CPU资源,将它让给其他的任务去占用CPU执行时间。

23.Java内存模型

JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本。
1)线程A吧本地内存A中更新的共享变量刷新到主内存中去;
2)线程B到主内存中去读取线程A之前已更新过的共享变量。

发布了2 篇原创文章 · 获赞 1 · 访问量 33

猜你喜欢

转载自blog.csdn.net/weixin_45341772/article/details/105178667