juc(1)

1.了解线程吗,他和进程有什么区别

线程的基本概念
  线程是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,线程基本上自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。

好处 :

​ (1)易于调度。

​ (2)提高并发性。通过线程可方便有效地实现并发性。进程可创建多个线程来执行同一程序的不同部分。

​ (3)开销少。创建线程比创建进程要快,所需开销很少。

​ (4)利于充分发挥多处理器的功能。通过创建多线程进程,每个线程在一个处理器上运行,从而实现应用程序的并发性,使每个处理器都得到充分运行

线程和进程的区别一

  简单地讲,任何的一个程序都必须有且有一个以上的进程,而相对于一个进程而言也必须要有且有一个以上的线程。相对于进程而言,对线程进行划分的尺度一般要小很多,这就导致了多线程的一些程序能够出现更高的并发性。

(线程相比进程划分尺度小,迸发性更高)

线程和进程的区别二

  在执行进程的时候,一般会具有相互独立的多个内存单元。但是多个线程是可以共享内存的,这样运行效率就很大的程度上被提高了。相对于单个的独立进程而言都会有相应程序的运行入口以及一些程序等出口。线程就不一样了,它不能独立的去执行而必须要依附在相应的应用程序里面。这样的话应用程序就可以执行多个线程并进行相应的控制。

(同一进程的线程共享内存,运行效率高)

线程和进程的区别三

  通过了解逻辑角度我们可以得知,多线程这样的意义是相对于在一个应用程序里面的,能够同时的执行。而操作系统不会认为多个线程就是多个独立应用,因此也就不会使其调度以及管理实现资源的分配。

(线程没有资源分配)

1
2
3
4
5
6
7
8
9
了解的,线程时进程中执行运算的最小单位,是被系统独立调度和分派的基本单位。可以创建线程或者撤销另一个线程,可以迸发执行。
区别:
1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位

2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行

3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源.

4)系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。

进程和线程的区别是什么?

进程是执行着的应用程序,而线程是进程内部的一个执行序列。一个进程可以有多个线程。线程又叫做轻量级进程。

2.线程和进程的通信方式了解吗

进程间通信方式
进程间通信主要包括管道、系统IPC(包括消息队列、信号量、信号、共享内存等)、以及套接字socket

  (1)管道(Pipe):管道可用于具有亲缘关系进程间的通信,允许一个进程和另一个与它有共同祖先的进程之间进行通信。
  (2)命名管道(named pipe):命名管道克服了管道没有名字的限制,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。
  (3)信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送 信号给进程本身。
  (4)消息(Message)队列:消息队列是消息的链接表,包括Posix消息队列system V消息队列。
  (5)共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。
  (6)内存映射(mapped memory):内存映射允许任何多个进程间通信,每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间来实现它。
  (7)信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
  (8)套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。

线程间通信的方式

临界区:通过多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问;
互斥量: Synchronized/Lock:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问
信号量: Semphare:为控制具有有限数量的用户资源而设计的,它允许多个线程在同一时刻去访问同一个资源,但一般需要限制同一时刻访问此资源的最大线程数目。
事件(信号),Wait/Notify:通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较

3.线程的创建方式

四种方式

(1)通过继承Thread类来创建新的线程。

(2)通过实现Runnable接口来创建线程。

Thread和Runnable区别:

  • Thread 是类,需要继承,但是一个类只能继承一个类,一旦继承 Thread 之后,无法在继承其他的类。
  • Runnable 是接口,一个类可以实现多个接口,所以实现Runnable接口可拓展性更好
  • Thread是Runnable接口的实现类

(3)通过实现Callable接口来创建线程。

Runnable和Callable的区别

  • Runnable没有返回值。
  • Callable可以返回值。

(4)自定义线程池(推荐),从中获取线程。

4.sleep和wait的区别

(1)限制

使用 sleep 方法可以让让当前线程休眠,时间一到当前线程继续往下执行,在任何地方都能使用,但需要捕获 InterruptedException 异常。

而使用 wait 方法则必须放在 synchronized 块里面,同样需要捕获 InterruptedException 异常,并且需要获取对象的锁。而且 wait 还需要额外的方法 notify/ notifyAll 进行唤醒,它们同样需要放在 synchronized 块里面,且获取对象的锁。

(2)使用场景

sleep 一般用于当前线程休眠,或者轮循暂停操作,wait 则多用于多线程之间的通信。

(3)类不同

sleep 是 Thread 类的静态本地方法,wait 则是 Object 类的本地方法。

(4)释放锁

wait 可以释放当前线程对 lock 对象锁的持有,而 sleep 则不会。

(5)线程切换

sleep 会让出 CPU 执行时间且强制上下文切换,而 wait 则不一定,wait 后可能还是有机会重新竞争到锁继续执行的。

5.run和start的区别

1.线程中的start()方法和run()方法的主要区别在于,当程序调用start()方法,将会创建一个新线程去执行run()方法中的代码。但是如果直接调用run()方法的话,会直接在当前线程中执行run()中的代码,注意,这里不会创建新线程。这样run()就像一个普通方法一样。

2.另外当一个线程启动之后,不能重复调用start(),否则会报IllegalStateException异常。但是可以重复调用run()方法。

总结起来就是run()就是一个普通的方法,而start()会创建一个新线程去执行run()的代码。

6.了解synchronize吗

  1. 修饰实例方法,对于普通同步方法,锁是当前的实例对象
  2. 修饰静态方法,对于静态同步方法,锁是当前的Class对象
  3. 修饰方法代码块,对于同步方法块,锁是synchronized括号里面配置的对象!

7.创建线程池的核心参数有哪些?多讲几个?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
  1. corePoolSize:线程池的核心线程数。核心线程会一直存活,即便没有任务需要执行,当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理。设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭。

  2. maximumPoolSize:线程池允许的最大线程数。当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务。当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常。

  3. keepAliveTime:线程空闲时间。当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize。如果allowCoreThreadTimeout=true,则会直到线程数量=0。

  4. unit:keepAliveTime的时间单位。

  5. workQueue:当核心线程数达到最大时,新任务会放在队列中排队等待执行。

  6. threadFactory:线程工厂,ThreadFactory是一个接口,只有一个方法,即newThread(Runnable r)。从这个方法名字就可以知道,这接口是用来创建新的线程的。其使用也很简单,仅仅只需要实现newThread方法,根据自己的需要进行线程的创建即可。

  7. handler:任务拒绝处理器。两种情况会拒绝处理任务:

    1)、当线程数已经达到maxPoolSize,切队列已满,会拒绝新任务;

    2)、当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置默认是AbortPolicy,会抛出异常。拒绝策略有如下几种:

    • AbortPolicy 丢弃任务,抛运行时异常。

    • CallerRunsPolicy 如果任务被拒绝了,则由调用线程(提交任务的线程)直接执行此任务

    • DiscardPolicy 丢弃任务,不抛出异常

    • DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务,重新提交被拒绝的任务。

9.说说你对volatile关键字的理解

被volatile修饰的共享变量,就具有了以下两点特性:

1 . 保证了不同线程对该变量操作的内存可见性;

2 . 禁止指令重排序

连环问(1)能不能详细说下什么是内存可见性,什么又是重排序呢?

内存可见性是指当一个线程修改了某个变量的值,其它线程总是能知道这个变量变化。 也就是说,如果线程 A 修改了共享变量 V 的值,那么线程 B 在使用 V 的值时,能立即读到 V 的最新值。

重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。

10.说一下公平锁和非公平锁区别

1
2
3
4
公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。

优点:所有的线程都能得到资源,不会饿死在队列中。
缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。
1
2
3
4
非公平锁:多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。

优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。
缺点:你们可能也发现了,这样可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。

11.ReentrantLock的trylock和lock区别

1: lock拿不到锁会一直等待,并且没有返回值。tryLock是去尝试,拿不到就返回false,拿到返回true。

2: tryLock是可以被打断的,被中断的,lock是不可以。

12.CountDownLatch和Semaphore的区别和底层原理

CountDownLatch表示计数器,可以给CountDownLatch设置一个数字,一个线程调用CountDownLatch的await()将会阻塞,其他线程可以调用CountDownLatch的countDown()方法来对CountDownLatch中的数字减一,当数字被减成o后,所有await的线程都将被唤醒.

对应的底层原理就是,调用await()方法的线程会利用AQS排队,一旦数字被减为O,则会将AQS中排队的线程依次唤醒。

Semaphore表示信号量,可以设置许可的个数,表示同时允许最多多少个线程使用该信号量,通过acquire()来获取许可,如果没有许可可用则线程阻塞,并通过AQS来排队,可以通过release()方法来释放许可,当某个线程释放了某个许可后,会从AQS中正在排队的第一个线程开始依次唤醒,直到没有空闲许可。

13.Sychronized的偏向锁、轻量级锁、重量级锁

1.偏向锁:在锁对象的对象头中记录一下当前获取到该锁的线程ID,该线程下次如果又来获取该锁就可以直接获取到了

轻量级锁:由偏向锁升级而来,当一个线程获取到锁后,此时这把锁是偏向锁,此时如果有第二个线程来竞争锁,偏向锁就会升级为轻量级锁,之所以叫轻量级锁,是为了和重量级锁区分开来,轻量级锁底层是通过自旋来实现的,并不会阻塞线程

3.如果自旋次数过多仍然没有获取到锁,则会升级为重量级锁,重量级锁会导致线程阻塞

4.自旋锁:自旋锁就是线程在获取锁的过程中,不会去阻塞线程,也就无所谓唤醒线程。阻塞和唤醒这两个步骤都是需要操作系统去进行的,比较消耗时间,自旋锁是线程通过CAS获取预期的一个标记,如果没有获取到,则继续循环获取,如果获取到了则表示获取到了锁,这个过程线程一直在运行中,相对而言没有使用太多的操作系统资源,比较轻量。

14.Sychronized和ReentrantLock的区别

  1. sychronized是一个关键字,ReentrantLock是一个类
  2. sychronized会自动的加锁与释放锁,ReentrantLock需要程序员手动加锁与释放锁
  3. sychronized的底层是JVM层面的锁,ReentrantLock是APl层面的锁
  4. sychronized是非公平锁,ReentrantLock可以选择公平锁或非公平锁
  5. sychronized锁的是对象,锁信息保存在对象头中,ReentrantLock通过代码中int类型的state标识来标识锁的状态
  6. sychronized底层有一个锁升级的过程

15.线程池的底层工作原理

线程池内部是通过队列+线程实现的,当我们利用线程池执行任务时:

1.如果此时线程池中的线程数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。

⒉.如果此时线程池中的线程数量等于corePoolSize,但是缓冲队列workQueue未满,那么任务被放入缓冲队列。

3.如果此时线程池中的线程数量大于等于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。

4.如果此时线程池中的线程数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。

5.当线程池中的线程数量大于corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数

16.BIO,NIO,AIO分别是什么

1.BIO:同步阻塞lO,使用BIO读取数据时,线程会阻塞住,并且需要线程主动去查询是否有数据可读,并且需要处理完一个Socket之后才能处理下一个Socket

2.NIO:同步非阻塞lO,使用NIO读取数据时,线程不会阻塞,但需要线程主动的去查询是否有IO事件

3.AlO:也叫做NIO 2.0,异步非阻塞lO,使用AIO读取数据时,线程不会阻塞,并且当有数据可读时会通知给线程,不需要线程主动去查询