文档章节

【Synchronized】对象锁 & 类锁(二)

大白来袭
 大白来袭
发布于 2017/07/10 17:11
字数 2663
阅读 81
收藏 4

一、synchronized关键字

synchronized关键字有如下几种用法:

  1. 非静态方法或静态方法上加入关键字synchronized;
  2. 使用synchronized(对象/this/类.class)静态快;

下面对上述两种情况进行区分。

二、对象锁和类锁

多线程的线程同步机制实际上是靠锁的概念来控制的。

在Java程序运行时环境中,JVM需要对两类线程共享的数据进行协调:

  1. 保存在堆中的实例变量
  2. 保存在方法区中的类变量

这两类数据是被所有线程共享的。 (程序不需要协调保存在Java 栈当中的数据。因为这些数据是属于拥有该栈的线程所私有的) 下面说说在虚拟机中内存的分配

  • 方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。
  • :在Java中,JVM中的栈记录了线程的方法调用。每个线程拥有一个栈。在某个线程的运行过程中,如果有新的方法调用,那么该线程对应的栈就会增加一个存储单元,即帧(frame)。在frame中,保存有该方法调用的参数、局部变量和返回地址。
  • 是JVM中一块可自由分配给对象的区域。当我们谈论垃圾回收(garbage collection)时,我们主要回收堆(heap)的空间。

Java的普通对象存活在堆中。与栈不同,堆的空间不会随着方法调用结束而清空。因此,在某个方法中创建的对象,可以在方法调用结束之后,继续存在于堆中。这带来的一个问题是,如果我们不断的创建新的对象,内存空间将最终消耗殆尽。

在java虚拟机中,每个对象和类在逻辑上都是和一个监视器相关联的。

  • 对于对象来说,相关联的监视器保护对象的实例变量。
  • 对于类来说,监视器保护类的类变量。

(如果一个对象没有实例变量,或者一个类没有变量,相关联的监视器就什么也不监视。)

为了实现监视器的排他性监视能力,java虚拟机为每一个对象和类都关联一个锁。代表任何时候只允许一个线程拥有的特权。线程访问实例变量或者类变量不需锁。

但是如果线程获取了锁,那么在它释放这个锁之前,就没有其他线程可以获取同样数据的锁了。(锁住一个对象就是获取对象相关联的监视器)

类锁实际上用对象锁来实现。当虚拟机装载一个class文件的时候,它就会创建一个java.lang.Class类的实例。当锁住一个对象的时候,实际上锁住的是那个类的Class对象。

一个线程可以多次对同一个对象上锁。对于每一个对象,java虚拟机维护一个加锁计数器,线程每获得一次该对象,计数器就加1,每释放一次,计数器就减 1,当计数器值为0时,锁就被完全释放了。

java编程人员不需要自己动手加锁,对象锁是java虚拟机内部使用的。

在java程序中,只需要使用synchronized块或者synchronized方法就可以标志一个监视区域。当每次进入一个监视区域时,java 虚拟机都会自动锁上对象或者类。

三、synchronized关键字各种用法与实例

synchronized修饰非静态方法、同步代码块的synchronized (this)用法和synchronized (非this对象)的用法锁的是对象,线程想要执行对应同步代码,需要获得对象锁。

synchronized修饰静态方法以及同步代码块的synchronized (类.class)用法锁的是类,线程想要执行对应同步代码,需要获得类锁。

1、同一个对象中的方法锁 ---> 对象锁

先来看一个非线程安全实例:

    public class Run {

        public static void main(String[] args) {

            HasSelfPrivateNum numRef = new HasSelfPrivateNum();

            ThreadA athread = new ThreadA(numRef);
            athread.start();

            ThreadB bthread = new ThreadB(numRef);
            bthread.start();
        }
    }

     
    class HasSelfPrivateNum {

        private int num = 0;

        public void addI(String username) {
            try {
                if (username.equals("a")) {
                    num = 100;
                    System.out.println("a set over!");
                    Thread.sleep(2000);
                } else {
                    num = 200;
                    System.out.println("b set over!");
                }
                System.out.println(username + " num=" + num);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

     

    class ThreadA extends Thread {

        private HasSelfPrivateNum numRef;

        public ThreadA(HasSelfPrivateNum numRef) {
            super();
            this.numRef = numRef;
        }
     
        @Override
        public void run() {
            super.run();
            numRef.addI("a");
        }
     
    }

     

     class ThreadB extends Thread {

        private HasSelfPrivateNum numRef;

        public ThreadB(HasSelfPrivateNum numRef) {
            super();
            this.numRef = numRef;
        }
     
        @Override
        public void run() {
            super.run();
            numRef.addI("b");
        }
    }
运行结果为:

    a set over!
    b set over!
    b num=200
    a num=100

修改HasSelfPrivateNum如下,方法用synchronized修饰如下:

    class HasSelfPrivateNum {

        private int num = 0;

        public synchronized void addI(String username) {
            try {
                if (username.equals("a")) {
                    num = 100;
                    System.out.println("a set over!");
                    Thread.sleep(2000);
                } else {
                    num = 200;
                    System.out.println("b set over!");
                }
                System.out.println(username + " num=" + num);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }    
    }
运行结果是线程安全的:

    b set over!
    b num=200
    a set over!
    a num=100

实验结论:两个线程访问同一个对象中的同步方法是一定是线程安全的。本实现由于是同步访问,所以先打印出a,然后打印出b

这里线程获取的是HasSelfPrivateNum的对象实例的锁——对象锁。

2、多个对象多个锁 ---> 不同步

就上面的实例,我们将Run改成如下:

    public class Run {

        public static void main(String[] args) {

            HasSelfPrivateNum numRef1 = new HasSelfPrivateNum();
            HasSelfPrivateNum numRef2 = new HasSelfPrivateNum();
     
            ThreadA athread = new ThreadA(numRef1);
            athread.start();
     
            ThreadB bthread = new ThreadB(numRef2);
            bthread.start();
        }
    }

运行结果为:

    a set over!
    b set over!
    b num=200
    a num=200

这里是非同步的,因为线程athread获得是numRef1的对象锁,而bthread线程获取的是numRef2的对象锁,他们并没有在获取锁上有竞争关系,因此,出现非同步的结果 。

3、同步块synchronized (this) ---> 同步,对象锁

    public class Run {

        public static void main(String[] args) {

            ObjectService service = new ObjectService();

            ThreadA a = new ThreadA(service);
            a.setName("a");
            a.start();

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

    class ObjectService {

        public void serviceMethod() {
            try {
                synchronized (this) {
                    System.out.println("begin time=" + System.currentTimeMillis());
                    Thread.sleep(2000);
                    System.out.println("end    end=" + System.currentTimeMillis());
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    class ThreadA extends Thread {

        private ObjectService service;

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

        @Override
        public void run() {
            super.run();
            service.serviceMethod();
        }
    }

    class ThreadB extends Thread {

        private ObjectService service;

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

        @Override
        public void run() {
            super.run();
            service.serviceMethod();
        }
    }
运行结果:

    begin time=1466148260341
    end    end=1466148262342
    begin time=1466148262342
    end    end=1466148264378

这样也是同步的,线程获取的是同步块synchronized (this)括号()里面的对象实例的对象锁,这里就是ObjectService实例对象的对象锁了。需要注意的是synchronized (){}的{}前后的代码依旧是异步的。

4、synchronized (非this对象) ---> 对象锁

    public class Run {

        public static void main(String[] args) {

            Service service = new Service("ss");

            ThreadA a = new ThreadA(service);
            a.setName("A");
            a.start();

            ThreadB b = new ThreadB(service);
            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(3000);
                    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");
        }
    }

不难看出,这里线程争夺的是anyString的对象锁,两个线程有竞争同一对象锁的关系,出现同步。现在有一个问题:一个类里面有两个非静态同步方法,会有影响么?

答案是:如果对象实例A,线程1获得了对象A的对象锁,那么其他线程就不能进入,需要获得对象实例A的对象锁才能访问的同步代码(包括同步方法和同步块)。

5、静态synchronized同步方法 ---> 类锁

    public class Run {

        public static void main(String[] args) {

            ThreadA a = new ThreadA();
            a.setName("A");
            a.start();

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

    class Service {

        public synchronized static void printA() {
            try {
                System.out.println("线程名称为:" + Thread.currentThread().getName()
                        + "在" + System.currentTimeMillis() + "进入printA");
                Thread.sleep(3000);
                System.out.println("线程名称为:" + Thread.currentThread().getName()
                        + "在" + System.currentTimeMillis() + "离开printA");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        public synchronized static void printB() {
            System.out.println("线程名称为:" + Thread.currentThread().getName() + "在"
                    + System.currentTimeMillis() + "进入printB");
            System.out.println("线程名称为:" + Thread.currentThread().getName() + "在"
                    + System.currentTimeMillis() + "离开printB");
        }
    }

    class ThreadA extends Thread {

        @Override
        public void run() {
            Service.printA();
        }
    }

    class ThreadB extends Thread {

        @Override
        public void run() {
            Service.printB();
        }
    }
运行结果:

    线程名称为:A在1466149372909进入printA
    线程名称为:A在1466149375920离开printA
    线程名称为:B在1466149375920进入printB
    线程名称为:B在1466149375920离开printB

两个线程在争夺同一个类锁,因此同步

6、synchronized (class) ---> 类锁

对上面Service类代码修改成如下:

    class Service {

        public static void printA() {

            synchronized (Service.class) {
                try {
                    System.out.println("线程名称为:" + Thread.currentThread().getName()
                            + "在" + System.currentTimeMillis() + "进入printA");
                    Thread.sleep(3000);
                    System.out.println("线程名称为:" + Thread.currentThread().getName()
                            + "在" + System.currentTimeMillis() + "离开printA");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        public static void printB() {

            synchronized (Service.class) {
                System.out.println("线程名称为:" + Thread.currentThread().getName()
                        + "在" + System.currentTimeMillis() + "进入printB");
                System.out.println("线程名称为:" + Thread.currentThread().getName()
                        + "在" + System.currentTimeMillis() + "离开printB");
            }
        }
    }
运行结果:

    线程名称为:A在1466149372909进入printA
    线程名称为:A在1466149375920离开printA
    线程名称为:B在1466149375920进入printB
    线程名称为:B在1466149375920离开printB

两个线程依旧在争夺同一个类锁,因此同步。

需要特别说明:对于同一个类A,线程1争夺A对象实例的对象锁,线程2争夺类A的类锁,这两者不存在竞争关系。也就说对象锁和类锁互补干预内政

静态方法则一定会同步,非静态方法需在单例模式才生效,但是也不能都用静态同步方法,总之用得不好可能会给性能带来极大的影响。另外,有必要说一下的是Spring的bean默认是单例的。

~~~~~~~~~~~~~~~end~~~~~~~~~~~~~~~~~

留到作业题,下面的同步代码是否会争锁?

public class TestThreadLock {

	public static void main(String[] args) {
		Service service1 = new Service("service1");
		Service service2 = new Service("service2");
		Thread a = new ThreadA(service1);
		a.start();
		Thread b = new ThreadB(service2);
		b.start();
	}
}

class Service{
	String name = null;
	public Service(String name){
		this.name = name;
	}
	public synchronized void add1(String num){
		try {
				System.out.println(num+"(1):"+System.currentTimeMillis());
				Thread.sleep(3000);
				System.out.println(num+"(1):"+System.currentTimeMillis());
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	public synchronized void add2(String num){
		try {
				System.out.println(num+"(2):"+System.currentTimeMillis());
				Thread.sleep(3000);
				System.out.println(num+"(2):"+System.currentTimeMillis());
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

class ThreadA extends Thread{
	public Service service;
	public ThreadA(Service service){
		this.service = service;
	}
	@Override
	public void run(){
		service.add1("A");
	}
}

class ThreadB extends Thread{
	public Service service;
	public ThreadB(Service service){
		this.service = service;
	}
	@Override
	public void run(){
		service.add2("B");
	}
}

 

本文转载自:https://blog.csdn.net/u013142781/article/details/51697672

大白来袭
粉丝 4
博文 41
码字总数 13667
作品 0
海淀
程序员
私信 提问
java synchronized 关键字详解

Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。当两个并发线程访问同一个对象object中的这个加锁同步...

alonelywolf
2015/12/30
136
0
Java多线程学习(二)

非线程安全:在多个线程对同一个对象中的实例进行并发访问时发生,产生的后果即为脏读。 线程安全:获得的实例变量的值是经过同步处理的,不会出现脏读现象。 非线程安全问题存在于实例变量中...

kakayang2011
2016/02/29
76
0
synchronized 之 对象锁 和 类锁

一、synchronized(object) 如果object没有被加锁,则获取object的锁;如果object已经被加锁则等待object的锁被释放。 二、需要加锁的情景 多线程共享同一资源会引起线程安全的情况下,才需要...

MyOldTime
2018/09/21
44
0
浅析多线程的对象锁和Class锁

一、前言 本来想在另外一篇文章说的,发现可能篇幅有点大,所以还是另开一篇博文来说好了。知识参考《Java多线程编程核心技术》,评价下这本书吧——大量的代码,简单的说明,真像在看博客。...

jmcui
2017/09/08
0
0
synchronized和ReentrantLock区别

一.什么是sychronized sychronized是java中最基本同步互斥的手段,可以修饰代码块,方法,类. 在修饰代码块的时候需要一个reference对象作为锁的对象. 在修饰方法的时候默认是当前对象作为锁的对...

Lubby
2015/05/04
5.3K
0

没有更多内容

加载失败,请刷新页面

加载更多

vue 2打包注意点

使用npm run build打包之后往往直接本地运行,路径类似这样:http://127.0.0.1:5500/xa/dist/index.html 或者http://127.0.0.1:5500/dist/index.html。然后页面打开是空白的,打开控制台查看...

牧云橙
6分钟前
1
0
归并排序

1.原理图 2.代码 public static void merge(int []a,int left,int mid,int right){ int []tmp=new int[a.length];//辅助数组 int p1=left,p2=mid+1,k=left;//p1、p2是检测......

wen123
10分钟前
2
0
css实现透明的两种方法

一、opacity:0~1 值越高,透明度越低: div{opacity:0.5 } 选择器匹配到的节点们,包括节点们的孩子节点,都会实现%50透明,另 0.5 可直接写成 .5 二、rgba(0~255,0~255,0~255,0~1) r...

Bing309
13分钟前
2
0
Tomcat 配置访问路径

此处只是部署完成后idea打开的默认路径,并非项目部署路径, 此处才是项目实际部署路径,可以有多个项目部署路径,idea可以配置默认打开一个

Aeroever
16分钟前
2
0
将ApiBoot Logging采集的日志上报到Admin

通过ApiBoot Logging可以将每一条请求的详细信息获取到,在分布式部署方式中,一个请求可能会经过多个服务,如果是每个服务都独立保存请求日志信息,我们没有办法做到统一的控制,而且还会存...

恒宇少年
17分钟前
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部