Synchronized——的使用(对象锁、类

在进行多线程编程时,Synchronized 基本上都会涉及到,但是一直都是停留在使用的层面,没有系统的了解过,更没有深入的研究过,现在做个简单的整理,有不足的地方,忘大神们多多指教:

  • 类上非静态 Synchronized 方法,锁为 this 对象,是对象锁
    在多个线程同时执行同一个实例的 Synchronized 的两个非静态方法时,后面的线程会被阻塞,进入锁池中
    在多个线程同时执行不同实例的 Synchronized 的两个非静态方法时,线程直接没有影响
  • 类上的静态 Synchronized 方法,锁为 类.clss 对象,其实质也是一个对象(类Class 对象的实例),是类锁
    在多个线程同时执行同一个实例或不同实例的 Synchronized 的两个静态方法时,后面的线程会被阻塞
  • 当存在一个线程竞争对象锁,另一个线程竞争类锁时,这两个线程时不存在竞争关系的,即对象锁与类锁不是同一把锁
  • jvm 中,每个对象和类在逻辑上都和一个监视器相关联,即 对象锁与类锁
  • 一个线程可以多次对同一个对象上锁,对于每一个对象,java虚拟机维护一个加锁计数器,线程每获得
    一次对对象,计数器就加 1,每释放一次,计数器就减 1,当计数器值为 0 时,锁就被释放了
  • 在用 String 类型作为锁时,一定要注意因为 String 类型有 常量池,两个String类型可能 == 判断是true
  • 在用对象实例做同步锁时,判断是否为同一个锁的依据是 对象没存地址是否相同
    内存地址是否相同,可以用 == 来判断,不等价于 hashCode
    如果两个对象 == 为 true时,则该对象锁为同一个锁
    如果两个对象 == 为 false时,则该对象锁不为同一个锁
下面贴几个实例来具体验证下:
默认情况下 synchronized 修饰的非静态方法,其锁为 this,与  synchronized(this) 效果一样:
public class Synchronized {

    public static void main(String[] args) {
        Synchronized sync1 = new Synchronized();
        Synchronized sync2 = new Synchronized();

        /**
         当两个线程运行的不是同一个类实例时,结果是
         sync2 is running
         sync1 is running
         sync1-0
         sync2-0
         sync2-1
         sync1-1
         sync2-2
         sync1-2
         sync2 is end
         sync1 is end
         可见两个线程执行的顺序没有影响
         所以可以说明:同一个类中 synchronized 声明的非静态方法,其锁不为 Synchronized.class
         同一个类的两个实例在访问 synchronized 声明的非静态方法时,互不影响
         */
        syncTest(sync1, sync2);

        /*
        当两个线程运行的 是同一个类实例时,结果是
        sync1 is running
        sync1-0
        sync1-1
        sync1-2
        sync1 is end
        sync2 is running
        sync2-0
        sync2-1
        sync2-2
        sync2 is end
        可见两个线程执行的顺序有明显的先后顺序
        所以可以说明:同一个类中,同一个实例在访问 synchronized 修饰的不同方法时,是需要等待 synchronized 锁的
        默认的 synchronized 修改的方法的锁为 this
         */
        syncTest(sync1, sync1);

    }
    public static void syncTest(final Synchronized sync1, final Synchronized sync2) {
        Thread th1 = new Thread(new Runnable() {
            @Override
            public void run() {
                sync1.sync1();
            }
        }, "th-1");
        th1.start();
        Thread th2 = new Thread(new Runnable() {
            @Override
            public void run() {
                sync2.sync2();
            }
        }, "th-2");
        th2.start();
    }

    /**
     * 同步方法一
     */
    public synchronized void sync1() {
        System.out.println("sync1 is running");
        try {
            for(int i = 0; i < 3; i++) {
                Thread.sleep(2000);
                System.out.println("sync1-" + i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sync1 is end");
    }

    /**
     * 同步方法二
     */
    public synchronized void sync2() {
        System.out.println("sync2 is running");
        try {
            for(int i = 0; i < 3; i++) {
                Thread.sleep(2000);
                System.out.println("sync2-" + i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sync2 is end");
    }
}

上面是对非静态 synchronized 的方法。

下面是静态的 synchronized 运行结果:

	默认情况下,synchronized 修饰的静态方法,其锁为 类.class

public class Synchronized {

    public static void main(String[] args) {
        Synchronized sync1 = new Synchronized();
        Synchronized sync2 = new Synchronized();

        /**
         当两个线程运行的不是同一个类实例时,结果是
         sync1 is running
         sync1-0
         sync1-1
         sync1-2
         sync1 is end
         sync2 is running
         sync2-0
         sync2-1
         sync2-2
         sync2 is end
         可见对于静态的 synchronized 修饰的方法,在同时调用不同实例的 同步 方法时,也是会阻塞的
         */
        syncTest(sync1, sync2);

        /*
        当两个线程运行的 是同一个类实例时,结果是
        sync1 is running
        sync1-0
        sync1-1
        sync1-2
        sync1 is end
        sync2 is running
        sync2-0
        sync2-1
        sync2-2
        sync2 is end
        可见对于静态的 synchronized 修饰的方法,在同时调用相同实例的 同步 方法时,同样会阻塞
         */
        syncTest(sync1, sync1);

    }
    public static void syncTest(final Synchronized sync1, final Synchronized sync2) {
        Thread th1 = new Thread(new Runnable() {
            @Override
            public void run() {
                sync1.sync1();
            }
        }, "th-1");
        th1.start();
        Thread th2 = new Thread(new Runnable() {
            @Override
            public void run() {
                sync2.sync2();
            }
        }, "th-2");
        th2.start();
    }

    /**
     * 同步方法一
     */
    public static synchronized void sync1() {
        System.out.println("sync1 is running");
        try {
            for(int i = 0; i < 3; i++) {
                Thread.sleep(2000);
                System.out.println("sync1-" + i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sync1 is end");
    }

    /**
     * 同步方法二
     */
    public static synchronized void sync2() {
        System.out.println("sync2 is running");
        try {
            for(int i = 0; i < 3; i++) {
                Thread.sleep(2000);
                System.out.println("sync2-" + i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sync2 is end");
    }
}
对于 synchronized 修饰的静态方法,因为其锁默认为 类.class,所以在多个线程同时运行时,会存在锁竞争的情况,所以会阻塞


下面是对 synchronized(实例对象) 的使用:

public class Synchronized2 {
    String ob1 = new String("sync");
    String ob2 = ob1;

    public static void main(String[] args) {
        final Synchronized2 sy1 = new Synchronized2();
        System.out.println("ob1 == ob2: " + (sy1.ob1 == sy1.ob2));
        System.out.println("ob1.hashCode() == ob2.hashCode(): " + (sy1.ob1.hashCode() == sy1.ob2.hashCode()));

        Thread th1 = new Thread(new Runnable() {
            @Override
            public void run() {
                sy1.syncTest(sy1.ob1);
            }
        }, "th-1");
        Thread th2 = new Thread(new Runnable() {
            @Override
            public void run() {
                sy1.syncTest(sy1.ob2);
            }
        }, "th-2");
        th1.start();
        th2.start();
    }

    public void syncTest(Object sync) {
        synchronized (sync) {
            System.out.println("thread name : " + Thread.currentThread().getName());
            try {
                for(int i = 0; i < 3; i++) {
                    System.out.println(Thread.currentThread().getName() + " : " + i);
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
上面代码运行结果为:

ob1 == ob2: true
ob1.hashCode() == ob2.hashCode(): true
thread name : th-1
th-1 : 0
th-1 : 1
th-1 : 2
thread name : th-2
th-2 : 0
th-2 : 1
th-2 : 2
原因是:两个线程竞争的锁是同一个锁,因为 ob1==ob2  结果为true。


下面看,如果把上面的

String ob1 = new String("sync");
String ob2 = ob1;

改为:

String ob1 = new String("sync");
String ob2 = new String("sync");


结果就为:

ob1 == ob2: false
ob1.hashCode() == ob2.hashCode(): true
thread name : th-2
th-2 : 0
thread name : th-1
th-1 : 0
th-1 : 1
th-2 : 1
th-2 : 2
th-1 : 2

因为此时两个线程竞争的锁不为同一个锁,所以不存在锁竞争。
下面的实例来自网上,原文出自(抱歉,没有保存到)

public class Run {

    public static void main(String[] args) {
        Service service1 = new Service("xiaobaoge");
        ThreadA a = new ThreadA(service1);
        a.setName("A");
        a.start();

        ThreadB b = new ThreadB(service1);
        b.setName("B");
        b.start();
    }
}

class Service {
    String anyString = new String();

    public Service(String anyString){
        this.anyString = anyString;
    }

    public void setUsernamePassword(String username, String password) {
        try {
            synchronized (anyString) {
                System.out.println("线程名称为:" + Thread.currentThread().getName()
                        + "在" + System.currentTimeMillis() + "进入同步块");
                Thread.sleep(1000);
                System.out.println("线程名称为:" + Thread.currentThread().getName()
                        + "在" + System.currentTimeMillis() + "离开同步块");
            }
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

class ThreadA extends Thread {
    private Service service;

    public ThreadA(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.setUsernamePassword("a", "aa");
    }
}

class ThreadB extends Thread {

    private Service service;

    public ThreadB(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.setUsernamePassword("b", "bb");
    }
}
运行结果为:
线程名称为:A在1482044312482进入同步块
线程名称为:A在1482044313483离开同步块
线程名称为:B在1482044313483进入同步块
线程名称为:B在1482044314483离开同步块

因为作为锁的对象为同一个对象  service1 的  anyString 对象,所以存在锁竞争,所以两个进程会按顺序输出
如果我将 main 方法改为如下,结果会是什么呢:

public static void main(String[] args) {
        Service service1 = new Service("xiaobaoge");
        Service service2 = new Service("xiaobaoge");
        System.out.println(service1.anyString == service2.anyString);
        ThreadA a = new ThreadA(service1);
        a.setName("A");
        a.start();

        ThreadB b = new ThreadB(service2);
        b.setName("B");
        b.start();
    }

这次我传的对象为两个不同的对象,那么输出结果是什么呢?答案是不变的:

true
线程名称为:A在1482044611516进入同步块
线程名称为:A在1482044612517离开同步块
线程名称为:B在1482044612517进入同步块
线程名称为:B在1482044613518离开同步块

因为这个例子中,作为锁的不是 两个 service1 与 service2,而是其中的  anyString 字符串对象。写到这,可能细心的读者会发现,在用 String 类型作为锁时,一定要注意,因为在jvm 中存在一个 字符串常量池 的内存区域,这就涉及到有关 String 的内存空间分配的问题了,如:
String  str1 =  "12345";
String  str2 = new  String("12345");
String  str3 = "12345".intern();
他们直接先后顺序不一样,变量的内存地址也是不一样的














  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值