Java并发:synchronized 实例方法同步/静态方法同步/实例方法中的同步块/静态方法中的同步块 理解

Java中的同步块用synchronized标记。同步块在Java中是同步在某个对象上。所有同步在一个对象上的同步块在同时只能被一个线程进入并执行操作。所有其他等待进入该同步块的线程将被阻塞,直到执行该同步块中的线程退出。

并且存在4中不同的同步块
1.实例方法
2.静态方法
3.实例方法中的同步块
4.静态方法中的同步块

在分别讲这4个同步块的区别前需要首先了解下Java中synchronized的原理。
Java的同步机制是通过对对象(资源)加锁实现的,举个栗子:
现在有一个对象A,线程T1和T2都会修改对象A的值。
首先假设线程T1比线程T2更早运行到修改对象A的代码。
此时线程T1修改对象A的流程应该是这样的:线程T1从主存中获取对象A,并在自己缓存中存下对象A的副本,然后修改自己缓存中的对象A的副本。修改完毕后将自己缓存中的对象A的副本赋值给主存中的对象A,实现对象A的修改。
但是在线程T1执行修改对象A时,线程T2可能也跑到了修改对象A值的代码,而此时线程T1还在修改自己缓存中的对象A的副本,还没有将副本更新到主存中的对象A,但是线程T2又不知道,所以直接就从主存去取对象A了,这样就出现了竞态条件
直观点,如果A=1,T1&T2都是将A+1,那么我们期望执行完T1&T2后A=3,但是上面栗子中最后A=2,因此我们为了防止这种竞态条件的出现,就需要给对象A加锁。
在上面的例子中当线程T1从主存获取对象A时,对象A就会加锁,这时如果线程T2要从主存中取对象A时就会被阻塞,直到线程T1完成对主存中对象A的修改,这时锁会被打开,线程T2才可以调用对象A。

实例方法

实例方法(类的非静态方法)同步是同步在拥有该方法的对象上的,即加锁会加在对象上。
比如:有多个线程调用一个对象的多个函数(限定下,只有同步函数会被多个线程重复调用)时。任何一个线程在调用函数时将会判断这个函数是否是同步函数(即加了synchronized关键字),如果是,则会去检查该方法的对象(拥有者)是否已经加锁,如果加锁了,该线程就会阻塞,等待该对象解锁;如果是同步函数且对象没有加锁,则会继续运行,并给对象加锁;如果不是同步函数的话,就不需要去检查对象是否加锁,直接可以继续运行。

可以看下示例:
现在有sTest 类,包含2个(add,add2)同步实例(非静态)方法,一个(addX1)实例(非静态)方法。

public class sTest {
    public synchronized void add(){
        for(int i=0 ;i<100;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
    public synchronized void add2(){
        for(int i=100 ;i<200;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
    public void addX1(){
        for(int i=200 ;i<300;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

现在新建4个线程,并分别命名为thread1-4,所有线程全都只执行sTest 的实例对象Test的一个方法。其中1运行add,2运行add2,3运行add,4运行addX1。

public class SynchronizedTest {
    public static void main(String[] args){ 
        final sTest Test=new sTest();

        Thread thread1 = new Thread(new Runnable(){
            @Override
            public void run() {
                Test.add();
            }
        },"thread1");

        Thread thread2 = new Thread(new Runnable(){
            @Override
            public void run() {
                Test.add2();
            }
        },"thread2");

        Thread thread3 = new Thread(new Runnable(){
            @Override
            public void run() {
                Test.add();
            }
        },"thread3");

        Thread thread4 = new Thread(new Runnable(){
            @Override
            public void run() {
                Test.addX1();
            }
        },"thread4");

        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
    }
}

大致运行结果应该会符合以下说明:
开头一段是Thread1和Thread4混合交错的输出
等Thread1输出完毕后是Thread3或者Thread2的输出
Thread3或者Thread2输出完毕后是Thread2或者Thread3

以上,也可以自己尝试创建多个sTest 的对象,然后在多个线程中运行不同对象的同一个同步函数。你会发现输出应该是混合交错的,即只会对对象加锁。


静态方法同步

静态方法同步是同步在静态方法的类对象(不是类的实例,是指保存类信息的对象)上的,事实上,Java虚拟机中一个类只能对应一个类对象,所以只允许一个线程执行同一个类静态同步方法。其实和实例方法同步是类似的,只不过实例方法同步是在类实例上加锁,静态方法同步是在类对象上加锁。
示例:

public class sTest {
    public static synchronized void addJ1(){
        for(int i=400 ;i<500;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
    public static synchronized void addJ2(){
        for(int i=500 ;i<600;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}
public class SynchronizedTest {
    public static void main(String[] args){ 
        Thread thread1 = new Thread(new Runnable(){
            @Override
            public void run() {
                sTest.addJ1();
            }
        },"thread1");

        Thread thread2 = new Thread(new Runnable(){
            @Override
            public void run() {
                sTest.addJ2();
            }
        },"thread2");

        thread1.start();
        thread2.start();
    }
}

结果:线程1调用完静态同步函数addJ1后,线程2才会从阻塞中恢复并调用完静态同步函数addJ2
这里写图片描述


实例方法中的同步块

非同步函数里的同步块例子

public class MyClass {
   public void function(){
      synchronized(X){
          ......
      }
   }
 }

有时候我们可能只需要给某个函数中的一块代码加同步,这时就可以使用同步块方式实现。在上例中,我么可以看到用括号把一个对象X括了起来,在同步块中,被括号括起来的对象叫做监视对象,即Java会给这个对象X加锁以实现同步。
一次只有一个线程能够在同步于同一个监视对象的Java方法内执行。
栗子

public class sTest {
        //实例方法同步
        public synchronized void add(){
            for(int i=0 ;i<100;i++){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
        //实例方法中的同步块
        public void addQ1(){
            synchronized(this){
                for(int i=600 ;i<700;i++){
                    System.out.println(Thread.currentThread().getName()+":"+i);
                }
            }
        }
        public void addQ2(){
            synchronized(this){
                for(int i=700 ;i<800;i++){
                    System.out.println(Thread.currentThread().getName()+":"+i);
                }
            }
        }
        public void addQ3(String s){
            synchronized(s){
                for(int i=800 ;i<900;i++){
                    System.out.println(Thread.currentThread().getName()+":"+i);
                }
            }
        }
        public void addQ4(String s){
            synchronized(s){
                for(int i=900 ;i<1000;i++){
                    System.out.println(Thread.currentThread().getName()+":"+i);
                }
            }
        }
}

首先我们建两个线程并分别执行addQ1和addQ2

public static void main(String[] args){ 
        final sTest sTest =new sTest();
        final String s = "1";
        Thread thread1 = new Thread(new Runnable(){
            @Override
            public void run() {
                sTest.addQ1();
            }
        },"thread1");

        Thread thread2 = new Thread(new Runnable(){
            @Override
            public void run() {
                sTest.addQ2();
            }
        },"thread2");

        thread1.start();
        thread2.start();
    }

结果,因为监视对象为this,即类实例sTest,而thread1和thread2执行的都是同一个对象sTest。所以会出现如下图结果,执行完一个线程的方法前另一个线程会被阻塞。当然,这个栗子其实和实例方法同步里的一样,都是对类实例加锁,也可是尝试在thread2中执行add函数,结果也是会出现阻塞。
同样的,新建一个String变量s,然后在thread1和thread2中调用addQ3(s)和addQ4(s),也能看到同样的结果。
这里写图片描述


静态方法中的同步块

和实例方法中的同步块是一样的。
栗子:

//静态方法同步
public static synchronized void addJ1(){
            for(int i=400 ;i<500;i++){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
//静态方法中的同步块
public static void addS1(){
            synchronized(sTest.class){
                for(int i=1000 ;i<1100;i++){
                    System.out.println(Thread.currentThread().getName()+":"+i);
                }
            }
        }

addJ1,addS1这两个方法不允许同时被线程访问。
与实例方法中的同步块一样,当传入同一个对象时,这两个片段不允许同时被线程访问。

public static void addS1(String s){
            synchronized(s){
                for(int i=1000 ;i<1100;i++){
                    System.out.println(Thread.currentThread().getName()+":"+i);
                }
            }
        }
public static void addS2(String s){
            synchronized(s){
                for(int i=1000 ;i<1100;i++){
                    System.out.println(Thread.currentThread().getName()+":"+i);
                }
            }
        }

猜你喜欢

转载自blog.csdn.net/yeyinglingfeng/article/details/80019459