java并发编程

一、并发编程基础

1、线程和进程的区别(什么是线程,什么是进程)?

进程是资源分配的最小单位,线程是程序执行的最小单位。

进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。 而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。

线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。

但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。

2、什么情况下使用多线程?

通过使用多线程提高程序的执行能力(不一定快);

采用异步线程,减少需要等待网络、 I/O 响应导致耗费大量的执行时间。

3、什么是多线程的上下文切换?

CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这个任务的状态。所以任务从保存到再加载的过程就是一次上下文切换。

4、如何使用多线程(创建线程的方式)?

继承Thread类;

实现Runnable接口;

实现Callable接口通过FutureTask包装器来创建Thread线程;

5、线程的(状态)生命周期?

线程一共有6种状态(NEW、RUNNABLE、BLOCKED、WAITING、TIME_WAITING、TERMINATED)

NEW:初始状态,线程被构建,但是还没有调用start方法

RUNNABLED:运行状态,JAVA线程把操作系统中的就绪和运行两种状态统一称为“运行中”

BLOCKED:阻塞状态,表示线程阻塞与锁。

WAITING:等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程的一些特定动作(通知或中断)

TIME_WAITING:超时等待状态,超时以后自动返回

TERMINATED:终止状态,表示当前线程执行完毕

6、如何关闭一个线程?

调用目标关闭线程对象的interrupt()方法,,表示向当前线程打个招呼,告诉他可以中断线程的执行了,至于什么时候中断,取决于当前线程自己。线程通过检查资深是否被中断来进行相应,可以通过isInterrupted()来判断是否被中断。也可以在线程内部的代码中有条件的调用静态方法Thread.interrupted()方法复位线程。

7、什么是Daemon线程?

Daemon线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作,可以通过调用Thread.setDaemon(true)将线程设置为Daemon线程(必须在线程启动之前)。

8、如何理解并发编程的三个概念:原子性、可见性、有序性?

原子性指在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。

可见性指当一个线程对共享的变量进行修改后,其余线程能读到最新修改的值,Java提供了volatile关键字来保证可见性。当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。

有序性指在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。在Java里面,可以通过volatile关键字来保证一定的“有序性”。另外可以通过synchronized和Lock来保证有序性,很显然,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。

9、线程间是如何通信的?

大致有两类方式:

第一类是通过volatile或sychornized关键字保证成员变量保证成员变量的可见性;

另一类是通过等待/通知机制达到线程间通信的目的,例如使用wait/notify或者thread.join()

二、并发机制及底层原理

1、JMM是如何解决可见性、原子性、有序性的?(注意这里是JMM,不牵扯JDK的API例如锁之类的,但可以引申)

原子性:在java中提供了两个高级的字节码指令monitorenter和monitorexit,在Java中对应的Synchronized来保证代码块内的操作是原子的

可见性,Java中的volatile关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存,被其修饰的变量在每次是用之前都从主内存刷新。 因此,可以使用volatile来保证多线程操作时变量的可见性。除了volatile,Java中的synchronized和final两个关键字也可以实现可见性

有序性,在Java中,可以使用synchronized和volatile来保证多线程之间操作的有序性。 实现方式有所区别:volatile关键字会禁止指令重排。synchronized关键字保证同一时刻只允许一条线程操作。

2、如何理解volatile关键字?

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

  1)通过强制将修改的值立即写入主存保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

  2)通过加入内存屏障禁止进行指令重排序,确保程序有序性。

3、如何理解sychornized关键字?

sychornize关键字是通过加锁让代码同步方式解决了并发中的原子性、有序性、和可见性问题的,Java中的每一个对象都可以作为sychornized的锁,具体表现为以下3种形式:

对于普通同步方法,锁是当前实例对象。

对于静态同步方法,锁是当前类的Class对象。

对于同步方法块,锁是Synchonized括号里配置的对象。

三、Java中的锁

1、简述AQS?

AQS是队列同步器AbstractQueuedSynchronizer的简称,是用来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。

2、基于AQS都有哪些同步组件?

例如CountDownLatch,允许一个多多个线程等待其他线程完成操作,可以使用它做阻塞队列,例如创建时传入2,在countDownLatch.await()的位置就会阻塞,等待有两个线程调用count()将计数器减为0才会运行后续的方法。

例如Semaphore,它是用来控制并发线程数的,通过信号量来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。

四、线程池

1、简述Java中的线程池,及其使用好处?

Java中的线程池是用于管理线程生命周期,调度线程执行的工具,合理的使用线程池可以:

重用存在的线程,减少对象的创建、消亡的开销,提高了性能。

可有效控制最大并发线程数,提高系统资源利用率,同时可以避免过多资源竞争,避免阻塞。

提供定时执行、定期执行、单线程、并发线程数控制等功能。

2、简述ThreadPoolExecutor的参数,都代表什么含义?

int corePoolSize, 核心线程数量。

int maximumPoolSize, 最大线程数量。

long keepAliveTime,  线程没有任务执行最多保持多久时间。

TimeUnit unit, keepAliveTime的时间单位。

BlockingQueue workQueue, 阻塞队列,存储等待执行的任务,很重要,会对线程池运行过程产生重大影响。

ThreadFactory threadFactory, 线程池创建工厂。

RejectedExecutionHandler handler, 拒绝策略。

3、简述线程池执行过程(ThreadPoolExecutor执行execute的4种情况)?

1)如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁)。

2)如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue。

3)线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。

4)如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法。

4、常见的线程池都有哪些?

FixedThreadPool(可重用固定线程数的线程池)

SingleThreadExecutor(是使用单个worker线程的Executor)

CachedThreadPool(根据需要创建新线程的线程池)

你可能感兴趣的:(java并发编程)