1.开启线程的三种方式?
Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。
Java可以用三种方式来创建线程:

1、继承Thread类创建线程
2、实现Runnable接口创建线程
3、使用Callable和Future创建线程

1、继承Thread类创建线程
通过继承Thread类来创建并启动多线程的一般步骤如下
1、定义Thread类的子类,并重写该类的run()方法,该方法的方法体就是线程需要完成的任务,run()方法也称为线程执行体
2、创建Thread子类的实例,也就是创建了线程对象
3、启动线程,即调用线程的start()方法
public class MyThread extends Thread {

public void run(){
    System.out.println("打印---测试1");
}
}

public class Main {

public static void main(String[] args) {
    new MyThread().start();//创建并启动线程
}
}

2、实现Runnable接口创建线程
通过实现Runnable接口创建并启动线程一般步骤如下:
1、定义Runable接口的实现类,一样要重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体
2、创建Runable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象
3、通过调用线程对象的start()方法来启动线程

    public class MyThread2 implements Runnable {
    @Override
    public void run() {
    System.out.println("打印---测试Runnable");
    }
       }

    public class Main2 {
    public static void main(String[] args) {
    //创建并启动线程
    MyThread2 myThread2=new MyThread2();
    Thread thread=new Thread(myThread2);
    thread.start();
    // 或者 new Thread(new MyThread2()).start();
    }
    }

3、使用Callable和Future创建线程
1、创建 Callable 接口的实现类,并实现 call()方法,该call()方法将作为线程执行体,并且有返回值
2、创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值
3、使用FutureTask对象作为 Thread 对象的 target 创建并启动新线程
4、调用FutureTask对象的 get()方法来获得子线程执行结束后的返回值

public class MyThread3 implements Callable {
@Override
public Object call() throws Exception {
    System.out.println("打印---Callable方式");
    return 10;
}
}
public class Main3 {
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
    Callable callable=new MyThread3();
    FutureTask task=new FutureTask(callable);
    new Thread(task).start();
    System.out.println(task.get());
    Thread.sleep(10);//等待线程执行结束
    //task.get() 获取call()的返回值,若调用时call()方法未返回,则阻塞线程等待返回值
    //get的传入参数为等待时间,超时抛出超时异常;传入参数为空时,则不设超时,一直等待
    System.out.println(task.get(100L, TimeUnit.MILLISECONDS));
}
}

三种方式的优缺点
采用继承Thread类方式:
(1)优点:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this,即可获得当前线程
(2)缺点:因为线程类已经继承了Thread类,所以不能再继承其他的父类
采用实现Runnable接口方式:
(1)优点:线程类只是实现了Runable接口,还可以继承其他的类。在这种方式下,可以多个线程共享同一个目标对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU代码和数据分开,形成清晰的模型,较好的体现了面向对象的思想。
(2)缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法
Runnable和Callable的区别:
(1)Callable规定的方法是call(),Runnable规定的方法是run()
(2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值的
(3)call方法可以抛出异常,run方法不可以,因为run方法本身没有抛出异常,所以自定义的线程类在重写run的时候也无法抛出异常
(4)运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务的执行情况,可取消任务的执行,还可获取执行结果
start()和run()的区别
1、start()方法用来,开启线程,但是线程开启后并没有立即执行,他需要获取CPU的执行权才可以执行
2、run()方法是由JVM创建完本地操作系统级线程后回调的方法,不可以手动调用(否则就是普通方法)

2.说说进程,线程,协程之间的区别
  1、进程

进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。由于进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全。

  2、线程

线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。

  3、协程

协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。

二、区别:

  1、进程多与线程比较

线程是指进程内的一个执行单元,也是进程内的可调度实体。线程与进程的区别:

1) 地址空间:线程是进程内的一个执行单元,进程内至少有一个线程,它们共享进程的地址空间,而进程有自己独立的地址空间
2) 资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源
3) 线程是处理器调度的基本单位,但进程不是
4) 二者均可并发执行

5) 每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口,但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制

  2、协程多与线程进行比较

1) 一个线程可以多个协程,一个进程也可以单独拥有多个协程,这样python中则能使用多核CPU。

2) 线程进程都是同步机制,而协程则是异步

3) 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态

3.进程之间是如何通信的?
1、管道:单向,a把数据给B,要等B确实拿走了之后才返回
2、消息队列:A一送走消息就回来,不需要等B确认拿走,类似缓存,但是A每次拷贝消息内容很占内存
3、共享内存:我们都知道,系统加载一个进程的时候,分配给进程的内存并不是实际物理内存,而是虚拟内存空间。那么我们可以让两个进程各自拿出一块虚拟地址空间来,然后映射到相同的物理内存中,这样,两个进程虽然有着独立的虚拟内存空间,但有一部分却是映射到相同的物理内存,这就完成了内存共享机制了。
4、信号量:共享内存最大的问题在于线程竞争,信号量就解决了线程安全。线程进去,信号量置0;
5、Socket:以上都是多个线程在一台主机上的通信,多台计算机之间的线程通信可以用套接字:源地址+源端口+目的地址+目的端口

4.什么是Daemon线程?它有什么意义?
所谓后台(daemon)线程,是指在程序运行的时候在后台提供一种通用服务的线
程,并且这个线程并不属于程序中不可或缺的部分。因此,当所有的非后台线程
结束时,程序也就终止了,同时会杀死进程中的所有后台线程。反过来说,
只要有任何非后台线程还在运行,程序就不会终止。必须在线程启动之前调用
setDaemon()方法,才能把它设置为后台线程。注意:后台进程在不执行 finally
子句的情况下就会终止其 run()方法。
比如:JVM 的垃圾回收线程就是 Daemon 线程,Finalizer 也是守护线程。

5.在java中守护线程和本地线程区别?
java 中的线程分为两种:守护线程(Daemon)和用户线程(User)。
任何线程都可以设置为守护线程和用户线程,通过方法 Thread.setDaemon(boolon);true 则把该线程设置为守护线程,反之则为用户线程。Thread.setDaemon()必须在 Thread.start()之前调用,否则运行时会抛出异常。
两者的区别:
唯一的区别是判断虚拟机(JVM)何时离开,Daemon 是为其他线程提供服务,如果全部的 User Thread 已经撤离,Daemon 没有可服务的线程,JVM 撤离。也可以理解为守护线程是 JVM 自动创建的线程(但不一定),用户线程是程序创建的线程;比如 JVM 的垃圾回收线程是一个守护线程,当所有线程已经撤离,不再产生垃圾,守护线程自然就没事可干了,当垃圾回收线程是 Java 虚拟机上仅剩的线程时,Java 虚拟机会自动离开。
扩展:Thread Dump 打印出来的线程信息,含有 daemon 字样的线程即为守护进程,可能会有:服务守护进程、编译守护进程、windows 下的监听 Ctrl+break的守护进程、Finalizer 守护进程、引用处理守护进程、GC 守护进程。

6.为什么要有线程,而不是仅仅用进程?
进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率。很多人就不理解了,既然进程这么优秀,为什么还要线程呢?其实,仔细观察就会发现进程还是有很多缺陷的,主要体现在两点上:
进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。
1.多核cpu情况下,多线程实现了真正意义上的并行;
2.在一个应用进程中,一个任务的阻塞会引起其他不依赖于该任务的任务也被阻塞。可以给不同的任务创建不同的线程来提高程序处理的实时性;
3.线程被认为是轻量级的进程,线程的创建和销毁比进程快;

java的同步机制
为了解决多线程情况下对同一个变量访问的冲突:ThreadLocal、volatile、synchronized、wait notify
1.ThreadLocal:为了实现线程隔离。为每一个使用该变量的线程提供一个该变量的副本,这样每个线程都拥有自己的副本,相互之间没有影响;
2.volatile:可见性和有序性。强迫每次线程访问该变量时都要重读 共享内存中该变量的值。当该变量发生变化时,也强迫将变化后的值写入共享内存。
3.wait:会导致线程放弃cpu,也放弃对象锁,只有notify调用,才会进入锁的等待中。
4.synchronized:

7.什么是可重入锁(ReentrantLock)?
可重入锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized 和 ReentrantLock等等 ) 。这些已经写好提供的锁为我们开发提供了便利,但是锁的具体性质却很少被提及。本章将分析JAVA下可重入锁特性,为大家答疑解惑。

释义

广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁,重入锁以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。ReentrantLock和synchronized都是可重入锁

可重入锁的意义便在于防止死锁!!!

实现原理是通过为每个锁关联一个请求计数器和一个占有它的线程。当计数为0时,认为锁是未被占有的;线程请求一个未被占有的锁时,JVM将记录锁的占有者,并且将请求计数器置为1 。

如果同一个线程再次请求这个锁,计数将递增;

每次占用线程退出同步块,计数器值将递减。直到计数器为0,锁被释放。
不可重入锁

不可重入锁,与可重入锁相反,不可递归调用,递归调用就发生死锁。

同步与异步,阻塞与非阻塞
同步/异步, 它们是消息的通知机制

同步:
所谓同步,当前程序执行完才能执行后面的程序,程序执行时按照顺序执行,需要等待。平时写的代码基本都是同步的;

异步:
异步的概念和同步相对。
程序没有等到上一步程序执行完才执行下一步,而是直接往下执行,前提是下面的程序没有用到异步操作的值,异步的实现方式基本上都是多线程(定时任务也可实现,但是情况少)。

同步就是烧开水,需要自己去轮询(每隔一段时间去看看水开了没),异步就是水开了,然后水壶会通知你水已经开了,你可以回来处理这些开水了。
同步和异步是相对于操作结果来说,会不会等待结果返回。
阻塞/非阻塞, 它们是程序在等待消息(无所谓同步或者异步)时的状态.

阻塞:
阻塞调用是指调用结果返回之前,当前线程会被挂起。函数只有在得到结果之后才会返回。
有人也许会把阻塞调用和同步调用等同起来,实际上他是不同的。
对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。

非阻塞:非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。

阻塞就是说在煮水的过程中,你不可以去干其他的事情,非阻塞就是在同样的情况下,可以同时去干其他的事情。阻塞和非阻塞是相对于线程是否被阻塞。

8.什么是线程组,为什么在Java中不推荐使用?
线程组ThreadGroup对象中的stop,resume,suspend会导致安全问题,主要是死锁问题,已经被官方废弃,多以价值已经大不如以前。
线程组ThreadGroup不是线程安全的,在使用过程中不能及时获取安全的信息。

9.乐观锁和悲观锁的理解及如何实现,有哪些实现方式?
CAS机制:是英文单词Compare And Swap的缩写,翻译过来就是比较并替换。

CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。
更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。

从思想上来说,Synchronized属于悲观锁,悲观地认为程序中的并发情况严重,所以严防死守。CAS属于乐观锁,乐观地认为程序中的并发情况不那么严重,所以让线程不断去尝试更新。
悲观锁包括:
重量级锁:一个线程拿锁执行后,后面的线程全部进入阻塞态
自旋锁:线程进入阻塞态非常麻烦,需要从用户态切换到内核态,非常耗时,还要保存各种数据,所以可以认为给一个数字,让线程进入空循环,再看有没有释放锁。
自适应自旋锁:不需要认为定循环多少次,可以通过自适应算法,他以前拿过这个锁的次数多,则能拿到锁的概率大,可以多等待一段时间,反之,相反。
乐观锁包括:
轻量级锁:不加锁了,用一个信号量,只要有线程进来,就改变状态位,,出去相应的将状态位改回来。
偏向锁:类似单线程,不加锁,一个线程进入,存入他的地址,状态位置忙,出去了也不修改状态位,如果下次再进来直接比对地址,比对状态在忙,一样的地址则直接进,如果地址不一样就升级为轻量锁。

10.Java中用到的线程调度算法是什么?
有两种调度模型:分时调度模型和抢占式调度模型。

分时调度模型是指让所有的线程轮流获得 cpu 的使用权,并且平均分配每个线程占用的 CPU 的时间片这个也比较好理解。

Java虚拟机采用抢占式调度模型,是指优先让可运行池中优先级高的线程占用CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU。处于运行状态的线程会一直运行,直至它不得不放弃 CPU。

Java默认的线程调度算法好像是:
抢占式。一个线程用完CPU之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。

11.同步方法和同步块,哪个是更好的选择?
同步块是更好的选择,因为它不会锁住整个对象(当然你也可以让它锁住整个对
象)。同步方***锁住整个对象,哪怕这个类中有多个不相关联的同步块,这通
常会导致他们停止执行并需要等待获得这个对象上的锁。
同步块更要符合开放调用的原则,只在需要锁住的代码块锁住相应的对象,这样
从侧面来说也可以避免死锁。

12.run()和start()方法区别
1。start():

先来看看Java API中对于该方法的介绍:
 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
 结果是两个线程并发地运行;当前线程(从调用返回给 start 方法)和另一个线程(执行其 run 方法)。
 多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。

用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体中的代码执行完毕而直接继续执行后续的代码。通过调用Thread类的 start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里的run()方法 称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。

2。run():
同样先看看Java API中对该方法的介绍:
如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
Thread 的子类应该重写该方法。
run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。

3。总结:
调用start方法方可启动线程,而run方法只是thread类中的一个普通方法调用,还是在主线程里执行。

13.如何控制某个方法允许并发访问线程的个数?
static Semaphore sSemaphore = new Semaphore(6) 表示该方法最多允许几个线程访问
sSemaphore.acquire():调用一次,允许进入方法的线程数量减一
sSemaphore.release():调用一次,允许进入方法的线程数量加1
当允许进入方法的线程数量减为0的时候其他线程就等待,直到允许的线程数量大于0
sSemaphore.availablePermits():可以获取当前允许进入方法的线程数量

14.在Java中wait和seelp方法的不同;
1.来自不同的类Object和Thread;
2.wait释放锁而sleep不释放锁;
3.sleep必须抛出异常;
4.wait只能在同步代码块中,而Slepp可以用在任何地方;

join方法是用于在某一个线程的执行过程中调用另一个线程执行,等到被调用的线程执行结束后,再继续执行当前线程。如:t.join();//主要用于等待t线程运行结束,若无此句,main则会执行完毕,导致结果不可预测。

15.Thread类中的yield方法有什么作用?
yield方法
暂停当前正在执行的线程对象。
yield()方法是停止当前线程,让同等优先权的线程或更高优先级的线程有执行的机会。如果没有的话,那么yield()方法将不会起作用,并且由可执行状态后马上又被执行。

16.什么是不可变对象,它对写并发应用有什么帮助?
不可变对象(Immutable Objects)即对象一旦被创建它的状态(对象的数据,也即
对象属性值)就不能改变,反之即为可变对象(Mutable Objects)。
不可变对象的类即为不可变类(Immutable Class)。Java 平台类库中包含许多不可变类,如 String、基本类型的包装类、BigInteger 和 BigDecimal 等。
不可变对象天生是线程安全的。它们的常量(域)是在构造函数中创建的。既然
它们的状态无法修改,这些常量永远不会变。
不可变对象永远是线程安全的。只有满足如下状态,一个对象才是不可变的;它的状态不能在创建后再被修改;所有域都是 final 类型;并且,它被正确创建(创建期间没有发生 this 引用的逸出)。

17.谈谈wait/notify关键字的理解
同上

18.为什么wait, notify 和 notifyAll这些方法不在thread类里面?
wait的作用是在对象的方法调用时,根据某些条件判断,挂起当前线程。
因为是对象的调用,所以属于object类。
sleep是线程的调用。

19.什么导致线程阻塞?
阻塞状态的线程的特点是:该线程放弃CPU的使用,暂停运行,只有等到导致阻塞的原因消除之后才恢复运行。或者是被其他的线程中断,该线程也会退出阻塞状态,同时抛出InterruptedException。
sleep/wait/sychronized/i/o抢夺资源

20.讲一下java中的同步的方法
同步方法、同步代码块、wait/notify/notifyall方法、volatile关键字、线程隔离Threadlocal存副本,修改副本、重入锁lock,阻塞队列

21.谈谈对Synchronized关键字,类锁,方法锁,重入锁的理解
synchronized可以用来代码块,修饰方法,静态方法,类
1.代码块:修饰的代码块称为同步代码块。锁住的调用这个代码块的对象;
2.方法:修饰的方法称为同步方法。锁住的是调用这个方法的对象。
3.静态方法:作用范围是关键字后面大括号里面的内容,锁住的整个类的对象;
4.类:作用范围是关键字后面的大括号,锁住的是整个类的对象。

重入锁:广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。
即线程在执行某个方法时已经持有了这个锁,那么线程在执行另一个方法时也持有该锁。首先我们来看看加锁方法lock的实现

23.同一个类里面两个synchronized方法,两个线程同时访问的问题
多个线程访问同一个类的synchronized方法时, 都是串行执行的 ! 就算有多个cpu也不例外 ! synchronized方法使用了类java的内置锁, 即锁住的是方法所属对象本身. 同一个锁某个时刻只能被一个执行线程所获取, 因此其他线程都得等待锁的释放. 因此就算你有多余的cpu可以执行, 但是你没有锁, 所以你还是不能进入synchronized方法执行, CPU因此而空闲. 如果某个线程长期持有一个竞争激烈的锁, 那么将导致其他线程都因等待所的释放而被挂起, 从而导致CPU无法得到利用, 系统吞吐量低下. 因此要尽量避免某个线程对锁的长期占有 !

24.你如何确保main()方法所在的线程是Java程序最后结束的线程?
我们可以使用 Thread 类的 join()方法来确保所有程序创建的线程在 main()方法退出前结束。
(join用来在一个线程里面对另一个线程进行调用)

25.谈谈volatile关键字的作用
主要为了共享内存安全,线程使用的变量会放入自己的缓存,一个变量被多个线程使用修改,如果各改各的,就会出现差错,所以需要将变量用volatile关键字修饰,这样就在使用这个变量前用预估值与实际值比对,不一样则修改。

1.将当前内核高速缓存行的数据立刻回写到内存;
2.使在其他内核里缓存了该内存地址的数据无效。当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,那么他会发出信号通知其他CPU将该变量的缓存行设置为无效状态。当其他CPU使用这个变量时,首先会去嗅探是否有对该变量更改的信号,当发现这个变量的缓存行已经无效时,会从新从内存中读取这个变量。

26.谈谈ThreadLocal关键字的作用
为了线程隔离。ThreadLocal有个内部类ThreadLocalMap,类似hashmap,每个线程给自己开辟一个map,用来存自己的变量,执行正常的put,set操作,在线程回收的时候,map间的强引用没有被回收,需要养成remove的好习惯

27.谈谈NIO的理解
图片说明
1、面向流与面向缓冲

Java IO和NIO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。

Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。

Java NIO面向缓冲区的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。

2、阻塞与非阻塞IO

Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。

Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。

3、选择器(Selectors)

Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。

28.什么是Callable和Future?
Callable接口类似于Runnable,从名字就可以看出来了,但是Runnable不会返回结果,并且无法抛出返回结果的异常,而Callable功能更强大一些,被线程执行后,可以返回值,这个返回值可以被Future拿到,也就是说,Future可以拿到异步执行任务的返回值,

29.ThreadLocal、synchronized 和volatile 关键字的区别
ThreadLocal:利用了Map,实现了线程隔离;
synchronized:最安全的同步机制,同步方法同步代码块,静态方法上加Synchronized是同步类,锁定的是类
volatile:可见性、有序性,关键字修饰的不代表完全原子性操作,及一个缓存变量在修改,另一个已经在主内存读取了

30.synchronized与Lock的区别
两者区别:

1.首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
2.synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
3.synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
4.用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
5.synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
6.Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。

31.ReentrantLock 、synchronized和volatile比较
ReentrantLock 、synchronized的不同:
1.前者属于jdk方法,属于java类,需要手动的解锁,如果忘记容易造成死锁,可以中断响应;
2.后者属于jvm方法,是一个关键字,可以自动释放锁;
3.前者具有更细的颗粒度,可以使用condition和signal来进行挂起和唤醒;
4.前者可以通过AQS实现公平锁。

1、可中断响应
一个正在等待获取的线程被“告知”无须继续等待下去,就可以停止工作了。
lockInterruptibly();//以可以响应中断的方式加锁
isHeldByCurrentThread();//获取指示当前哪个线程持有锁
2、锁限时操作
tryLock()或者tryLock(long timeout, TimeUtil unit) 进行一次限时的锁等待
前者不带参数,这时线程尝试获取锁,如果获取到锁则继续执行,如果锁被其他线程持有,则立即返回 false ,也就是不会使当前线程等待,所以不会产生死锁。 后者带有参数,表示在指定时长内获取到锁则继续执行,如果等待指定时长后还没有获取到锁则返回false。
3、公平锁
当锁处于无线程占有的状态,在其他线程抢占该锁的时候,都需要先进入队列中等待。
4、结合 Condition 使用
AQS 中的 Condition 配合 ReentrantLock 使用,实现了 wait/notify 的功能。

32.在Java中CycliBarriar和CountdownLatch有什么区别?

33.CopyOnWriteArrayList可以用于什么应用场景?
1.读读同时,读写同时,写写不同时
2.写的时候先拷贝副本,修改副本,这时候读的线程读的是原本
3.缺点是占用内存太大,数据一致性问题:没办法看到正在修改的数据,只能等修改结束更新到原数组才能看到。

34.ReentrantLock的内部实现
AQS+CAS
1.默认是非公平锁
2.采用AQS(AbstractQueuedSynchronizer)双向链表,判断锁状态State是否置0,1为忙碌。没有得到锁的进入链表尾部。
3.公平锁采用的是队列的先入先出,非公平锁采用的是不按顺序,直接竞争

35.lock原理
AbstractQueuedSynchronizer通过构造一个基于阻塞的CLH队列容纳所有的阻塞线程,而对该队列的操作均通过Lock-Free(CAS)操作,但对已经获得锁的线程而言,ReentrantLock实现了偏向锁的功能。

synchronized的底层也是一个基于CAS操作的等待队列,但JVM实现的更精细,把等待队列分为ContentionList和EntryList,目的是为了降低线程的出列速度;当然也实现了偏向锁,从数据结构来说二者设计没有本质区别。但synchronized还实现了自旋锁,并针对不同的系统和硬件体系进行了优化,而Lock则完全依靠系统阻塞挂起等待线程。

当然Lock比synchronized更适合在应用层扩展,可以继承AbstractQueuedSynchronizer定义各种实现,比如实现读写锁(ReadWriteLock),公平或不公平锁;同时,Lock对应的Condition也比wait/notify要方便的多、灵活的多。

36.Java中Semaphore是什么?
用来控制进入锁的线程数量,而上面的代码中,new Semaphore(6) 表示初始化了 6个通路,semaphore.acquire() + semaphore.release() 在运行的时候,其实和 semaphore.acquire(1) + semaphore.release(1) 效果是一样的。

37.Java中invokeAndWait 和 invokeLater有什么区别?
与invoikeLater一样,invokeAndWait也把可运行对象排入事件派发线程的队列中,invokeLater在把可运行的对象放入队列后就返回,而invokeAndWait一直等待知道已启动了可运行的run方法才返回。如果一个操作在另外一个操作执行之前必须从一个组件获得信息,则invokeAndWait方法是很有用的。(都是针对被阻塞的队列中的线程)

38.多线程中的忙循环是什么?
忙循环就是程序员用循环让一个线程等待,不像传统方法wait(), sleep() 或 yield() 它们都放弃了CPU控制,

而忙循环不会放弃CPU,它就是在运行一个空循环。目的是为了保留CPU缓存,在多核系统中,一个等待线程醒来的时候可能会在另一个内核运行,这样会重建缓存。

目的——避免重建缓存和减少等待重建的时间

39.怎么检测一个线程是否拥有锁?
在 java.lang.Thread 中有一个方法叫 holdsLock(),它返回 true 如果当且仅当当

前线程拥有某个具体对象的锁。

40.死锁的四个必要条件?
线程互斥、不剥夺、请求和保持、循环等待

41.对象锁和类锁是否会互相影响?
不是同一个东西,一个锁类的实例,一个锁类的对象。

42.什么是线程池,如何使用?
https://www.jianshu.com/p/7726c70cdc40
线程池的四个重要参数:
corepollsize(基本线程数):线程没有达到这个数即便里面有空闲线程,也会创建新的线程执行;
maximunpoolsize(最大线程数):线程池里面允许存在的最大线程数。如果队列满了,只要线程数还小于这个值,线程池就可以创建新的线程。如果队列误解,则省略这个值;
keepalivetime(线程保活时间):用来控制线程的消亡。如果一个线程的存货时间远远小于空闲时间,则这个线程就会被销毁,知道已经到了基本线程数;
workqueue(工作队列):用来传输和保存等待任务执行的阻塞队列;
threadfactory(线程工厂):线程工厂里面用new thread来创建新的线程,pool-m-thread-n即m号线程池n号线程
handle(线程饱和策略):当线程池里面的线程数达到了饱和,则执行这个策略。
图片说明

1、什么是线程池: java.util.concurrent.Executors提供了一个 java.util.concurrent.Executor接口的实现用于创建线程池
多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。
假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。
如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能。
一个线程池包括以下四个基本组成部分:
1、线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务 3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。
线程池不仅调整T1,T3产生的时间段,而且它还显著减少了创建线程的数目,看一个例子:
假设一个服务器一天要处理50000个请求,并且每个请求需要一个单独的线程完成。在线程池中,线程数一般是固定的,所以产生线程总数不会超过线程池中线程的数目,而如果服务器不利用线程池来处理这些请求则线程总数为50000。一般线程池大小是远小于50000。所以利用线程池的服务器程序不会为了创建50000而在处理请求时浪费时间,从而提高效率。

2.常见线程池
①newSingleThreadExecutor
单个线程的线程池,即线程池中每次只有一个线程工作,单线程串行执行任务
②newFixedThreadExecutor(n)
固定数量的线程池,没提交一个任务就是一个线程,直到达到线程池的最大数量,然后后面进入等待队列直到前面的任务完成才继续执行
③newCacheThreadExecutor(推荐使用)
可缓存线程池,当线程池大小超过了处理任务所需的线程,那么就会回收部分空闲(一般是60秒无执行)的线程,当有任务来时,又智能的添加新线程来执行。
④newScheduleThreadExecutor
大小无限制的线程池,支持定时和周期性的执行线程

43.Java线程池中submit() 和 execute()方法有什么区别?
1.参数不一样,submit(Callable<t> task)、submit(Runnable task, T result),execute(Runnable command)
2.继承的接口不一样,submit(Runnable task)归属于ExecutorService接口,execute(Runnable command)归属于Executor接口。ExecutorService继承了Executor。
3.submit可以通过future。get()来捕获异常
4.submit有返回值,execute没有</t>

44.Java中interrupted 和 isInterruptedd方法的区别?
interrupted()是静态方法:内部实现是调用的当前线程的isInterrupted(),并且会重置当前线程的中断状态

isInterrupted()是实例方法,是调用该方法的对象所表示的那个线程的isInterrupted(),不会重置当前线程的中断状态

45.用Java实现阻塞队列

46.BlockingQueue介绍:

47.多线程有什么要注意的问题?
并发问题,安全问题,效率问题。

48.如何保证多线程读写文件的安全?
用nio缓存io
在JAVA的标准I/O中,提供了基于流的I/O实现,即InputStream和OutputStream。这种基于流的实现以字节为单位处理数据。NIO是New I/O的简称,表示一套新的JAVA I/O标准。在Jdk 1.4中开始引入,它具有以下特性:
为所有的原始类型提供(Buffer)缓存支持;
使用Java.nio.charset.Charset作为字符集编码解码解决方案;
增加通道(Cahnnel)对象,作为新的原始I/O抽象;
支持锁和内存映射文件的文件访问接口;
提供了基于Selector的异步网络I/O。
与流式的I/O不容,NIO是基于块(Block)的,它以块为基本单位处理数据。在NIO中,最为重要的2个组件是缓冲Buffer和通道Channel。缓冲是一块连续的内存块,是NIO读写数据的中转地。通道表示缓冲数据的源头或者目的地,它用于向缓冲读取或者写入数据,是访问缓冲的接口。

49.多线程断点续传原理
首次传输其流程如下
1.服务端向客户端传递文件名称和文件长度。
2.跟据文件长度计算文件块数(文件分块传输请参照第二篇文章)。
3.客户端将传输的块数写入临时文件(做为断点值)。
4.若文件传输成功则删除临时文件。
首次传输失败后将按以下流程进行。
1.客户端从临时文件读取断点值并发送给服务端。
2.服务端与客户端将文件指针移至断点处(Java已经为我们提供了这样的一个类,那就是RandomAccessFile)
3.从断点处传输文件。

50.断点续传的实现

51.实现生产者消费者模式

52.Java中的ReadWriteLock是什么?

53.用Java写一个会导致死锁的程序,你将怎么解决?

54.SimpleDateFormat是线程安全的吗?
不安全,日期格式化输出类都不安全,可以使用ThreadLocal线程隔离,将共享变量变为私有变量

55.Java中的同步集合与并发集合有什么区别?

56.Java中ConcurrentHashMap的并发度是什么?
ConcurrentHashMap 把实际 map 划分成若干部分来实现它的可扩展性和线程安全。这种划分是使用并发度获得的,它是 ConcurrentHashMap 类构造函数的一个可选参数,默认值为 16,这样在多线程情况下就能避免争用。在 JDK8 后,它摒弃了 Segment(锁段)的概念,而是启用了一种全新的方式实现,利用 CAS 算法。同时加入了更多的辅助变量来提高并发度,具体内容还是查看源码吧。

57.什么是Java Timer类?如何创建一个有特定时间间隔的任务?
java.util.Timer是一个工具类,可以用于安排一个线程在未来的某个特定时间执行zhi。Timer类可以用安排一次性任务或者周期任务。
java.util.TimerTask是一个实现了Runnable接口的抽象类,需要去继承这个类来创建自己的定时任务并使用Timer去安排它的执行。