Java多线程分类这个知识点网上内容很是繁杂。我个人认为,学习越多、越杂的知识,就越需要时时进行总结,这样才能记忆深刻,将知识变成自己的。下面这篇文章我就主要针对多线程的问题进行一个概括性的总结。
1、多线程有什么用?
如果你的想法还停留在我会用多线程就好了,那么你永远也不会真正的使用它。所谓"知其然也要知其所以然","会用"只是"知其然","为什么用"才是"知其所以然",只有达到"知其所以然"的程度才可以说是把这个知识点运用自如。
2、创建多线程的方式
两种方式:
(1)继承Thread类
(2)实现Runnable接口
3、start()方法和run()方法的区别
只有调用了start()方法,才会表现出多线程的特性,不同线程的run()方法里面的代码交替执行。如果只是调用run()方法,那么代码还是同步执行的,必须等待一个线程的run()方法里面的代码全部执行完毕之后,另外一个线程才可以执行其run()方法里面的代码。
4、Runnable接口和Callable接口的区别
这个问题可以看出一个Java程序员学习知识的广度。Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。
5、CyclicBarrier和CountDownLatch的区别
两个类都在java.util.concurrent下,都可以用来表示代码运行到某个点上,区别在于:
(1)CyclicBarrier的某个线程运行到某个点上之后,该线程即停止运行,直到所有的线程都到达了这个点,所有线程才重新运行;CountDownLatch则不是,某线程运行到某个点上之后,只是给某个数值-1而已,该线程继续运行
(2)CyclicBarrier只能唤起一个任务,CountDownLatch可以唤起多个任务
(3)CyclicBarrier可重用,CountDownLatch不可重用,计数值为0该CountDownLatch就不可再用了
6、volatile关键字的作用
这个问题是每个学习、应用多线程的Java程序员都必须掌握的。理解volatile关键字的作用的前提是要理解Java内存模型。volatile关键字的作用主要有两个:
(1)多线程主要围绕可见性和原子性两个特性而展开,使用volatile关键字修饰的变量,保证了其在多线程之间的可见性,即每次读取到volatile变量,一定是最新的数据
(2)代码底层的执行是Java代码-->字节码-->根据字节码执行对应的C/C++代码-->C/C++代码被编译成汇编语言-->和硬件电路交互。现实中,为了获取更好的性能JVM可能会对指令进行重排序,多线程下可能会出现一些意想不到的问题。使用volatile则会对禁止语义重排序,当然这也一定程度上降低了代码执行效率。
7、什么是线程安全
(1)不可变
像String、Integer、Long这些,都是final类型的类,任何一个线程都改变不了它们的值,要改变除非新创建一个,因此这些不可变对象不需要任何同步手段就可以直接在多线程环境下使用。
(2)绝对线程安全
不管运行时环境如何,调用者都不需要额外的同步措施。Java中标注自己是线程安全的类,实际上绝大多数都不是线程安全的。Java中绝对线程安全的类:CopyOnWriteArrayList、CopyOnWriteArraySet
(3)相对线程安全
相对线程安全也就是我们通常意义上所说的线程安全,像Vector这种,add、remove方法都是原子操作,不会被打断,但也仅限于此。如果有个线程在遍历某个Vector、有个线程同时在add这个Vector,99%的情况下都会出现ConcurrentModificationException,也就是fail-fast机制。
(4)线程非安全
ArrayList、LinkedList、HashMap等都是线程非安全的类
8、一个线程如果出现了运行时异常会怎么样
如果这个异常没有被捕获的话,这个线程就停止执行了。另外重要的一点是:如果这个线程持有某个某个对象的监视器,那么这个对象监视器会被立即释放。
9、Java中如何获取到线程dump文件
遇到死循环、死锁、阻塞、页面打开慢等问题,打线程dump是最好的解决问题途径。所谓线程dump也就是线程堆栈,获取到线程堆栈有两步:
(1)获取到线程的pid,可以通过使用jps命令,在Linux环境下还可以使用ps -ef | grep java
(2)打印线程堆栈,可以通过使用jstack pid命令,在Linux环境下还可以使用kill -3 pid
另外提一点,Thread类提供了一个getStackTrace()方法也可以用于获取线程堆栈。这是一个实例方法,此方法是和具体线程实例绑定的,每次获取获取到的是具体某个线程当前运行的堆栈,
10、如何在两个线程之间共享数据
通过在线程之间共享对象就可以了,然后通过wait/notify/notifyAll、await/signal/signalAll进行唤起和等待。
11、ThreadLocal有什么用
简单说ThreadLocal就是一种以空间换时间的做法,在每个Thread里面维护了一个以开地址法实现的ThreadLocal.ThreadLocalMap,把数据进行隔离,数据不共享,自然就没有线程安全方面的问题了。
12、sleep方法和wait方法有什么区别
相同在于sleep方法和wait方法都可以用来放弃CPU一定的时间。不同点在于如果线程持有某个对象的监视器,sleep方法不会放弃这个对象的监视器,wait方法会放弃这个对象的监视器。
13、为什么要使用线程池
线程池可以避免频繁地创建和销毁线程,达到线程对象的重用。使用线程池还可以根据项目灵活地控制并发的数目。
14、怎么检测一个线程是否持有对象监视器
判断某个线程是否持有对象监视器:Thread类提供了一个holdsLock(Object obj)方法,当且仅当对象obj的监视器被某条线程持有的时候才会返回true,这是一个static方法,这意味着"某条线程"指的是当前线程。
15、FutureTask是什么
FutureTask表示一个异步运算的任务。FutureTask里面可以传入一个Callable的具体实现类,可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。由于FutureTask也是Runnable接口的实现类,因此FutureTask也可以放入线程池中。
16、怎么唤醒一个阻塞的线程
如果线程是因为调用了wait()、sleep()或者join()方法而导致的阻塞,可以中断线程,并且通过抛出InterruptedException来唤醒它;如果线程遇到了IO阻塞,则没有办法解决,因为IO是操作系统实现的,Java代码并没有办法直接接触到操作系统。
17、同步方法和同步块,谁是更好的选择
同步块,这意味着同步块之外的代码是异步执行的,这比同步整个方法更提升代码的效率。请知道一条原则:同步的范围越小越好。
本文出自 “风也不知道往哪吹” 博客,请务必保留此出处http://12652057.blog.51cto.com/12642057/1902711
原文:http://12652057.blog.51cto.com/12642057/1902711