方法锁、对象锁、类锁

一、java内存模型

java内存模型规定了所有变量都存储在主内存(Main Memory)中,每个线程还有自己的工作线程(Working Memory),线程的工作内存保存了该线程使用到的变量的主内存副本拷贝,线程对变量的所有读写操作都必须在工作线程中进行,而不能直接读写主内存中的变量(这里的变量指的是实例字段、静态字段和构成数组对象的元素以及堆上的数据)。不同线程之间也无法直接访问其它线程工作内存中的变量,线程间变量值的传递都通过主内存来完成,线程、主内存、工作内存三者的交互关系如下所示:

先看一个两个线程同时操作的例子

public class SynMethodTest {
    private int a;

    public int getA() {
        return a;
    }

    public void setA(int a) {
        this.a = a;
    }
    public  void increaseI() {
        for (int i = 0; i < 5; i++) {
            ++a;
            System.out.print("thread1 run: a="+ a +",,thread id="+Thread.currentThread().getId()+"\n");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public static void main(String[] args) {
    SynMethodTest synMethodTest=new SynMethodTest();
    Thread thread1=new Thread(new Runnable() {
        @Override
        public void run() {
            synMethodTest.increaseI();
            System.out.print("thread1 run: end======================="+"\n");
        }
    });
    Thread thread2=new Thread(new Runnable() {
        @Override
        public void run() {
            synMethodTest.increaseI();
            System.out.print("thread2 run: end======================="+"\n");
        }
    });
    thread1.start();
    thread2.start();
}

log打印如下:

thread1 run: a=2,,thread id=11
thread1 run: a=1,,thread id=12
thread1 run: a=3,,thread id=12
thread1 run: a=3,,thread id=11
thread1 run: a=4,,thread id=12
thread1 run: a=5,,thread id=11
thread1 run: a=7,,thread id=12
thread1 run: a=6,,thread id=11
thread1 run: a=8,,thread id=11
thread1 run: a=9,,thread id=12
thread1 run: end=======================
thread2 run: end=======================

两个线程都在执行同一个对象的同一个方法导致数据变化无法预测,由于多个线程同时操作某个对象就可能导致数据不一致以及死锁等并发问题,而解决这些并发问题可以通过volatile、synchrozied等锁机制来解决。

二、方法锁、对象锁、类锁

在java中,最基本的互斥同步手段就是synchronized关键字,synchronized关键字经过编译之后,会在同步块的前后形成monitorenter和monitorexit两个字节码指令,这两个字节码指令都需要一个reference类型的参数来明确要锁定和解锁的对象。

如果java程序中的synchronized明确指定了对象参数,那就是这个对象reference;如果没有明确指定,那么就要根据synchronized修饰的是实例方法还是类方法,去取对象的对象实例或者class对象来作为reference。

1、方法锁

方法锁是在一个方法名的前面加上synchronized关键字,如

public synchronized void method(){//todo}; 

这里要分两种情况,一种情况是此方法是静态方法,另外一种情况是此方法是非静态方法。

1)如果synchronized 修饰一个非静态方法,那么调用这个非静态方法的对象实例就会被锁定,其他线程再去调用该非静态方法就会堵塞,直到对象实例被解锁,其他线程才可以访问该非静态方法。

在SynMethodTest.java的increaseI方法前加上synchronized

public synchronized void increaseI() {
    for (int i = 0; i < 5; i++) {
        ++a;
        System.out.print("thread1 run: a="+ a +",,thread id="+Thread.currentThread().getId()+"\n");
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

log打印如下

thread1 run: a=1,,thread id=11
thread1 run: a=2,,thread id=11
thread1 run: a=3,,thread id=11
thread1 run: a=4,,thread id=11
thread1 run: a=5,,thread id=11
thread1 run: end=======================
thread1 run: a=6,,thread id=12
thread1 run: a=7,,thread id=12
thread1 run: a=8,,thread id=12
thread1 run: a=9,,thread id=12
thread1 run: a=10,,thread id=12
thread2 run: end=======================

可以看出thread1 执行完后释放锁,thread2才开始执行。

此时如果在SynMethodTest.java增加一个printM方法

public  void printM() {
    System.out.print("just do a print test ,,thread id="+Thread.currentThread().getId()+"\n");
}

log打印如下:

thread1 run: a=1,,thread id=11
just do a print test ,,thread id=12
thread1 run: a=2,,thread id=11
thread1 run: a=3,,thread id=11
thread1 run: a=4,,thread id=11
thread1 run: a=5,,thread id=11
thread1 run: end=======================
thread1 run: a=6,,thread id=12
thread1 run: a=7,,thread id=12
thread1 run: a=8,,thread id=12
thread1 run: a=9,,thread id=12
thread1 run: a=10,,thread id=12
thread2 run: end=======================

如果在printM方法加上synchronized,log打印如下:

thread1 run: a=1,,thread id=11
thread1 run: a=2,,thread id=11
thread1 run: a=3,,thread id=11
thread1 run: a=4,,thread id=11
thread1 run: a=5,,thread id=11
thread1 run: end=======================
just do a print test ,,thread id=12
thread1 run: a=6,,thread id=12
thread1 run: a=7,,thread id=12
thread1 run: a=8,,thread id=12
thread1 run: a=9,,thread id=12
thread1 run: a=10,,thread id=12
thread2 run: end=======================

synchronized修饰方法时会将实例对象锁定,该对象实例不能同时执行其他被synchronized修饰的方法。

刚才说到的synchronized修饰方法时会将实例对象锁定,如果是同一个类的两个不同实例,那就相互不影响

public static void main(String[] args) {
    SynMethodTest synMethodTest=new SynMethodTest(); //实例1
    SynMethodTest synMethodTest2=new SynMethodTest(); //实例2
    Thread thread1=new Thread(new Runnable() {
        @Override
        public void run() {
            synMethodTest.increaseI();
            System.out.print("thread1 run: end======================="+"\n");
        }
    });
    Thread thread2=new Thread(new Runnable() {
        @Override
        public void run() {
            synMethodTest2.printM();
            synMethodTest2.increaseI();
            System.out.print("thread2 run: end======================="+"\n");
        }
    });
    thread1.start();
    thread2.start();
}

log打印:

thread1 run: a=1,,thread id=11
just do a print test ,,thread id=12
thread1 run: a=1,,thread id=12
thread1 run: a=2,,thread id=11
thread1 run: a=2,,thread id=12
thread1 run: a=3,,thread id=11
thread1 run: a=3,,thread id=12
thread1 run: a=4,,thread id=11
thread1 run: a=4,,thread id=12
thread1 run: a=5,,thread id=11
thread1 run: a=5,,thread id=12
thread1 run: end=======================
thread2 run: end=======================

2)如果synchronized 修饰一个静态方法,那么这个类会被锁定。

上例中可以同时执行同一个类的两个不同对象实例的同一个synchronized非静态方法,如果执行的是同一个synchronized静态方法,则就必须一次只有一个线程可以执行,另一个线程则需要等前一个线程执行完释放锁才可以开始执行。SynMethodTest 修改如下:

public class SynMethodTest {
    private static int a;

    public  int getA() {
        return a;
    }

    public void setA(int a) {
        this.a = a;
    }
    public static synchronized void increaseI() {
        for (int i = 0; i < 5; i++) {
            ++a;
            System.out.print("thread1 run: a="+ a +",,thread id="+Thread.currentThread().getId()+"\n");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
 public synchronized void printM() {
        System.out.print("just do a print test ,,thread id="+Thread.currentThread().getId()+"\n");
    }
}

log打印如下:

thread1 run: a=1,,thread id=11
just do a print test ,,thread id=12
thread1 run: a=2,,thread id=11
thread1 run: a=3,,thread id=11
thread1 run: a=4,,thread id=11
thread1 run: a=5,,thread id=11
thread1 run: end=======================
thread1 run: a=6,,thread id=12
thread1 run: a=7,,thread id=12
thread1 run: a=8,,thread id=12
thread1 run: a=9,,thread id=12
thread1 run: a=10,,thread id=12
thread2 run: end=======================

可以看出同一个synchronized 静态方法不能同时被两个线程执行,但是synchronized 非静态printM方法却可以执行,如果此时将printM方法也改为静态的呢

public static synchronized void printM() {
    System.out.print("just do a print test ,,thread id="+Thread.currentThread().getId()+"\n");
}

log打印如下:

thread1 run: a=1,,thread id=11
thread1 run: a=2,,thread id=11
thread1 run: a=3,,thread id=11
thread1 run: a=4,,thread id=11
thread1 run: a=5,,thread id=11
thread1 run: end=======================
just do a print test ,,thread id=12
thread1 run: a=6,,thread id=12
thread1 run: a=7,,thread id=12
thread1 run: a=8,,thread id=12
thread1 run: a=9,,thread id=12
thread1 run: a=10,,thread id=12
thread2 run: end=======================

可以看出一个类中一次只能执行一个synchronized 静态方法。

3)与方法锁类似的情况是用另外一个对象实例去锁住一个方法中的代码块,他与方法锁的区别是方法锁锁住的是方法所在对象实例,而代码块中的对象实例可以是任意一个不想关的对象实例。

 

2、对象锁

对象锁修饰的是一个对象实例,下面三个锁是等价的

public synchronized void method()  //锁定的是对象实例
{
   // todo
}
public void method()
{
   synchronized(this) {   //锁定的是对象实例
      // todo
   }
}
synchronized (synMethodTest){  //锁定的是对象实例
    synMethodTest.increaseI();
    System.out.print("thread1 run: end======================="+"\n");
}

如果将SynMethodTest.java中的方法去掉synchronized 关键字,将synchronized 移到调用实例方法的地方

public class SynMethodTest {
    private  int a;

    public  int getA() {
        return a;
    }

    public void setA(int a) {
        this.a = a;
    }
    public  void increaseI() {
        for (int i = 0; i < 5; i++) {
            ++a;
            System.out.print("thread1 run: a="+ a +",,thread id="+Thread.currentThread().getId()+"\n");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public  synchronized void printM() {
        System.out.print("just do a print test ,,thread id="+Thread.currentThread().getId()+"\n");
    }

}
public static void main(String[] args) {
    SynMethodTest synMethodTest=new SynMethodTest();
    SynMethodTest synMethodTest2=new SynMethodTest();
    Thread thread1=new Thread(new Runnable() {
        @Override
        public void run() {
            synchronized (synMethodTest){
                synMethodTest.increaseI();
                System.out.print("thread1 run: end======================="+"\n");
            }
        }
    });
    Thread thread2=new Thread(new Runnable() {
        @Override
        public void run() {
            synchronized (synMethodTest) {
                synMethodTest2.printM();
                synMethodTest2.increaseI();
                System.out.print("thread2 run: end=======================" + "\n");
            }
        }
    });
    thread1.start();
    thread2.start();
}

log打印如下:

thread1 run: a=1,,thread id=11
thread1 run: a=2,,thread id=11
thread1 run: a=3,,thread id=11
thread1 run: a=4,,thread id=11
thread1 run: a=5,,thread id=11
thread1 run: end=======================
just do a print test ,,thread id=12
thread1 run: a=1,,thread id=12
thread1 run: a=2,,thread id=12
thread1 run: a=3,,thread id=12
thread1 run: a=4,,thread id=12
thread1 run: a=5,,thread id=12
thread2 run: end=======================

3、类锁

类锁锁定的是一个类或者一个静态方法,如静态方法锁中所述类锁定后类的不同对象实例也不能执行同一个被synchronized 修饰的方法。

参考文献:https://blog.csdn.net/luoweifu/article/details/46613015

https://www.cnblogs.com/edwardru/articles/6030686.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值