当前位置: 首页 > 科技观察

深入理解Android进阶Synchronized关键字

时间:2023-03-22 15:56:09 科技观察

本文转载自微信公众号《Android开发与编程》,作者Android开发与编程。转载本文请联系Android开发编程公众号。一、Synchronized详解Synchronized是Java中的一个关键字。在多线程联合操作共享资源的情况下,可以保证同一时刻只有一个线程可以对共享资源进行操作,从而实现共享资源的线程安全。二、同步原子性的特点。synchronized可以保证多线程下对共享资源的访问互斥,受synchronized影响的代码可以实现原子性。能见度。synchronized确保可以及时看到对共享资源的更改。在Java内存模型中,在对共享变量进行操作后释放锁之前,即执行解锁操作之前,必须将修改同步到主内存。如果一个共享资源被加锁,即在加锁操作之前,工作内存中共享变量的值必须被清除(因为每个线程获取的共享变量都是主内存中共享变量的副本,如果不清除,就会发生数据不一致,即当前线程中的共享变量与主存中的共享变量不一致),使用这个共享变量时,需要从线程中重新加载共享变量主存获取共享变量的最新值。井然有序。synchronized可以有效解决重排序问题,即在后续线程对同一个锁进行lock操作之前,必须先发生unlockunlock操作,这样主存值的shared变量永远是最新的。3.Synchronized的使用在应用Synchronized关键字时,需要把握以下几点:一个锁只能同时被一个线程获取,没有获取到锁的线程只能等待;每个实例都有自己的锁(this),不同实例互不影响;异常:当锁对象为*.class且synchronized修饰为静态方法时,所有对象共享同一个锁;synchronized修饰的方法,不管方法是正常执行还是抛出异常,都会释放锁。对象锁包括方法锁(默认锁对象是this,当前实例对象)和同步代码块锁(自定义锁对象锁)代码块形式:手动指定锁对象,或者this,或者自定义锁publicclassSynchronizedObjectLockimplementsRunnable{staticSynchronizedObjectLockinstence=newSynchronizedObjectLock();//创建2个锁Objectblock1=newObject();Objectblock2=newObject();@Overridepublicvoidrun(){//本代码块使用第一个锁,释放时,后面自代码块使用第二个锁,它可以立即执行。synchronized(block1){System.out.println("block1锁,我是线程"+Thread.currentThread().getName());try{Thread.sleep(3000);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println("block1lock,"+Thread.currentThread().getName()+"end");}synchronized(block2){System.out.println("block2锁,我是线程"+Thread.currentThread().getName());try{Thread.sleep(3000);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println("block2lock,"+Thread.currentThread().getName()+"end");}}publicstaticvoidmain(String[]args){Threadt1=newThread(instence);Threadt2=newThread(instence);t1.start();t2.start();}}复制代码输出结果:block1lock,IamthreadThread-0block1lock,Thread-0endsblock2lock,IamthreadThread-0  //可以看到第一个线程什么时候执行第一个同步代码块执行完后,可以马上执行第二个同步代码块,因为他们使用的锁不是同一个block1锁,我是线程Thread-1block2锁,Thread-0结束block1锁,Thread-1endsblock2lock,我是threadThread-1block2lock,Thread-1endmethodlock形式:synchronized修改普通方法,默认锁对象是thispublicclassSynchronizedObjectLockimplementsRunnable{staticSynchronizedObjectLockinstence=newSynchronizedObjectLock();@Overridepublicvoidrun(){method();}publicsynchronizedvoidmethod(){System.out.println("我是一个线程"+Thread.currentThread().getName());try{Thread.sleep(3000);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"end");}publicstaticvoidmain(String[]args){Threadt1=newThread(instence);Threadt2=newThread(instence);t1.start();t2.start();}}类锁包括方法锁(默认锁对象是this,当前实例对象)和同步代码块锁(自己指定锁对象)synchronize修改静态方法(类的类对象)publicclassSynchronizedObjectLockimplementsRunnable{staticSynchronizedObjectLockinstence1=newSynchronizedObjectLock();staticSynchronizedObjectLockinstence2=newSynchronizedObjectLock();@Overridepublicvoidrun(){method();}//synchronized用在静态方法上,默认锁是当前Class类,所以无论哪个线程访问它,需要的锁都有只有一个publicstaticsynchronizedvoidmethod(){System.out.println("我是一个线程"+Thread.currentThread().getName());try{Thread.sleep(3000);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"end");}publicstaticvoidmain(String[]args){Threadt1=newThread(instence1);Threadt2=newThread(instence2);t1。start();t2.start();}}输出结果:我是threadThread-0Thread-0ends我是threadThread-1Thread-1ends复制代码synchronizedmodifyinstancemethod/***synchronizedmodifyinstancemethod,当线程拿到锁,而其他线程无法拿到该对象的锁,所以其他线程无法访问该对象的其他同步方法*但可以访问其他非同步方法zedmethodsoftheobject*加锁的是类的实例对象*/publicclasssynchronizedDemo1implementsRunnable{//模拟一个共享数据privatestaticinttotal=0;//同步方法,每个线程获得锁后,执行5次累加操作publicsynchronizedvoidincrease(){对于(inti=1;i<6;i++){System.out。println(Thread.currentThread().getName()+"执行累加操作..."+""+i+"thaccumulation");try{total=total+1;Thread.sleep(2000);}catch(InterruptedExceptione){e.printStackTrace();}}}//实例对象的另一种同步方法publicsynchronizedvoiddeclare(){System.out.println(Thread.currentThread().getName()+"执行总数-1");total--;System.out.println(Thread.currentThread().getName()+"执行总数-1completed");}//普通实例方法publicvoidsimpleMethod(){System.out.println(Thread.currentThread().getName()+"--实例对象的普通方法---");}@Overridepublicvoidrun(){//线程执行体System.out.println(Thread.currentThread().getName()+"准备执行累加,尚未获取锁");//执行公共方法simpleMethod();//call同步方法执行累加操作increase();//执行increase同步方法后,会释放锁,然后线程1和线程2再次竞争锁,谁先竞争获得锁执行先声明同步方法System.out.println(Thread.currentThread().getName()+"完成累加操作");//调用实例对象的另一个同步方法System.out.println(Thread.currentThread().getName()+"准备执行total-1");declare();}publicstaticvoidmain(String[]args)throwsInterruptedException{synchronizedDemo1syn=newsynchronizedDemo1();Threadthreadd1=newThread(syn,"Thread1");Threadthread2=newThread(syn,"Thread2");thread1.start();thread2.start();}}输出结果:线程1准备执行累加,但是hasnotyetgetting当锁线程2准备执行累加时,锁线程2还没有获取到-实例对象的公共方法-线程2执行累加操作...第一次累加//线程2率先通过与线程1的竞争获得锁后,进入增量同步方法线程2进行累加操作...二次累加线程1----实例对象的公共方法---//从这里可以看出,当线程2访问同步方法时,线程1可以访问非同步方法,但是不能访问另外一个同步方法。线程2执行累加操作...第三个累加线程2执行累加操作...第四个累加线程2执行累加操作...第五个累加线程2完成累加操作//线程2会释放锁执行累加后线程2准备执行total-1线程1执行累加操作...第一次累加//然后线程1获取锁后进入increase同步方法执行累加线程1执行累加操作...第2次累加线程1执行累加操作...第3次累加线程1执行累加操作...第4次累加线程1执行累加操作...第5次累加线程1完成累加操作//线程1完成后也会释放锁累加操作,然后线程1和线程2会进行另一次锁竞争线程1准备executetotal-1Thread2executetotal-1//线程2先通过Compete拿到锁,进入declear方法执行total-1操作。线程2执行total-1完成线程1执行total-1。线程1执行total-1完成复制代码。4.Synchronized实现原理加锁和释放锁Synchronized同步是通过monitorenter和monitorexit等指令实现的,对象会被执行,其锁计数器会加1或减1monitorenter指令:每个对象同时只关联一个monitor(锁),一个monitor同时只能被一个线程获取。当一个对象试图获取与该对象关联的Monitor锁的所有权时,会出现以下三种情况之一:monitor计数器为0,表示还没有获取,那么线程会立即获取,并且然后把锁计数器加1,一旦+1,其他线程想要获取它需要等待如果monitor已经获取了锁的所有权,重新入锁,锁计数器会累加变为2,并且会随着重新进入的次数不断累积。如果其他线程已经持有对象监视器,则当前线程进入阻塞状态,直到对象监视器中的条目数为0,并重新尝试获取监视器的所有权。monitorexit命令:释放监视器的所有权。释放过程很简单,就是把monitor的计数器减1,如果减1后计数器不为0,说明刚才重入了,当前线程继续持有锁.所有权,如果计数器变为0,说明当前线程不再拥有管程的所有权,即释放锁。对象、对象监视器、同步队列和执行线程状态之间的关系:从图中可以看出,任何访问一个Object的线程都必须首先获得该Object的监视器。如果获取失败,则线程进入同步状态,线程状态变为BLOCKED,当Object的monitor占用被释放后,同步队列中的线程将有机会重新获取monitor。重入原理:锁数计数器由上图可以看出,执行静态同步方式时,只有一条monitorexit指令,没有monitorenter获取锁的指令。这就是锁的可重入性,即在同一个锁线程中,线程不需要再次获取同一个锁。同步本质上是可重入的。每个对象都有一个计数器。当线程获得对象锁时,计数器会加一,释放锁后计数器会减一。保证可见性的原则:内存模型和happens-before规则Synchronizedhappens-beforerule,即monitor锁规则:解锁同一个monitor,happens-before锁定monitor。公共类MonitorDemo{privateinta=0;publicsynchronizedvoidwriter(){//1a++;//2}//3publicsynchronizedvoidreader(){//4inti=a;//5}//6}复制代码的happens-before关系如图所示:图中每个箭头连接的两个节点代表它们之间的happens-before关系,黑色的是程序导出的sequence规则,红色的是monitor锁规则推导:线程A释放锁happens-before线程B锁,蓝色的是通过程序顺序规则和monitor锁规则推导出的happens-befor关系,happens-before关系是通过传递规则进一步导出的。总结synchronized同步语句块的实现使用了monitorenter和monitorexit指令,其中monitorenter指令指向同步代码块的开始位置,monitorexit指令表示同步代码块的结束位置。synchronized修饰的方法没有monitorenter指令和monitorexit指令,而是用ACC_SYNCHRONIZED标志代替,表示该方法是同步方法。但两者的本质都是对象监听monitor的获取。使用Synchronized需要注意什么?锁对象不能为空,因为锁信息保存在对象头中;范围不宜过大,影响程序执行速度,控制范围过大,写代码容易出错;避免死锁Lock;如果可以选择,既不应该使用Lock也不应该使用synchronized关键字,而应该使用java.util.concurrent包中的各种类,可以使用synchronized关键字,因为代码量小,可以避免出错。