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

1、synchronized关键字

synchronized关键字有以下几种用法:javascript

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

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

2、对象锁和类锁

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

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

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

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

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

Java的普通对象存活在堆中。与栈不一样,堆的空间不会随着方法调用结束而清空。所以,在某个方法中建立的对象,能够在方法调用结束以后,继续存在于堆中。这带来的一个问题是,若是咱们不断的建立新的对象,内存空间将最终消耗殆尽。异步

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

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

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

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

可是若是线程获取了锁,那么在它释放这个锁以前,就没有其余线程能够获取一样数据的锁了。(锁住一个对象就是获取对象相关联的监视器)spa

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

一个线程能够屡次对同一个对象上锁。对于每个对象,java虚拟机维护一个加锁计数器,线程每得到一次该对象,计数器就加1,每释放一次,计数器就减 1,当计数器值为0时,锁就被彻底释放了。

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

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

3、synchronized关键字各类用法与实例

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

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

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

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

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的对象实例的锁——对象锁。

二、多个对象多个锁 ---> 不一样步

就上面的实例,咱们将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的对象锁,他们并无在获取锁上有竞争关系,所以,出现非同步的结果 。

三、同步块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 (){}的{}先后的代码依旧是异步的。

四、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的对象锁才能访问的同步代码(包括同步方法和同步块)。

五、静态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

两个线程在争夺同一个类锁,所以同步

六、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");
	}
}
相关文章
相关标签/搜索