本文提纲:什么是synchronized?synchronized关键字解决了多线程间资源访问的同步问题。synchronized关键字可以保证任何时候只有一个线程可以执行它修改的方法或代码块。主要用法如下:1.修改一个代码块。修改后的代码块称为同步语句块。其作用范围为花括号{}括起来的代码,作用对象为调用该代码块的对象;2.修饰一个方法,被修饰的方法称为同步方法,它的作用范围是整个方法,作用对象是调用这个方法的对象;3.修改一个静态方法,它的作用范围是整个静态方法,而作用的对象是这个类的所有对象;4、修改一个类,其作用范围为synchronized后括号内的部分,作用的主要对象为该类的所有对象。使用规则如下:synchronized原理实现原理:JVM通过进入和退出对象监视器(Monitor)来实现方法和同步块的同步,而对象监视器的本质依赖于对象监视器的互斥锁(MutexLock)底层操作系统)完成。具体实现是在编译后的同步方法调用前添加monitor.enter指令,在exit方法和exception处插入monitor.exit指令。对于没有获取到锁的线程,会阻塞在方法入口处,直到获取到锁的线程monitor.exit才会尝试继续获取锁。流程图如下:运行如下代码:publicstaticvoidmain(String[]args){synchronized(Synchronize.class){System.out.println("Synchronize");}}使用命令javap-cTestSyn.class查看编译后的具体信息。可以看到在同步块的入口处和出口处分别有monitorenter和monitorexit指令。当monitorenter指令执行时,线程尝试获取锁,也就是获取monitor(monitor对象存在于每个Java对象的对象头中,synchronized锁是通过这种方式获取锁的,这就是为什么anyJava中的object可以作为锁(原因)的所有权。当计数器为0时,可以成功获取。获取后,将锁计数器置1,即加1。相应的,执行完monitorexit指令后,将锁计数器置0,表示释放锁。如果获取对象锁失败,当前线程会阻塞等待,直到锁被另一个线程释放。synchronized方法修改时,增加ACC_SYNCHRONIZED标志,表示该方法是同步方法。JVM通过ACC_SYNCHRONIZED访问标志来识别一个方法是否声明为同步方法,从而执行相应的同步调用。synchronized的特点:修改一个代码块1.当一个线程访问对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞。publicclassTestSyn{publicstaticvoidmain(String[]args){System.out.println("使用关键字同步");同步线程syncThread=newSyncThread();线程thread1=newThread(syncThread,"SyncThread1");线程thread2=newThread(syncThread,"SyncThread2");thread1.start();thread2.start();}}类SyncThread实现Runnable{privatestaticintcount;公共SyncThread(){计数=0;}publicvoidrun(){synchronized(SyncThread.class){for(inti=0;i<5;i++){try{System.out.println("线程名:"+Thread.currentThread().getName()+":"+(计数++));线程.睡眠(100);}catch(InterruptedExceptione){e.printStackTrace();}}}}publicintgetCount(){返回计数;}}运行结果如下:把同步注释publicclassTestSyn{publicstaticvoidmain(String[]args){System.out.println("使用关键字同步");同步线程syncThread=newSyncThread();线程thread1=newThread(syncThread,"SyncThread1");线程thread2=newThread(syncThread,"SyncThread2");thread1.start();thread2.start();}}类SyncThread实现Runnable{privatestaticintcount;公共SyncThread(){计数=0;}publicvoidrun(){//synchronized(SyncThread.class){for(inti=0;i<5;i++){try{System.out.println("线程名称:"+Thread.currentThread().getName()+":"+(count++));线程.睡眠(100);}catch(InterruptedExceptione){e.printStackTrace();}}//}}publicintgetCount(){返回计数;}}运行结果如下:当两个并发线程thread1和thread2访问同一个对象syncThread中的同步代码块时,只有一个线程可以在另一个线程被阻塞时执行。在执行代码块之前必须等待当前线程执行完这个代码块。thread1和thread2是互斥的,因为在synchronized代码块执行时,当前对象会被锁住,只有代码块执行完后,对象锁才能释放,下一个线程才能执行并锁住对象。让我们稍微改变一下SyncThread的调用:线程thread1=newThread(newSyncThread(),"SyncThread1");线程thread2=newThread(newSyncThread(),"SyncThread2");thread1.start();thread2.start();}运行结果如下:为什么上面例子中的thread1和thread2同时执行。这是因为synchronized只对对象加锁,每个对象只有一个与之关联的锁(lock)。修改后的代码thread1和thread2是两个不同的对象。上面的代码也等价于下面的代码:SyncThreadsyncThread1=newSyncThread();SyncThreadsyncThread2=newSyncThread();线程thread1=newThread(syncThread1,"SyncThread1");线程thread2=newThread(syncThread2,"SyncThread2");thread1.start();thread2.start();此时创建了两个SyncThread对象syncThread1和syncThread2,线程thread1执行syncThread1对象中的同步代码(run),线程thread2执行syncThread2对象中的同步代码(run);我们知道synchronized锁是对象,然后会有两个锁分别锁住syncThread1对象和syncThread2对象,这两个锁互不干扰,不形成互斥,所以两个线程可以同时执行同时。2.当一个线程访问对象的synchronized(this)同步代码块时,另一个线程仍然可以访问对象中的non-synchronized(this)同步代码块。多线程访问同步和非同步代码块publicclassTestSyn{publicstaticvoidmain(String[]args){System.out.println("使用关键字同步");Mthreadsmt=newMthreads();线程thread1=newThread(mt,"mt1");线程thread2=newThread(mt,"mt2");thread1.start();thread2.start();}}类Mthreads实现Runnable{privateintcount;publicMthreads(){计数=0;}publicvoidcountAdd(){synchronized(this){for(inti=0;i<5;i++){try{System.out.println(Thread.currentThread().getName()+":"+(count++));线程.睡眠(100);}catch(InterruptedExceptione){e.printStackTrace();}}}}//非同步代码块,不对count进行读写操作,可以省略synchronizedpublicvoidprintCount(){for(inti=0;i<5;i++){try{System.out.println(Thread.currentThread().getName()+"计数:"+计数);线程.睡眠(100);}catch(InterruptedExceptione){e.printStackTrace();}}}publicvoidrun(){StringthreadName=Thread.currentThread().getName();如果(threadName.equals("mt1")){countAdd();}elseif(threadName.equals("mt2")){printCount();}}}运行结果:上面代码中的countAdd是同步的,printCount是非同步的从上面的结果可以看出,当一个线程访问对象的同步代码块时,其他线程可以访问非同步的目标代码块的代码块没有阻塞。修改一个方法synchronized修改一个方法很简单,就是在方法前面加上synchronized,publicsynchronizedvoidmethod(){};synchronized修改方法类似于修改一个代码块,但是作用范围不同,被修改的代码块用花括号括起来,方法的作用域向上,被修改方法的作用域是整个函数。如果把run方法改成下面的方法,效果是一样的。publicsynchronizedvoidrun(){{for(inti=0;i<5;i++){try{System.out.println("线程名称:"+Thread.currentThread().getName()+":"+(计数++));线程.睡眠(100);}catch(InterruptedExceptione){e.printStackTrace();}}}}运行结果:synchronized作用于整个方法。写法一:publicsynchronizedvoidmethod(){}写法二:publicvoidmethod(){synchronized(this){}}写法一修改一个方法,写法二修改一个代码块,但是写法一和写法2是等价的,都是锁住整个方法的内容。用synchronized修饰方法时要注意以下几点:1.synchronized关键字不能被继承。尽管可以使用synchronized来定义方法,但synchronized不是方法定义的一部分。所以不能继承synchronized关键字。如果父类中的某个方法使用了synchronized关键字,在子类中重写了这个方法,那么子类中的方法默认是不同步的,必须在子类中显式指定。将synchronized关键字添加到方法中。当然也可以在子类的方法中调用父类中对应的方法,这样虽然子类中的方法没有同步,但是子类调用了父类的同步方法,所以子类的方法是等价的同步起来。这两个方法的示例代码如下:在子类方法中添加synchronized关键字父类的同步方法}}2、定义接口方法时不能使用synchronized关键字。3、构造方法中不能使用synchronized关键字,但可以使用synchronized代码块进行同步。修饰静态方法synchronized也可以修饰一个静态方法,用法如下:publicsynchronizedstaticvoidmethod(){}静态方法属于类不属于对象,那么synchronized修饰的静态方法会锁住这个类的所有对象.synchronized修改静态方法publicclassTestSyn{publicstaticvoidmain(String[]args){System.out.println("使用关键字静态同步");同步线程syncThread=newSyncThread();线程thread1=newThread(syncThread,"SyncThread1");线程thread2=newThread(syncThread,"SyncThread2");thread1.start();thread2.start();}}类SyncThread实现Runnable{privatestaticintcount;公共SyncThread(){计数=0;}publicsynchronizedstaticvoidmethod(){for(inti=0;i<5;i++){try{System.out.println(Thread.currentThread().getName()+":"+(count++));线程.睡眠(100);}catch(InterruptedExceptione){e.printStackTrace();}}}publicsynchronizedvoidrun(){方法();}}运行结果如下:syncThread1和syncThread2是SyncThread的两个对象,但是thread1和thread2并发执行时保持线程同步。这是因为run中调用的是静态方法method,而静态方法是属于类的,所以syncThread1和syncThread2相当于使用了同一个锁。这与使用synchronized关键字运行相同。修改一个类还可以用于一个类,使用方法如下:publicclassTestSyn{publicstaticvoidmain(String[]args){System.out.println("使用ClassName");同步线程syncThread=newSyncThread();线程thread1=newThread(syncThread,"SyncThread1");线程thread2=newThread(syncThread,"SyncThread2");thread1.start();thread2.start();}}/*classClassName{publicvoidmethod(){synchronized(ClassName.class){}}}*/classSyncThreadimplementsRunnable{privatestaticintcount;公共SyncThread(){计数=0;}publicstaticvoidmethod(){synchronized(SyncThread.class){for(inti=0;i<5;i++){try{System.out.println(Thread.currentThread().getName()+":"+(计数++));线程.睡眠(100);}catch(InterruptedExceptione){e.printStackTrace();}}}}publicsynchronizedvoidrun(){方法();}}运行结果如下:效果和上面synchronized修饰的静态方法一样。当synchronized作用于一个类时,它会锁定该类。对象共享同一个锁总结1.如果synchronized动作的对象是非静态的,那么它获取的锁就是一个对象;如果synchronized动作的对象是静态方法或者类,它获取的锁是针对类的,所有对象共享同一个锁。2、每个对象只有一把锁(lock)与之关联,谁得到了锁,谁就可以运行它所控制的代码。3、实现同步需要大量的系统开销作为代价,甚至可能造成死锁!所以尽量避免不必要的同步控制。
