java并发——线程(1)

首先是网上摘抄的关于线程的面试题目,答案待完善。

(1)什么是线程。

一个程序同时执行多个任务,每个任务称为一个线程。

线程是操作系统能够进行运算调度的最小单位,他被包含在进程之中,是进程中的实际运作单位。程序员可以通过它进行多线程处理编程,你可以 使用多线程对运算密集型任务提速。比如,如果一个线程完成一个任务要100ms,那么用十个线程完成该任务只需要10ms.Java在语言层面对多线程提供了卓越的支持。

(2)进程和线程的区别。

线程是进程的子集,一个进程可以有多个线程,每个线程并行执行不同的任务,不同的进程使用不同的本地空间,而所有的线程共享一片相同的内存空间。与占栈内存不同,每个线程都有单独的栈内存用来存储本地数据。与进程相比,线程更轻量级,创建,撤销一个线程比启动新进程的开销要小的多。

(3)如何在Java中实现线程

继承Thread类,实现Runnable接口,实现Callable接口。

事项Runnable,Callable接口的类只能当作一个可以在线程中运行的任务,不是真正意义上的线程,因此最后还需要通过Thread来调用,可以输任务是通过线程驱动而执行的。

(4)使用Runnable还是Thread

java不支持类的多重继承但允许调用多个接口,如果奥继承其他类就要调用Runnable接口;类可能只要求可执行,继承整个Thread类开销过大,实现接口的方式还能降低耦合度。

(5)Thread类中的start()和run()方法区别

start()方法被用来启动新创建的线程,而且start内部调用了run方法,这和直接调用run方法效果不一样,当你调用run方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。

如果执行耗时任务,直接调用run方法,主线程将在执行耗时任务时卡住

你不能在线程对象上调用两次start,一旦启动,第二次调用start将在java中抛出illegalStateException,然而可以调用run方法两次。

(6)Java中Runnable和Callable有什么不同?

Runnable和Callable都代表那些要在不同的线程中执行的任务。

1)Runnable从JDK1.0就有了,Callable是在JDK1.5增加的。

2)Runnable接口有run()方法定义任务,而Callable接口使用call()方法定义任务。

3)run方法不返回任何值,不能抛出checked异常,call方法可以有返回值和抛出异常,Callable接口是通用参数化接口,在创建Callable实现的实例时提供值的Type 。Callable可以返回装载有计算结果的Future对象。

(7)Java中CyclicBarrier 和 CountDownLatch有什么不同?

二者都可以用来让一组线程等待其他线程。都是通过维护计数器实现的,线程执行await()方法之后计数器会减一,并进行等待,直到计数器为0,所有调用await的方法而等待的线程能继续执行,与CyclicBarrier不同的是,后者不能重新使用,前者可以通过调用reset方法循环使用,所以叫循环屏障。

(8)Java内存模型是什么?

java内存模型试图屏蔽各种硬件和操作系统的内存访问差异,以实现让java程序在各种平台下都能达到一致的内存访问效果。

处理器上的寄存器读写速度比内存快好几个数量级,为了解决这种速度矛盾,在他们之间加入了高缓。但是国歌高缓如果共享一块内存区域,那么其数据可能会不一致,需要一些协议解决这个问题。所有变量都存储在主存中,每个线程还有自己的工作内存,工作内存存储在高欢或者寄存器中,保存了该线程使用的变量的主内存副本拷贝。线程只能操作工作内存中的变量,不同线程之间的变量值传递需要通过主存来完成。

内存间的交互操作如下:

内存模型三大特性:

1.原子性:Java 内存模型保证了 read、load、use、assign、store、write、lock 和 unlock 操作具有原子性。

2.可见性:可见性指当一个线程修改了共享变量的值,其它线程能够立即得知这个修改。Java 内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值来实现可见性的。

3.有序性:在本线程内观察,所有操作都是有序的。在一个线程观察另一个线程,所有操作都是无序的,无序是因为发生了指令重排序。在 Java 内存模型中,允许编译器和处理器对指令进行重排序,重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。

先行发生原则:让一个操作无需控制就能嫌疑另一个操作完成。

1)单一线程原则:在一个线程内,在程序前面的操作先行发生于后面的操作;

2)管程锁定规则:一个 unlock 操作先行发生于后面对同一个锁的 lock 操作;

3)volatile变量规则:对一个 volatile 变量的写操作先行发生于后面对这个变量的读操作;

4)线程启动规则:Thread 对象的 start() 方法调用先行发生于此线程的每一个动作;

5)线程加入规则:Thread 对象的结束先行发生于 join() 方法返回;

6)线程中断规则:对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过 interrupted() 方法检测到是否有中断发生;

7)对象终结规则:一个对象的初始化完成(构造函数执行结束)先行发生于它的 finalize() 方法的开始。;

8)传递性:如果操作 A 先行发生于操作 B,操作 B 先行发生于操作 C,那么操作 A 先行发生于操作 C。

(9)Java中的volatile 变量是什么?

volatile是一个特殊的修饰符,只有成员变量才能使用它,保证了有序性和可见性。

什么时候使用:1)如果要原子读取和写入long,double变量,可以使用volatile变量。因为他俩是64位数据类型,默认情况下,long和double的写入不是源自的并且平台依赖。许多平台都是2步写入,因此可能一个线程会从两个不同的写中看到32位。volatile避免此问题的发生;2)某些情况下,使用volatile变量作为java中实现同步的替代方法,保证所有读线程一旦写入操作完成就会看到volatile变量的个性之,没有这个福安尖子不同的读线程可能会看到不同的值;3)volatile变量可用于通知编译器特定字段可有多个线程访问。

(10)什么是线程安全?Vector是一个线程安全类吗?

如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。一个线程安全的计数器类的同一个实例对象在被多个线程使用的情况下也不会出现计算失误。很显然你可以将集合类分成两组,线程安全和非线程安全的。Vector 是用同步方法来实现线程安全的, 而和它相似的ArrayList不是线程安全的。

(11)Java中什么是竞态条件? 举个例子说明。

竞态条件是一种并发错误或问题,因为多个线程并行执行某个程序,多线程对一些资源的竞争的时候就会产生竞态条件,如果首先要执行的程序竞争失败排到后面执行了,那么整个程序就会出现一些不确定的bugs。这种bugs很难发现而且会重复出现,因为线程间的随机竞争。

竞态条件的经典示例是递增计数器,因为增量不是原子操作,可以进一步分为三个步骤,如读取,更新和写入。如果两个线程同时尝试递增计数,并且如果由于一个线程的读操作交错到另一个线程的更新操作而读取相同的值,则当其他线程完成一个线程覆盖增量时,将丢失一个计数。原子操作不受竞争条件的影响,因为这些操作不能交错。

(12)Java中如何停止一个线程?

Java提供了很丰富的API但没有为停止线程提供API。JDK 1.0本来有一些像stop(), suspend() 和 resume()的控制方法但是由于潜在的死锁威胁因此在后续的JDK版本中他们被弃用了,之后Java API的设计者就没有提供一个兼容且线程安全的方法来停止一个线程。当run() 或者 call() 方法执行完的时候线程会自动结束,如果要手动结束一个线程,你可以用volatile 布尔变量来退出run()方法的循环或者是取消任务来中断线程。

(13)一个线程运行时发生异常会怎样?

如果异常没有被捕获该线程将会停止执行。Thread.UncaughtExceptionHandler是用于处理未捕获异常造成线程突然中断情况的一个内嵌接口。当一个未捕获异常将造成线程中断的时候JVM会使用Thread.getUncaughtExceptionHandler()来查询线程的UncaughtExceptionHandler并将线程和异常作为参数传递给handler的uncaughtException()方法进行处理。

(14)如何在两个线程间共享数据?

使用共享对象或者阻塞队列这样并发的数据结构

(15)Java中notify 和 notifyAll有什么区别?

当你调用notify时,只有一个等待线程会被唤醒而且它不能保证哪个线程会被唤醒,这取决于线程调度器。虽然如果你调用notifyAll方法,那么等待该锁的所有线程都会被唤醒,但是在执行剩余代码之前,所有被唤醒的线程都将争夺锁,这就是为什么在循环上调用wait,因为如果多个线程被唤醒,那么获得锁的线程首先执行,它可能会重置等待条件,这将迫使后续线程等待。因此,notify和notifyAll之间的关键区别在于notify()只会唤醒一个线程,而notifyAll方法会唤醒所有线程。

(16)为什么wait, notify 和 notifyAll这些方法不在thread类里面?

回答这些问题的时候,你要说明为什么把这些方法放在Object类里是有意义的,还有不把它放在Thread类里的原因。一个很明显的原因是JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。

(17)什么是ThreadLocal变量?

ThreadLocal是Java里一种特殊的变量。每个线程都有一个ThreadLocal就是每个线程都拥有了自己独立的一个变量,竞争条件被彻底消除了。它是为创建代价高昂的对象获取线程安全的好方法,比如你可以用ThreadLocal让SimpleDateFormat变成线程安全的,因为那个类创建代价高昂且每次调用都需要创建不同的实例所以不值得在局部范围使用它,如果为每个线程提供一个自己独有的变量拷贝,将大大提高效率。首先,通过复用减少了代价高昂的对象的创建个数。其次,你在没有使用高代价的同步或者不变性的情况下获得了线程安全。线程局部变量的另一个不错的例子是ThreadLocalRandom类,它在多线程环境中减少了创建代价高昂的Random对象的个数。

(18)什么是FutureTask?

在Java并发程序中FutureTask表示一个可以取消的异步运算。它有启动和取消运算、查询运算是否完成和取回运算结果等方法。只有当运算完成的时候结果才能取回,如果运算尚未完成get方法将会阻塞。一个FutureTask对象可以对调用了Callable和Runnable的对象进行包装,由于FutureTask也是调用了Runnable接口所以它可以提交给Executor来执行。当一个计算任务需要执行很长时间,那么就可以用 FutureTask 来封装这个任务,主线程在完成自己的任务之后再去获取结果。

(19)Java中interrupted 和 isInterrupted方法的区别?

interrupted() 和 isInterrupted()的主要区别是前者会将中断状态清除而后者不会。Java多线程的中断机制是用内部标识来实现的,调用Thread.interrupt()来中断一个线程就会设置中断标识为true。当中断线程调用静态方法Thread.interrupted()来检查中断状态时,中断状态会被清零。而非静态方法isInterrupted()用来查询其它线程的中断状态且不会改变中断状态标识。简单的说就是任何抛出InterruptedException异常的方法都会将中断状态清零。无论如何,一个线程的中断状态有有可能被其它线程调用中断来改变。

(20)为什么wait和notify方法要在同步块中调用?

主要是因为Java API强制要求这样做,如果你不这么做,你的代码会抛出IllegalMonitorStateException异常。还有一个原因是为了避免wait和notify之间产生竞态条件。

(21)为什么你应该在循环中检查等待条件?

处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。因此,当一个等待线程醒来时,不能认为它原来的等待状态仍然是有效的,在notify()方法调用之后和等待线程醒来之前这段时间它可能会改变。这就是在循环中使用wait()方法效果更好的原因。

(22)Java中的同步集合与并发集合有什么区别?

同步集合与并发集合都为多线程和并发提供了合适的线程安全的集合,不过并发集合的可扩展性更高。在Java1.5之前程序员们只有同步集合来用且在多线程并发的时候会导致争用,阻碍了系统的扩展性。Java5介绍了并发集合像ConcurrentHashMap,不仅提供线程安全还用锁分离和内部分区等现代技术提高了可扩展性。

(23)Java中堆和栈有什么不同?

栈是一块和线程紧密相关的内存区域。每个线程都有自己的栈内存,用于存储本地变量,方法参数和栈调用,一个线程中存储的变量对其它线程是不可见的。而堆是所有线程共享的一片公用内存区域。对象都在堆里创建,为了提升效率线程会从堆中弄一个缓存到自己的栈,如果多个线程使用该变量就可能引发问题,这时volatile 变量就可以发挥作用了,它要求线程从主存中读取变量的值。

(24)什么是线程池? 为什么要使用它?

创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。从JDK1.5开始,Java API提供了Executor框架让你可以创建不同的线程池。

Executor 管理多个异步任务的执行,而无需程序员显式地管理线程的生命周期。这里的异步是指多个任务的执行互不干扰,不需要进行同步操作。

主要有三种 Executor:

  • CachedThreadPool:一个任务创建一个线程;
  • FixedThreadPool:所有任务只能使用固定大小的线程;
  • SingleThreadExecutor:相当于大小为 1 的 FixedThreadPool。

(25)如何写代码来解决生产者消费者问题?

比较低级的办法是用wait和notify来解决这个问题,比较赞的办法是用Semaphore 或者 BlockingQueue来实现生产者消费者模型。Semaphore类似于操作系统中的信号量,可以控制对互斥资源的访问线程数。下图表示Semaphore模拟对某个服务的并发请求,每次只能由2个客户端访问,一共有4个请求。

                                           

BlockingQueue接口有以下阻塞队列的实现:

FIFO队列:LinkedBlockingQueue.ArrayBlockingQueue(固定长度)

优先级队列:PriorityBlockingQueue

提供了阻塞的take()和put()方法:如果队列为空take()将阻塞,直到队列中有内容;如果队列为满put()将阻塞,直到队列有空闲位置。

(26)如何避免死锁?
死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。这是一个严重的问题,因为死锁会让你的程序挂起无法完成任务,死锁的发生必须满足以下四个条件:

  • 互斥条件:一个资源每次只能被一个进程使用。
  • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  • 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

避免死锁最简单的方法就是阻止循环等待条件,将系统中所有的资源设置标志位、排序,规定所有的进程申请资源必须以一定的顺序(升序或降序)做操作来避免死锁

(27)Java中活锁和死锁有什么区别?

活锁和死锁类似,不同之处在于处于活锁的线程或进程的状态是不断改变的,活锁可以认为是一种特殊的饥饿。一个现实的活锁例子是两个人在狭小的走廊碰到,两个人都试着避让对方好让彼此通过,但是因为避让的方向都一样导致最后谁都不能通过走廊。简单的说就是,活锁和死锁的主要区别是前者进程的状态可以改变但是却不能继续执行。

(28) 怎么检测一个线程是否拥有锁?

在java.lang.Thread中有一个方法叫holdsLock(),它返回true当且仅当当前线程拥有某个具体对象的锁。

(29)你如何在Java中获取线程堆栈?

对于不同的操作系统,有多种方法来获得Java进程的线程堆栈。当你获取线程堆栈时,JVM会把所有线程的状态存到日志文件或者输出到控制台。在Windows你可以使用Ctrl + Break组合键来获取线程堆栈,Linux下用kill -3命令。你也可以用jstack这个工具来获取,它对线程id进行操作,你可以用jps这个工具找到id。

(30)JVM中哪个参数是用来控制线程的栈堆栈小的?

-Xss参数用来控制线程的堆栈大小。

(31)Java中synchronized 和 ReentrantLock 有什么不同?

Java在过去很长一段时间只能通过synchronized关键字来实现互斥,它有一些缺点。比如你不能扩展锁之外的方法或者块边界,尝试获取锁时不能中途取消等。Java 5 通过Lock接口提供了更复杂的控制来解决这些问题。 ReentrantLock 类实现了 Lock,它拥有与 synchronized 相同的并发性和内存语义且它还具有可扩展性。

1)锁的实现:synchronized是JVM实现的,而ReentrantLock是JDK实现的。

2)性能:新版本Java对synchronized进行了很多优化,例如自旋锁等,二者大致相同。

3)等待可中断:当持有锁的线程长期不能释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情。ReentrantLock可中断,而synchronized不行。

4)公平锁:公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来一次获得锁。

synchronized中的锁时非公平的,ReentrantLock默认情况下也是非公平的,但是也可以是公平的。

5)锁绑定多个条件:一个ReentrantLock可以同时绑定多个Condition对象。

除非需要使用 ReentrantLock 的高级功能,否则优先使用 synchronized。这是因为 synchronized 是 JVM 实现的一种锁机制,JVM 原生地支持它,而 ReentrantLock 不是所有的 JDK 版本都支持。并且使用 synchronized 不用担心没有释放锁而导致死锁问题,因为 JVM 会确保锁的释放。

(32) 有三个线程T1,T2,T3,怎么确保它们按顺序执行?

在多线程中有多种方法让线程按特定顺序执行,你可以用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成。

join()在线程中调用另一个线程的 join() 方法,会将当前线程挂起,而不是忙等待,直到目标线程结束。

(33)Thread类中的yield方法有什么作用?

Yield方法可以暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。它是一个静态方法,对其的调用声明了当前线程已经完成了生命周期中最重要的部分,可以切换给其他线程来执行,只是对线程调度器的一个建议,只保证当前线程放弃CPU占用而不能保证使其它线程一定能占用CPU,执行yield()的线程有可能在进入到暂停状态后马上又被执行。(Thread.yield()).

(34) Java中ConcurrentHashMap的并发度是什么?

ConcurrentHashMap把实际map划分成若干部分来实现它的可扩展性和线程安全。这种划分是使用并发度获得的,它是ConcurrentHashMap类构造函数的一个可选参数,默认值为16,这样在多线程情况下就能避免争用。

(35) Java中Semaphore是什么?

Java中的Semaphore是一种新的同步类,它是一个计数信号。从概念上讲,信号量维护了一个许可集合。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release()添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore只对可用许可的号码进行计数,并采取相应的行动。信号量常常用于多线程的代码中,比如数据库连接池。

(36)如果你提交任务时,线程池队列已满。会时发会生什么?

这个问题问得很狡猾,许多程序员会认为该任务会阻塞直到线程池队列有空位。事实上如果一个任务不能被调度执行那么ThreadPoolExecutor’s submit()方法将会抛出一个RejectedExecutionException异常。

(37)Java线程池中submit() 和 execute()方法有什么区别?

两个方法都可以向线程池提交任务,execute()方法的返回类型是void,它定义在Executor接口中, 而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,其它线程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法。如果提交的任务不需要一个结果的话直接用execute()会提升很多性能。

(38)什么是阻塞式方法?

阻塞式方法是指程序会一直等待该方法完成期间不做其他事情,ServerSocket的accept()方法就是一直等待客户端连接。这里的阻塞是指调用结果返回之前,当前线程会被挂起,直到得到结果之后才会返回。此外,还有异步和非阻塞式方法在任务完成前就返回。

(39)Swing是线程安全的吗? 为什么?

不是。当我们说swing不是线程安全的常常提到它的组件,这些组件不能在多线程中进行修改,所有对GUI组件的更新都要在AWT线程中完成,而Swing提供了同步和异步两种回调方法来进行更新。

(40)Java中invokeAndWait 和 invokeLater有什么区别?

这两个方法是Swing API 提供给Java开发者用来从当前线程而不是事件派发线程更新GUI组件用的。InvokeAndWait()同步更新GUI组件,比如一个进度条,一旦进度更新了,进度条也要做出相应改变。如果进度被多个线程跟踪,那么就调用invokeAndWait()方法请求事件派发线程对组件进行相应更新。而invokeLater()方法是异步调用更新组件的。

(41)Swing API中那些方法是线程安全的?

虽然组件不是线程安全的但是有一些方法是可以被多线程安全调用的,比如repaint(), revalidate()。 JTextComponent的setText()方法和JTextArea的insert() 和 append() 方法也是线程安全的。

(42)如何在Java中创建Immutable对象?

Immutable(不可变类)对象可以在没有同步的情况下共享,降低了对该对象进行并发访问时的同步化开销。可是Java没有@Immutable这个注解符,要创建不可变类,要实现下面几个步骤:通过构造方法初始化所有成员、对变量不要提供setter方法、将所有的成员声明为私有的,这样就不允许直接访问这些成员、在getter方法中,不要直接返回对象本身,而是克隆对象,并返回对象的拷贝。

(43)Java中的ReadWriteLock是什么?

一般而言,读写锁是用来提升并发程序性能的锁分离技术的成果。Java中的ReadWriteLock是Java 5 中新增的一个接口,一个ReadWriteLock维护一对关联的锁,一个用于只读操作一个用于写。在没有写线程的情况下一个读锁可能会同时被多个读线程持有。写锁是独占的,你可以使用JDK中的ReentrantReadWriteLock来实现这个规则,它最多支持65535个写锁和65535个读锁。

(44)多线程中的忙循环是什么?

忙循环就是程序员用循环让一个线程等待,不像传统方法wait(), sleep() 或 yield() 它们都放弃了CPU控制,而忙循环不会放弃CPU,它就是在运行一个空循环。这么做的目的是为了保留CPU缓存,在多核系统中,一个等待线程醒来的时候可能会在另一个内核运行,这样会重建缓存。为了避免重建缓存和减少等待重建的时间就可以使用它了。

(45)volatile 变量和 atomic 变量有什么不同?

首先,volatile 变量和 atomic 变量看起来很像,但功能却不一样。Volatile变量可以确保先行关系,即写操作会发生在后续的读操作之前, 但它并不能保证原子性。例如用volatile修饰count变量那么 count++ 操作就不是原子性的。而AtomicInteger类提供的atomic方法可以让这种操作具有原子性如getAndIncrement()方法会原子性的进行增量操作把当前值加一,其它数据类型和引用变量也可以进行相似操作。

(46)如果同步块内的线程抛出异常会发生什么?

无论你的同步块是正常还是异常退出的,里面的线程都会释放锁,所以对比锁接口我更喜欢同步块,因为它不用我花费精力去释放锁,该功能可以在finally block里释放锁实现。

(47)单例模式的双检锁是什么?

public class DoubleCheckedLock {
    private static DoubleCheckedLock instance;  
	  
    public static DoubleCheckedLock getInstance() {  
        if (instance == null) {  
        	synchronized (DoubleCheckedLock.class) { 
        		if(instance==null){ 
        			instance=new DoubleCheckedLock(); 
        		}
        	}
        }  
        return instance;  
    }  
}

(48)如何在Java中创建线程安全的Singleton?

可以利用JVM的类加载和静态变量初始化特征来创建Singleton实例,或者是利用枚举类型来创建Singleton。

 public enum Singleton{
    INSTANCE;
    public void show(){
        System.out.println("Singleton using Enum in Java");
    }
}
//You can access this Singleton as Singleton.INSTANCE and call any method like below
Singleton.INSTANCE.show();

(49)写出3条你遵循的多线程最佳实践。

  • 给线程起个有意义的名字,这样可以方便找 Bug。

  • 缩小同步范围,从而减少锁争用。例如对于 synchronized,应该尽量使用同步块而不是同步方法。

  • 多用同步工具少用 wait() 和 notify()。首先,CountDownLatch, CyclicBarrier, Semaphore 和 Exchanger 这些同步类简化了编码操作,而用 wait() 和 notify() 很难实现复杂控制流;其次,这些同步类是由最好的企业编写和维护,在后续的 JDK 中还会不断优化和完善。

  • 使用 BlockingQueue 实现生产者消费者问题。

  • 多用并发集合少用同步集合,例如应该使用 ConcurrentHashMap 而不是 Hashtable。

  • 使用本地变量和不可变类来保证线程安全。

  • 使用线程池而不是直接创建线程,这是因为创建线程代价很高,线程池可以有效地利用有限的线程来启动任务。

(50)如何强制启动一个线程?

不知道。

(51)Java中的fork join框架是什么?

主要用于并行计算中,和 MapReduce 原理类似,都是把大的计算任务拆分成多个小任务并行计算。fork join框架一个巨大的优势是它使用了工作窃取算法,可以完成更多任务的工作线程可以从其它线程中窃取任务来执行。ForkJoin 使用 ForkJoinPool 来启动,它是一个特殊的线程池,线程数量取决于 CPU 核数。

ForkJoinPool 实现了工作窃取算法来提高 CPU 的利用率。每个线程都维护了一个双端队列,用来存储需要执行的任务。工作窃取算法允许空闲的线程从其它线程的双端队列中窃取一个任务来执行。窃取的任务必须是最晚的任务,避免和队列所属线程发生竞争。例如下图中,Thread2 从 Thread1 的队列中拿出最晚的 Task1 任务,Thread1 会拿出 Task2 来执行,这样就避免发生竞争。但是如果队列中只有一个任务时还是会发生竞争。

(52)Java多线程中调用wait() 和 sleep()方法有什么不同?

  • wait() 是 Object 的方法用于线程间通信,而 sleep() 是 Thread 的静态方法;
  • wait() 如果等待条件为真且其他线程被唤醒时他会释放锁,sleep() 仅仅释放CPU资源或者让当前线程停止执行一段时间,但不会释放锁。
发布了25 篇原创文章 · 获赞 1 · 访问量 7526

猜你喜欢

转载自blog.csdn.net/qq_28334237/article/details/85217600