1、引言ide
因为同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问的问题。函数
这套机制就是synchronized关键字,它包括两种用法:synchronized 方法(同步方法)和synchronized语句块(同步语句块)。this
2、synchronized不一样的修饰状况spa
一、synchronized方法(同步方法):synchronized修饰类中的方法,以下所示:线程
class P implements Runnable { public synchronized void methodPA() { //...
} }
二、synchronized语句块(同步语句块):带有某具体对象的synchronized修饰类中方法内的语句,以下所示:3d
class P implements Runnable { public void methodPA(SomeObject so) { synchronized(so) {//so为对象锁得引用 //...
} } }
3、几个重要概念code
①对象锁:每一个对象都只有一个锁,称为对象锁;对象
②synchronized锁定的是哪一个对象:调用该synchronized方法的对象或者synchronized指定的对象;blog
③线程拿到锁(对象锁):同上;进程
④取得对象锁的线程:某个对象有可能在不一样的线程中调用同步方法,只有拿到对象锁得线程,才可使用该对象来调用同步方法。
4、对象锁的理解
①不管synchronized加在方法上仍是加在对象上,线程拿到的锁都是对象(关键要分析出synchronized锁定的是哪一个对象),不能把某个函数或一段代码看成锁;
②每一个对象只有一个锁;
③实现同步须要很大的系统开销,甚至可能形成死锁,因此尽可能避免无谓的同步。
5、理解线程同步(synchronized)
用个例子来说解比较清晰:假设P一、P2是同一个类的不一样对象,这个类中定义了如下几种状况的同步方法或同步块,P1对象、P2对象可以执行它们。
一、synchronized方法(同步方法)
class P implements Runnable { public synchronized void methodPA() { //...
} }
此时synchronized锁定的是哪一个对象呢(对象锁)?
synchronized锁定的是调用该同步方法的对象,即对象P1在不一样线程中调用该同步方法,这些线程间会造成互斥关系,只有拿到P1对象锁的线程,才可以调用该同步方法。
可是对于P1对象所属类所产生的另外一对象P2而言,仍是可以任意调用这个被该同步方法,所以程式也可能在这种情形下摆脱同步机制的控制,形成数据混乱。
上边的示例代码等同于以下代码:
class P implements Runnable { public synchronized void methodPA() { synchronized(this) { //...
} } }
上述代码中的this指的是调用这个方法的对象即P1。
二、synchronized语句块(同步语句块)
class P implements Runnable { public void methodPA(SomeObject so) { synchronized(so) { //...
} } }
此时锁就是so这个对象,只有拿到这个锁的线程,才可以运行该锁对象控制的这段代码。当有一个明确的对象做为锁时,就可以这样写程式,但当没有明确的对象做为锁,只是想让一段代码同步时,可以建立一个特别的instance变量对象来充当锁:
class P implements Runnable { private byte[] lock = new byte[0];//特别的instance变量
public void methodPA(){ synchronized(lock){ //...
} } }
三、synchronized修饰static函数
class P implements Runnable { public synchronized static void methodPA() { //...
} public void methodPB(){ synchronized(P.class){ //...
} } }
6、线程访问对象锁的规则—这里比较特殊使用的是synchronized(this)
①当两个线程访问同一个对象的synchronized(this)代码块时,同一时间内只能有一个线程获得执行,另外一个线程必须等待当前线程执行完这个代码块之后才能执行该代码块;
②然而,当一个线程访问对象的一个synchronized(this)代码块时,其余线程仍然能够访问该对象的非synchronized(this)的代码;
③尤为关键的是,当一个线程访问对象的一个synchronized(this)代码块时,其余线程对该对象中其它全部synchronized(this)代码块的访问将被阻塞(由于前一个线程得到了该对象的锁,致使其余线程没法得到该对象的锁)。
例以下面的程序:当有线程进入同步代码1时,其余全部的线程都不能进入同步代码1和同步代码2,由于此时锁1处于锁上的状态,但能够进入同步代码3,由于同步代码3使用的是锁2,与锁1的状态没有关系。
synchronized(锁1) { 同步代码1 } synchronized(锁1) { 同步代码2 } synchronized(锁2) { 同步代码3 }
使用如下代码来说解一下线程同步的执行步骤:
public class MyThreadDemo { public static void main(String[] args) { //任意定义一个对象,传给线程做为锁,能够是任意对象,例如int myLock = 1;也能够是内存中已经存在的对象,例如Object.class
String myLock = "hi"; Thread t1 = new LockThread("t1",myLock); Thread t2 = new LockThread("t2",myLock); Thread t3 = new LockThread("t3",myLock); t1.start(); t2.start(); t3.start(); } } class LockThread extends Thread { private Object MyLock; public LockThread(String name, Object MyLock){ super(name); this.MyLock = MyLock; } public void run(){ System.out.println(currentThread().getName()+":我得到cpu了,准备进入同步代码块....."); synchronized(MyLock){ System.out.println(currentThread().getName()+":我进来了!"); System.out.println(currentThread().getName()+":歇会"); try { Thread.sleep(100); }catch(Exception e){ e.printStackTrace(); } System.out.println(currentThread().getName()+":走了!"); } } }
执行结果以下,能够看到当t1先进入同步代码块后,即便t3,t2得到了cpu,也没有办法进入到同步代码块中,直到t1出来,并开锁后t2才能进入:
t1:我得到cpu了,准备进入同步代码块.....
t1:我进来了!
t1:歇会
t2:我得到cpu了,准备进入同步代码块.....
t3:我得到cpu了,准备进入同步代码块.....
t1:走了!
t3:我进来了!
t3:歇会
t3:走了!
t2:我进来了!
t2:歇会
t2:走了!