系列文章传送门:java
Java多线程学习(二)synchronized关键字(1)程序员
java多线程学习(二)synchronized关键字(2) github
Java多线程学习(四)等待/通知(wait/notify)机制编程
系列文章将被优先更新于微信公众号<font color="red">“Java面试通关手册”</font>,欢迎广大Java程序员和爱好技术的人员关注。并发
本节思惟导图:
思惟导图源文件+思惟导图软件关注微信公众号:“Java面试通关手册”回复关键字:“Java多线程”免费领取。
使用<font color="red">synchronized关键字</font>声明方法有些时候是有很大的弊端的,好比咱们有两个线程一个线程A调用同步方法后得到锁,那么另外一个线程B就须要等待A执行完,可是若是说A执行的是一个很费时间的任务的话这样就会很耗时。
先来看一个<font color="red">暴露synchronized方法的缺点实例</font>,而后在看看如何经过synchronized同步语句块解决这个问题。
<font size="2">Task.java</font>
public class Task { private String getData1; private String getData2; public synchronized void doLongTimeTask() { try { System.out.println("begin task"); Thread.sleep(3000); getData1 = "长时间处理任务后从远程返回的值1 threadName=" + Thread.currentThread().getName(); getData2 = "长时间处理任务后从远程返回的值2 threadName=" + Thread.currentThread().getName(); System.out.println(getData1); System.out.println(getData2); System.out.println("end task"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
<font size="2">CommonUtils.java</font>
public class CommonUtils { public static long beginTime1; public static long endTime1; public static long beginTime2; public static long endTime2; }
<font size="2">MyThread1.java</font>
public class MyThread1 extends Thread { private Task task; public MyThread1(Task task) { super(); this.task = task; } @Override public void run() { super.run(); CommonUtils.beginTime1 = System.currentTimeMillis(); task.doLongTimeTask(); CommonUtils.endTime1 = System.currentTimeMillis(); } }
<font size="2">MyThread2.java</font>
public class MyThread2 extends Thread { private Task task; public MyThread2(Task task) { super(); this.task = task; } @Override public void run() { super.run(); CommonUtils.beginTime2 = System.currentTimeMillis(); task.doLongTimeTask(); CommonUtils.endTime2 = System.currentTimeMillis(); } }
<font size="2">Run.java</font>
public class Run { public static void main(String[] args) { Task task = new Task(); MyThread1 thread1 = new MyThread1(task); thread1.start(); MyThread2 thread2 = new MyThread2(task); thread2.start(); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } long beginTime = CommonUtils.beginTime1; if (CommonUtils.beginTime2 < CommonUtils.beginTime1) { beginTime = CommonUtils.beginTime2; } long endTime = CommonUtils.endTime1; if (CommonUtils.endTime2 > CommonUtils.endTime1) { endTime = CommonUtils.endTime2; } System.out.println("耗时:" + ((endTime - beginTime) / 1000)); } }
<font size="2">运行结果:</font>
从运行时间上来看,synchronized方法的问题很明显。能够<font color="red">使用synchronized同步块来解决这个问题</font>。可是要注意synchronized同步块的使用方式,若是synchronized同步块使用很差的话并不会带来效率的提高。
修改上例中的Task.java以下:
public class Task { private String getData1; private String getData2; public void doLongTimeTask() { try { System.out.println("begin task"); Thread.sleep(3000); String privateGetData1 = "长时间处理任务后从远程返回的值1 threadName=" + Thread.currentThread().getName(); String privateGetData2 = "长时间处理任务后从远程返回的值2 threadName=" + Thread.currentThread().getName(); synchronized (this) { getData1 = privateGetData1; getData2 = privateGetData2; } System.out.println(getData1); System.out.println(getData2); System.out.println("end task"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
<font size="2">运行结果:</font>
从上面代码能够看出<font color="red">当一个线程访问一个对象的synchronized同步代码块时,另外一个线程任然能够访问该对象非synchronized同步代码块</font>。
时间虽然缩短了,可是你们考虑一下synchronized代码块真的是同步的吗?它真的持有当前调用对象的锁吗?
<font color="red">是的。不在synchronized代码块中就异步执行,在synchronized代码块中就是同步执行。</font>
验证代码:synchronizedDemo1包下
<font size="2">MyObject.java</font>
public class MyObject { }
<font size="2">Service.java</font>
public class Service { public void testMethod1(MyObject object) { synchronized (object) { try { System.out.println("testMethod1 ____getLock time=" + System.currentTimeMillis() + " run ThreadName=" + Thread.currentThread().getName()); Thread.sleep(2000); System.out.println("testMethod1 releaseLock time=" + System.currentTimeMillis() + " run ThreadName=" + Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } } }
<font size="2">ThreadA.java</font>
public class ThreadA extends Thread { private Service service; private MyObject object; public ThreadA(Service service, MyObject object) { super(); this.service = service; this.object = object; } @Override public void run() { super.run(); service.testMethod1(object); } }
<font size="2">ThreadB.java</font>
public class ThreadB extends Thread { private Service service; private MyObject object; public ThreadB(Service service, MyObject object) { super(); this.service = service; this.object = object; } @Override public void run() { super.run(); service.testMethod1(object); } }
<font size="2"> Run1_1.java</font>
public class Run1_1 { public static void main(String[] args) { Service service = new Service(); MyObject object = new MyObject(); ThreadA a = new ThreadA(service, object); a.setName("a"); a.start(); ThreadB b = new ThreadB(service, object); b.setName("b"); b.start(); } }
<font size="2">运行结果:</font>
能够看出以下图所示,<font color="red">两个线程使用了同一个“对象监视器”,因此运行结果是同步的。</font>
<font color="red">那么,若是使用不一样的对象监视器会出现什么效果呢?</font>
修改Run1_1.java以下:
public class Run1_2 { public static void main(String[] args) { Service service = new Service(); MyObject object1 = new MyObject(); MyObject object2 = new MyObject(); ThreadA a = new ThreadA(service, object1); a.setName("a"); a.start(); ThreadB b = new ThreadB(service, object2); b.setName("b"); b.start(); } }
<font size="2">运行结果:</font>
能够看出以下图所示,<font color="red">两个线程使用了不一样的“对象监视器”,因此运行结果不是同步的了。</font>
当一个对象访问synchronized(this)代码块时,其余线程对同一个对象中全部其余synchronized(this)代码块代码块的访问将被阻塞,这说明<font color="red">synchronized(this)代码块使用的“对象监视器”是一个。</font>
也就是说<font color="red">和synchronized方法同样,synchronized(this)代码块也是锁定当前对象的。</font>
另外经过上面的学习咱们能够得出<font color="red">两个结论</font>。
<font color="red">synchronized关键字加到static静态方法和synchronized(class)代码块上都是是给Class类上锁,而synchronized关键字加到非static静态方法上是给对象上锁。</font>
<font size="2">Service.java</font>
package ceshi; public 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(); } } } synchronized public static void printB() { System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB"); System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB"); } synchronized public void printC() { System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printC"); System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printC"); } }
<font size="2">ThreadA.java</font>
public class ThreadA extends Thread { private Service service; public ThreadA(Service service) { super(); this.service = service; } @Override public void run() { service.printA(); } }
<font size="2">ThreadB.java</font>
public class ThreadB extends Thread { private Service service; public ThreadB(Service service) { super(); this.service = service; } @Override public void run() { service.printB(); } }
<font size="2">ThreadC.java</font>
public class ThreadC extends Thread { private Service service; public ThreadC(Service service) { super(); this.service = service; } @Override public void run() { service.printC(); } }
<font size="2">Run.java</font>
public class Run { public static void main(String[] args) { Service service = new Service(); ThreadA a = new ThreadA(service); a.setName("A"); a.start(); ThreadB b = new ThreadB(service); b.setName("B"); b.start(); ThreadC c = new ThreadC(service); c.setName("C"); c.start(); } }
<font size="2">运行结果:</font>
从运行结果能够看出:静态同步synchronized方法与synchronized(class)代码块持有的锁同样,都是Class锁,Class锁对对象的全部实例起做用。synchronized关键字加到非static静态方法上持有的是对象锁。
线程A,B和线程C持有的锁不同,因此A和B运行同步,可是和C运行不一样步。
<font color="red">在Jvm中具备String常量池缓存的功能</font>
String s1 = "a"; String s2="a"; System.out.println(s1==s2);//true
上面代码输出为true.<font color="red">这是为何呢?</font>
字符串常量池中的字符串只存在一份! 即执行完第一行代码后,常量池中已存在 “a”,那么s2不会在常量池中申请新的空间,而是直接把已存在的字符串内存地址返回给s2。
由于数据类型String的常量池属性,因此synchronized(string)在使用时某些状况下会出现一些问题,好比两个线程运行
synchronized("abc"){
}和
synchronized("abc"){
}修饰的方法时,这两个线程就会持有相同的锁,致使某一时刻只有一个线程能运行。因此尽可能不要使用synchronized(string)而使用synchronized(object)
参考:
《Java多线程编程核心技术》
《Java并发编程的艺术》
若是你以为博主的文章不错,欢迎转发点赞。你能从中学到知识就是我最大的幸运。
欢迎关注个人微信公众号:“Java面试通关手册”(分享各类Java学习资源,面试题,以及企业级Java实战项目回复关键字免费领取)。另外我建立了一个Java学习交流群(群号:174594747),欢迎你们加入一块儿学习,这里更有面试,学习视频等资源的分享。