synchronized关键字有以下几种用法:javascript
下面对上述两种状况进行区分。java
多线程的线程同步机制其实是靠锁的概念来控制的。编程
在Java程序运行时环境中,JVM须要对两类线程共享的数据进行协调:安全
这两类数据是被全部线程共享的。 (程序不须要协调保存在Java 栈当中的数据。由于这些数据是属于拥有该栈的线程所私有的) 下面说说在虚拟机中内存的分配:多线程
Java的普通对象存活在堆中。与栈不一样,堆的空间不会随着方法调用结束而清空。所以,在某个方法中建立的对象,能够在方法调用结束以后,继续存在于堆中。这带来的一个问题是,若是咱们不断的建立新的对象,内存空间将最终消耗殆尽。异步
在java虚拟机中,每一个对象和类在逻辑上都是和一个监视器相关联的。ide
(若是一个对象没有实例变量,或者一个类没有变量,相关联的监视器就什么也不监视。)性能
为了实现监视器的排他性监视能力,java虚拟机为每个对象和类都关联一个锁。表明任什么时候候只容许一个线程拥有的特权。线程访问实例变量或者类变量不需锁。this
可是若是线程获取了锁,那么在它释放这个锁以前,就没有其余线程能够获取一样数据的锁了。(锁住一个对象就是获取对象相关联的监视器)spa
类锁实际上用对象锁来实现。当虚拟机装载一个class文件的时候,它就会建立一个java.lang.Class类的实例。当锁住一个对象的时候,实际上锁住的是那个类的Class对象。
一个线程能够屡次对同一个对象上锁。对于每个对象,java虚拟机维护一个加锁计数器,线程每得到一次该对象,计数器就加1,每释放一次,计数器就减 1,当计数器值为0时,锁就被彻底释放了。
java编程人员不须要本身动手加锁,对象锁是java虚拟机内部使用的。
在java程序中,只须要使用synchronized块或者synchronized方法就能够标志一个监视区域。当每次进入一个监视区域时,java 虚拟机都会自动锁上对象或者类。
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的对象锁,他们并无在获取锁上有竞争关系,所以,出现非同步的结果 。
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 (){}的{}先后的代码依旧是异步的。
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的对象锁才能访问的同步代码(包括同步方法和同步块)。
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
两个线程在争夺同一个类锁,所以同步
对上面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"); } }