上一篇博文:Java多线程(一) —— 线程的状态详解中详细介绍了线程的五种状态及状态间的转换。本文着重介绍了线程安全的相关知识点,包括线程同步和锁机制、线程间通讯以及相关面试题的总结html
多个线程在执行同一段代码的时候,每次的执行结果和单线程执行的结果都是同样的,不存在执行结果的二义性,就能够称做是线程安全的。面试
讲到线程安全问题,实际上是指多线程环境下对共享资源的访问可能会引发此共享资源的不一致性。所以,为避免线程安全问题,应该避免多线程环境下对此共享资源的并发访问。编程
线程安全问题可能是由全局变量和静态变量引发的,当多个线程对共享数据只执行读操做,不执行写操做时,通常是线程安全的;当多个线程都执行写操做时,须要考虑线程同步来解决线程安全问题。安全
线程同步:将操做共享数据的代码行做为一个总体,同一时间只容许一个线程执行,执行过程当中其余线程不能参与执行。目的是为了防止多个线程访问一个数据对象时,对数据形成的破坏。多线程
(1)同步方法(synchronized)并发
对共享资源进行访问的方法定义中加上synchronized关键字修饰,使得此方法称为同步方法。能够简单理解成对此方法进行了加锁,其锁对象为当前方法所在的对象自身。多线程环境下,当执行此方法时,首先都要得到此同步锁(且同时最多只有一个线程可以得到),只有当线程执行完此同步方法后,才会释放锁对象,其余的线程才有可能获取此同步锁,以此类推...格式以下:post
public synchronized void run() { // .... }
(2)同步代码块(synchronized)this
使用同步方法时,使得整个方法体都成为了同步执行状态,会使得可能出现同步范围过大的状况,因而,针对须要同步的代码能够直接另外一种同步方式——同步代码块来解决。格式以下:spa
synchronized (obj) { // .... }
其中,obj为锁对象,所以,选择哪个对象做为锁是相当重要的。通常状况下,都是选择此共享资源对象做为锁对象。操作系统
(3)同步锁(Lock)
使用Lock对象同步锁能够方便地解决选择锁对象的问题,惟一须要注意的一点是Lock对象须要与资源对象一样具备一对一的关系。Lock对象同步锁通常格式为:
class X { // 显示定义Lock同步锁对象,此对象与共享资源具备一对一关系 private final Lock lock = new ReentrantLock(); public void m(){ // 加锁 lock.lock(); //... 须要进行线程安全同步的代码 // 释放Lock锁 lock.unlock(); } }
何时须要同步:
(1)可见性同步:在如下状况中必须同步: 1)读取上一次多是由另外一个线程写入的变量 ;2)写入下一次可能由另外一个线程读取的变量
(2)一致性同步:当修改多个相关值时,您想要其它线程原子地看到这组更改—— 要么看到所有更改,要么什么也看不到。
这适用于相关数据项(如粒子的位置和速率)和元数据项(如链表中包含的数据值和列表自身中的数据项的链)。
在某些状况中,您没必要用同步来将数据从一个线程传递到另外一个,由于 JVM 已经隐含地为您执行同步。这些状况包括:
锁的原理:
wait():致使当前线程等待并使其进入到等待阻塞状态。直到其余线程调用该同步锁对象的notify()或notifyAll()方法来唤醒此线程。
notify():唤醒在此同步锁对象上等待的单个线程,若是有多个线程都在此同步锁对象上等待,则会任意选择其中某个线程进行唤醒操做,只有当前线程放弃对同步锁对象的锁定,才可能执行被唤醒的线程。
notifyAll():唤醒在此同步锁对象上等待的全部线程,只有当前线程放弃对同步锁对象的锁定,才可能执行被唤醒的线程。
这三个方法主要都是用于多线程中,但实际上都是Object类中的本地方法。所以,理论上,任何Object对象均可以做为这三个方法的主调,在实际的多线程编程中,只有同步锁对象调这三个方法,才能完成对多线程间的线程通讯。
注意点:
1.wait()方法执行后,当前线程当即进入到等待阻塞状态,其后面的代码不会执行;
2.notify()/notifyAll()方法执行后,将唤醒此同步锁对象上的(任意一个-notify()/全部-notifyAll())线程对象,可是,此时还并无释放同步锁对象,也就是说,若是notify()/notifyAll()后面还有代码,还会继续进行,知道当前线程执行完毕才会释放同步锁对象;
3.notify()/notifyAll()执行后,若是右面有sleep()方法,则会使当前线程进入到阻塞状态,可是同步对象锁没有释放,依然本身保留,那么必定时候后仍是会继续执行此线程,接下来同2;
4.wait()/notify()/nitifyAll()完成线程间的通讯或协做都是基于不一样对象锁的,所以,若是是不一样的同步对象锁将失去意义,同时,同步对象锁最好是与共享资源对象保持一一对应关系;
5.当wait线程唤醒后并执行时,是接着上次执行到的wait()方法代码后面继续往下执行的。
1. 线程和进程有什么区别?
答:一个进程是一个独立(self contained)的运行环境,它能够被看做一个程序或者一个应用。而线程是在进程中执行的一个任务。线程是进程的子集,一个进程能够有不少线程,每条线程并行执行不一样的任务。不一样的进程使用不一样的内存空间,而全部的线程共享一片相同的内存空间。别把它和栈内存搞混,每一个线程都拥有单独的栈内存用来存储本地数据。
2. 如何在Java中实现线程?比较这种种方式
答:建立线程有两种方式:
(1)继承 Thread 类,扩展线程。
(2)实现 Runnable 接口。
继承Thread类的方式有它固有的弊端,由于Java中继承的单一性,继承了Thread类就不能继承其余类了;同时也不符合继承的语义,Dog跟Thread没有直接的父子关系,继承Thread只是为了能拥有一些功能特性。
而实现Runnable接口,①避免了单一继承的局限性,②同时更符合面向对象的编程方式,即将线程对象进行单独的封装,③并且实现接口的方式下降了线程对象(Dog)和线程任务(run方法中的代码)的耦合性,④如上面所述,可使用同一个Dog类的实例来建立并开启多个线程,很是方便的实现资源的共享。实际上Thread类也是实现了Runnable接口。实际开发中可能是使用实现Runnable接口的方式。
3. 启动一个线程是调用run()仍是start()方法?
答:启动一个线程是调用start()方法,使线程所表明的虚拟处理机处于可运行状态,这意味着它能够由JVM 调度并执行,这并不意味着线程就会当即运行。run()方法是线程启动后要进行回调(callback)的方法。
4. wait()和sleep()比较
共同点:
1). 他们都是在多线程的环境下,sleep()方法和对象的wait()方法均可以让线程暂停执行,均可以在程序的调用处阻塞指定的毫秒数,并返回。
2). wait()和sleep()均可以经过interrupt()方法打断线程的暂停状态 ,从而使线程马上抛出InterruptedException。
若是线程A但愿当即结束线程B,则能够对线程B对应的Thread实例调用interrupt方法。若是此刻线程B正在wait/sleep /join,则线程B会马上抛出InterruptedException,在catch() {} 中直接return便可安全地结束线程。 须要注意的是,InterruptedException是线程本身从内部抛出的,并非interrupt()方法抛出的。对某一线程调用 interrupt()时,若是该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。可是,一旦该线程进入到 wait()/sleep()/join()后,就会马上抛出InterruptedException 。
不一样点:
1). Thread类的方法:sleep(),yield()等
Object类的方法:wait()和notify()等
2). 每一个对象都有一个锁来控制同步访问。Synchronized关键字能够和对象的锁交互,来实现线程的同步。
sleep()方法让当前线程暂停执行指定的时间,将执行机会(CPU)让给其余线程,可是对象的锁依然保持,休眠结束后线程会自动回到就绪状态;
wait()方法致使当前线程放弃对象的锁(线程暂停执行),进入对象的等待池(wait pool),只有调用对象的notify()方法(或notifyAll()方法)时才能唤醒等待池中的线程进入等锁池(lock pool),若是线程从新得到对象的锁就能够进入就绪状态。
3). wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep能够在任何地方使用
4). sleep必须捕获异常,而wait,notify和notifyAll不须要捕获异常
因此sleep()和wait()方法的最大区别是:
sleep()睡眠时,保持对象锁,仍然占有该锁;
而wait()睡眠时,释放对象锁。
可是wait()和sleep()均可以经过interrupt()方法打断线程的暂停状态,从而使线程马上抛出InterruptedException(但不建议使用该方法)。
5. sleep()方法和yield()方法有什么区别?
① sleep()方法给其余线程运行机会时不考虑线程的优先级,所以会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;
② 线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态;
③ sleep()方法须要声明抛出InterruptedException,而yield()方法没有声明任何异常;
④ sleep()方法比yield()方法(跟操做系统CPU调度相关)具备更好的可移植性。
6. 线程类的一些经常使用方法:
7. 同步代码块和同步方法的区别
二者的区别主要体如今同步锁上面。对于实例的同步方法,由于只能使用this来做为同步锁,若是一个类中须要使用到多个锁,为了不锁的冲突,必然须要使用不一样的对象,这时候同步方法不能知足需求,只能使用同步代码块(同步代码块能够传入任意对象);或者多个类中须要使用到同一个锁,这时候多个类的实例this显然是不一样的,也只能使用同步代码块,传入同一个对象。
8. 对比synchronized和Lock
1)、synchronized是关键字,就和if...else...同样,是语法层面的实现,所以synchronized获取锁以及释放锁都是Java虚拟机帮助用户完成的;ReentrantLock是类层面的实现,所以锁的获取以及锁的释放都须要用户本身去操做。特别再次提醒,ReentrantLock在lock()完了,必定要手动unlock(),通常放在finally语句块中。
2)、synchronized简单,简单意味着不灵活,而ReentrantLock的锁机制给用户的使用提供了极大的灵活性。这点在Hashtable和ConcurrentHashMap中体现得淋漓尽致。synchronized一锁就锁整个Hash表,而ConcurrentHashMap则利用ReentrantLock实现了锁分离,锁的只是segment而不是整个Hash表
3)、synchronized是不公平锁,而ReentrantLock能够指定锁是公平的仍是非公平的
4)、synchronized实现等待/通知机制通知的线程是随机的,ReentrantLock实现等待/通知机制能够有选择性地通知
5)、和synchronized相比,ReentrantLock提供给用户多种方法用于锁信息的获取,好比能够知道lock是否被当前线程获取、lock被同一个线程调用了几回、lock是否被任意线程获取等等
总结起来,我认为若是只须要锁定简单的方法、简单的代码块,那么考虑使用synchronized,复杂的多线程处理场景下能够考虑使用ReentrantLock。
Voiatile关键字:
volatile关键字是Java并发的最轻量级实现,本质上有两个功能,在生成的汇编语句中加入LOCK关键字和内存屏障
做用就是保证每一次线程load和write两个操做,都会直接从主内存中进行读取和覆盖,而非普通变量从线程内的工做空间(默认各位已经熟悉Java多线程内存模型)
但它有一个很致命的缺点,致使它的使用范围很少,就是他只保证在读取和写入这两个过程是线程安全的。若是咱们对一个volatile修饰的变量进行多线程 下的自增操做,仍是会出现线程安全问题。根本缘由在于volatile关键字没法对自增进行安全性修饰,由于自增分为三步,读取-》+1-》写入。中间多 个线程同时执行+1操做,仍是会出现线程安全性问题。
参考连接:
http://www.importnew.com/21136.html
http://lavasoft.blog.51cto.com/62575/99155/
http://www.cnblogs.com/lwbqqyumidi/p/3821389.html