Java 中关于多线程的知识点
👨🎓作者:Java 学术趴
💌公众号:Java 学术趴
🚫特别声明:原创不易,未经授权不得转载或抄袭,如需转载可联系小编授权。
🙏版权声明:文章里的部分文字或者图片来自于互联网以及百度百科,如有侵权请尽快联系小编。微信搜索公众号 Java 学术趴联系小编。
☠️每日毒鸡汤:这个社会是存在不公平的,不要抱怨,因为没有用!人总是在反省中进步的!
👋大家好!我是你们的老朋友 Java 学术趴。
Java 多线程
1. Java 实现多线程的几种方式
继承 Thread 类
实现 Runnable 接口
实现 Callable 接口( JDK1.5>= )
线程池方式创建
采用实现 Runnable、Callable 接口的方式创建线程的优缺点
优点:线程类只是实现了 Runnable 或者 Callable 接口,还可以继承其他类。这种方式下,多个线程可以共享一个 target 对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将 CPU、代码和数据分开,形成清晰的模型,较好的体现了面向对象的思想。缺点:编程稍微复杂一些,如果需要访问当前线程,则必须使用 Thread.currentThread() 方法
采用继承 Thread 类的方式创建线程的优缺点
优点:编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获取当前线程缺点:因为线程类已经继承了 Thread 类,Java 语言是单继承的,所以就不能再继承其他父类了。
2. 如何终止一个正在运行的线程
1、使用退出标志,使线程正常退出,也就是当 run 方法完成后线程终止。2、使用 stop 方法强行终止,但是不推荐这个方法,因为 stop 和 suspend 及 resume 一样都是过期作废的方法。3、使用 interrupt 方法中断线程。
3. notify()和 notifyAll()有什么区别?
notify 可能会导致死锁,而 notifyAll 则不会
任何时候只有一个线程可以获得锁,也就是说只有一个线程可以运行 synchronized 中的代码使用 notifyall,可以唤醒 所有处于 wait 状态的线程,使其重新进入锁的争夺队列中,而 notify 只能唤醒一个。
wait() 应配合 while 循环使用,不应使用 if,务必在 wait()调用前后都检查条件,如果不满足,必须调用 notify()唤醒另外的线程来处理,自己继续 wait()直至条件满足再往下执行。
notify() 是对 notifyAll()的一个优化,但它有很精确的应用场景,并且要求正确使用。不然可能导致死锁。正确的场景应该是 WaitSet 中等待的是相同的条件,唤醒任一个都能正确处理接下来的事项,如果唤醒的线程无法正确处理,务必确保继续 notify()下一个线程,并且自身需要重新回到 WaitSet 中.
4. sleep()和 wait() 有什么区别?
对于 sleep()方法,我们首先要知道该方法是属于 Thread 类中的。而 wait()方法,则是属于 Object 类中的。
sleep()方法导致了程序暂停执行指定的时间,让出 cpu 该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。在调用 sleep()方法的过程中,线程不会释放对象锁。
当调用 wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用 notify()方法后本线程才进入对象锁定池准备,获取对象锁进入运行状态。
5. volatile 是什么?可以保证有序性吗?
一旦一个共享变量(类的成员变量、类的静态成员变量)被 volatile 修饰之后,那么就具备了两层语义:
保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的,volatile 关键字会强制将修改的值立即写入主存。
禁止进行指令重排序。
volatile 不是原子性操作
6. Thread 类中的 start() 和 run() 方法有什么区别?
start()方法被用来启动新创建的线程,而且 start()内部调用了 run()方法,这和直接调用 run()方法的效果不一样。当你调用 run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。
7. 为什么 wait, notify 和 notifyAll 这些方法不在 thread 类里面?
明显的原因是 JAVA 提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象中的 wait()方法就有意义了。如果 wait()方法定义在 Thread 类中,线程正在等待的是哪个锁就不明显了。简单的说,由于 wait,notify 和 notifyAll 都是锁级别的操作,所以把他们定义在 Object 类中因为锁属于对象。
8. 为什么 wait 和 notify 方法要在同步块中调用?
只有在调用线程拥有某个对象的独占锁时,才能够调用该对象的 wait(),notify()和 notifyAll()方法。
如果你不这么做,你的代码会抛出 IllegalMonitorStateException 异常。
还有一个原因是为了避免 wait 和 notify 之间产生竞态条件。wait()方法强制当前线程释放对象锁。这意味着在调用某对象的 wait()方法之前,当前线程必须经获得该对象的锁。因此,线程必须在某个对象的同步方法或同步代码块中才能调用该对象的 wait(方法。在调用对象的 notify()和 notifyAll()方法之前,调用线程必须已经得到该对象的锁。因此,必须在某个对象的同步方法或同步代码块中才能调用该对象的 notify()或 notifyAll()方法。调用 wait()方法的原因通常是,调用线程希望某个特殊的状态(或变量)被设置之后再继续执行。调用 notify()或 notifyAll()方法的原因通常是,调用线程希望告诉其他等待中的线程:"特殊状态已经被设置"。这个状态作为线程间通信的通道,它必须是一个可变的共享状态(或变量)。
9. Java 中 interrupted 和 isInterruptedd 方法的区别?
interrupted() 和 isInterrupted()的主要区别是前者会将中断状态清除而后者不会。Java 多线程的中断机制是用内部标识来实现的,调用 Thread.interrupt()来中断一个线程就会设置中断标识为 true。当中断线程调用静态方法 Thread.interrupted()来检查中断状态时,中断状态会被清零。而非静态方法 isInterrupted()用来查询其它线程的中断状态且不会改变中断状态标识。简单的说就是任何抛出 InterruptedException 异常的方法都会将中断状态清零。无论如何,一个线程的中断状态有有可能被其它线程调用中断来改变。
10. Java 中 synchronized 和 ReentrantLock 有什么不同?
相似点:这两种同步方式有很多相似之处,它们都是加锁方式同步,而且都是阻塞式的同步,也就是说当如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,而进行线程阻塞和唤醒的代价是比较高的.区别:这两种方式最大区别就是对于 Synchronized 来说,它是 java 语言的关键字,是原生语法层面的互斥,需要 jvm 实现。而 ReentrantLock 它是 JDK 1.5 之后提供的 API 层面的互斥锁,需要 lock()和 unlock()方法配合 try/finally 语句块来完成。synchronized 经过编译,会在同步块的前后分别形成 monitorenter 和 monitorexit 这个两个字节码指令。在执行 monitorenter 指令时,首先要尝试获取对象锁。如果这个对象没被锁定,或者当前线程已经拥有了那个对象锁,把锁的计算器加 1,相应的,在执行 monitorexit 指令时会将锁计算器就减 1,当计算器为 0 时,锁就被释放了。如果获取对象锁失败,那当前线程就要阻塞,直到对象锁被另一个线程释放为止。由于 ReentrantLock 是 java.util.concurrent 包下提供的一套互斥锁,相比 Synchronized,ReentrantLock 类提供了一些高级功能,主要有以下 3 项:1.等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于 Synchronized 来说可以避免出现死锁的情况。2.公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,Synchronized 锁非公平锁,ReentrantLock 默认的构造函数是创建的非公平锁,可以通过参数 true 设为公平锁,但公平锁表现的性能不是很好。3.锁绑定多个条件,一个 ReentrantLock 对象可以同时绑定对个对象。
11. SynchronizedMap 和 ConcurrentHashMap 有什么区别?
SynchronizedMap()和 Hashtable 一样,实现上在调用 map 所有方法时,都对整个 map 进行同步。而 ConcurrentHashMap 的实现却更加精细,它对 map 中的所有桶加了锁。所以,只要有一个线程访问 map,其他线程就无法进入 map,而如果一个线程在访问 ConcurrentHashMap 某个桶时,其他线程,仍然可以对 map 执行某些操作。
所以,ConcurrentHashMap 在性能以及安全性方面,明显比 Collections.synchronizedMap()更加有优势。同时,同步操作精确控制到桶,这样,即使在遍历 map 时,如果其他线程试图对 map 进行数据修改,也不会抛出 ConcurrentModificationException。
12. 什么是线程安全
线程安全就是说多线程访问同一段代码,不会产生不确定的结果。又是一个理论的问题,各式各样的答案有很多,我给出一个个人认为解释地最好的:如果你的代码在多线程下执行和在单线程下执行永远都能获得一样的结果,那么你的代码就是线程安全的。
这个问题有值得一提的地方,就是线程安全也是有几个级别的: (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 等都是线程非安全的类.
版权声明: 本文为 InfoQ 作者【Java学术趴】的原创文章。
原文链接:【http://xie.infoq.cn/article/c2b52df102625f946a32cf1e1】。文章转载请联系作者。
评论