美文网首页
类锁 、对象锁探究

类锁 、对象锁探究

作者: karlsu | 来源:发表于2017-02-26 21:07 被阅读1503次

熟练掌握多线程编程是程序猿的基本技能之一,很多朋友在平时的工作中,也许用惯了开源库,虽然知道自己写的代码是支持多线程的,却不懂多线程实现的原理。作者差不多也是这种状态,每次遇到问题才去翻资料。今天恰巧又想到了多线程的一个问题,所以得空自己写个demo证实下。

假如我们有一个工具类Utils,包含两个同步方法,如下:

    public synchronized void makeCall() {
        for (int i = 0; i < 5; i++) {
            try {
                System.out.println("makeCall");
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void sendMail() {
        for (int i = 0; i < 5; i++) {
            try {
                System.out.println("sendMail");
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

在main函数开启两个线程分别调用上面两个方法

 public static void main(String[] agrs) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Utils utils = new Utils();
                utils.makeCall();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                Utils utils = new Utils();
                utils.sendMail();
            }
        }).start();
    }

结果会是怎样的呢,会存在互斥的问题吗?看下面的结果

makeCall
sendMail
.......
makeCall
sendMail

Process finished with exit code 0

从结果来看,并不存在互斥的情况,有些同学有疑问了,Utils的两个方法不是都加锁了吗,为什么没有同步呢?
我们把调用方法的函数改一改,如下:

 public static void main(String[] agrs) {
       final Utils utils = new Utils();
        new Thread(new Runnable() {
            @Override
            public void run() {
                utils.makeCall();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                utils.sendMail();
            }
        }).start();
    }

看下打印结果:

makeCall
makeCall
makeCall
makeCall
makeCall
sendMail
sendMail
sendMail
sendMail
sendMail

从结果来看,本次调用出现了互斥现象,原因很简单,第一个实验,两个方法都加锁了是没毛病的,问题在于Utils的每个方法的锁是当前对象,对于两个不同的对象,相当于两把不同的锁,当然不存在互斥了。实验二恰好使用的是同一个对象,也就是同一把锁,也就存在互斥行为了。

继续实验三

// 将makeCall改成static的
public static synchronized void makeCall() {
        for (int i = 0; i < 5; i++) {
            try {
                System.out.println("makeCall");
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] agrs) {
       final Utils utils = new Utils();
        new Thread(new Runnable() {
            @Override
            public void run() {
            // 直接调用Utils的静态方法
                Utils.makeCall();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                utils.sendMail();
            }
        }).start();
    }

打印结果如下:

makeCall
sendMail
........
makeCall
sendMail

本次打印结果也不存在互斥现象,在改下代码

public class Utils {
    //静态同步方法
    public static synchronized void makeCall() {
        for (int i = 0; i < 5; i++) {
            try {
                System.out.println("makeCall");
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //静态同步方法
    public static synchronized void sendMail() {
        for (int i = 0; i < 5; i++) {
            try {
                System.out.println("sendMail");
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
 public static void main(String[] agrs) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Utils.makeCall();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                Utils.sendMail();
            }
        }).start();
    }

打印结果如下:

makeCall
makeCall
makeCall
makeCall
makeCall
sendMail
sendMail
sendMail
sendMail
sendMail

从输出来看,出现了互斥现象。实验三将makeCall改成静态同步方法,这把锁是类锁,而sendMail的锁依然是一个对象。在实验四,将两个方法都改成静态同步方法,使用的就是同一把锁了。

上面的几个实验都是为了引出两个概念,类锁/对象锁。
对象锁:JVM 在创建对象的时候,默认会给每个对象一把唯一的对象锁,一把钥匙
类锁:每一个类都是一个对象,每个对象都拥有一个对象锁。

总结:
1.对象锁钥匙只能有一把才能互斥,才能保证共享变量的唯一性
2.在静态方法上的锁,和实例方法上的锁,默认不是同样的,如果同步需要制定两把锁一样。
3.关于同一个类的方法上的锁,来自于调用该方法的对象,如果调用该方法的对象是相同的,那么锁必然相同,否则就不相同。比如 new A().x() 和 new A().x(),对象不同,锁不同,如果A的单利的,就能互斥。
4.静态方法加锁,能和所有其他静态方法加锁的进行互斥
5.静态方法加锁,和xxx.class 锁效果一样,直接属于类的

延伸一下:既然有了synchronized修饰方法的同步方式,为什么还需要synchronized修饰同步代码块的方式呢?
当某个线程进入同步方法获得对象锁,那么其他线程访问这里对象的同步方法时,必须等待或者阻塞,这对高并发的系统是致命的。如果某个线程在同步方法里面发生了死循环,那么它就永远不会释放这个对象锁,那么其他线程就要永远的等待。当然同步方法和同步代码块都会有这样的缺陷,只要用了synchronized关键字就会有这样的风险和缺陷。既然避免不了这种缺陷,那么就应该将风险降到最低。这也是同步代码块在某种情况下要优于同步方法的方面。
例如在某个类的方法里面:这个类里面声明了一个对象实例,

Object obj=new Object ();

在某个方法里面调用了这个实例的方法obj.program();但是调用这个方法需要进行同步,不能同时有多个线程同时执行调用这个方法。这时如果直接用synchronized修饰调用了obj.program();代码的方法,那么当某个线程进入了这个方法之后,这个对象其他同步方法都不能给其他线程访问了。假如这个方法需要执行的时间很长,那么其他线程会一直阻塞,影响到系统的性能。如果这时用synchronized来修饰代码块:

synchronized(obj){
obj.program();
}

那么这个方法加锁的对象是obj这个对象,跟执行这行代码的对象没有关系,当一个线程执行这个方法时,这对其他同步方法时没有影响的,因为他们持有的锁都完全不一样。

相关文章

  • 类锁 、对象锁探究

    熟练掌握多线程编程是程序猿的基本技能之一,很多朋友在平时的工作中,也许用惯了开源库,虽然知道自己写的代码是支持多线...

  • 让你不再害怕JAVA的锁(一)

    java中的同步锁包括对象锁和类锁。 对象锁: 针对的是具体的对象实例;类锁:针对的是整个class类 现在先让我...

  • MySQL的锁怎么解?

    大家都知道java里面的synchronized,对象锁。(这里不扯什么类锁,对象锁,类锁其实就是Class对象的...

  • java类锁和对象锁

    java对象锁有两种:对象锁、类锁。 对象锁:在非静态方法上加锁。声明了一个对象锁。类锁:在静态方法上加锁,声明了...

  • synchronized

    类锁和对象锁

  • Synchronized的使用

    锁的类型 类锁:只有synchronized修饰静态方法或者修饰一个类的class对象时,才是类锁。 对象锁:除了...

  • synchronized 修饰静态方法、普通方法与代码块的区别

    概念: 类锁:所有对象共用一个锁 对象锁:一个对象一把锁,多个对象多把锁。 一、synchronized修饰普通方...

  • synchronized :同步锁

    synchronized 同步锁分为对象锁、类锁、静态方法锁、非静态方法锁等。其中对象锁和非静态方法锁的作用域是对...

  • 线程、多线程和线程池 二

    1.对象锁和类锁是否会互相影响? · 对象锁:Java的所有对象都含有1个互斥锁,这个锁由JVM自动...

  • 多线程开发艺术之对象锁和类锁

    一.对象锁和类锁是否会互相影响? ·对象锁:Java的所有对象都含有1个互斥锁,这个锁由JVM自动获取和释放。线程...

网友评论

      本文标题:类锁 、对象锁探究

      本文链接:https://www.haomeiwen.com/subject/fglawttx.html