转载请备注地址: https://blog.csdn.net/qq_3433...java
Java多线程学习(二)将分为两篇文章介绍synchronized同步方法另外一篇介绍synchronized同步语句块。
系列文章传送门:程序员
Java多线程学习(二)synchronized关键字(1)编程
java多线程学习(二)synchronized关键字(2) 安全
Java多线程学习(四)等待/通知(wait/notify)机制多线程
系列文章将被优先更新与微信公众号<font color="red">“Java面试通关手册”</font>,欢迎广大Java程序员和爱好技术的人员关注。
本节思惟导图:
思惟导图源文件+思惟导图软件关注微信公众号:<font color="red">“Java面试通关手册”</font>回复关键字:<font color="red">“Java多线程”</font>免费领取。
Java并发编程这个领域中<font color="red">synchronized关键字</font>一直都是元老级的角色,好久以前不少人都会称它为<font color="red">“重量级锁”</font>。可是,在JavaSE 1.6以后进行了主要包括为了<font color="red">减小得到锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁</font>以及其它各类优化以后变得在某些状况下并非那么重了。
这一篇文章不会介绍synchronized关键字的实现原理,更多的是synchronized关键字的使用。若是想要了解的能够看看方腾飞的《Java并发编程的艺术》。
<font color="red">“非线程安全”</font>问题存在于<font color="red">“实例变量”</font>中,若是是<font color="red">方法内部的私有变量</font>,则不存在<font color="red">“非线程安全”</font>问题,所得结果也就是<font color="red">“线程安全”</font>的了。
若是两个线程同时操做对象中的实例变量,则会出现<font color="red">“非线程安全”</font>,解决办法就是在方法前加上<font color="red">synchronized关键字</font>便可。前面一篇文章咱们已经讲过,并且贴过相应代码,因此这里就再也不贴代码了。
先看例子:
<font size="2">HasSelfPrivateNum.java</font>
public class HasSelfPrivateNum { private int num = 0; synchronized public void addI(String username) { try { if (username.equals("a")) { num = 100; System.out.println("a set over!"); //若是去掉hread.sleep(2000),那么运行结果就会显示为同步的效果 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(); } } }
<font size="2">ThreadA.java</font>
public class ThreadA extends Thread { private HasSelfPrivateNum numRef; public ThreadA(HasSelfPrivateNum numRef) { super(); this.numRef = numRef; } @Override public void run() { super.run(); numRef.addI("a"); } }
<font size="2">ThreadB.java</font>
public class ThreadB extends Thread { private HasSelfPrivateNum numRef; public ThreadB(HasSelfPrivateNum numRef) { super(); this.numRef = numRef; } @Override public void run() { super.run(); numRef.addI("b"); } }
<font size="2">Run.java</font>
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(); } }
<font size="2">运行结果:</font>
a num=100停顿一会才执行
上面实例中两个线程ThreadA和ThreadB分别访问同一个类的不一样实例的相同名称的同步方法,可是效果确实异步执行。
<font color="red">为何会这样呢?</font>
这是由于<font color="red">synchronized取得的锁都是对象锁,而不是把一段代码或方法当作锁</font>。因此在上面的实例中,哪一个线程先执行带synchronized关键字的方法,则哪一个线程就持有该方法<font color="red">所属对象的锁Lock</font>,那么其余线程只能呈等待状态,<font color="red">前提是多个线程访问的是同一个对象</font>。本例中很显然是两个对象。
在本例中建立了两个HasSelfPrivateNum类对象,因此就<font color="red">产生了两个锁</font>。当ThreadA的引用执行到addI方法中的runThread.sleep(2000)语句时,ThreadB就会<font color="red">“伺机执行”</font>。因此才会致使执行结果如上图所示(备注:因为runThread.sleep(2000),“a num=100”停顿了两秒才输出)
经过上面咱们知道<font color="red">synchronized取得的锁都是对象锁,而不是把一段代码或方法当作锁</font>。若是多个线程访问的是同一个对象,哪一个线程先执行带synchronized关键字的方法,则哪一个线程就持有该方法,那么其余线程只能呈等待状态。若是多个线程访问的是多个对象则不必定,由于多个对象会产生多个锁。
<font color="red">那么咱们思考一下当多个线程访问的是同一个对象中的非synchronized类型方法会是什么效果?</font>
答案是:会异步调用非synchronized类型方法,解决办法也很简单在非synchronized类型方法前加上synchronized关键字便可。
发生脏读的状况实在读取实例变量时,此值已经被其余线程更改过。
<font size="2">PublicVar.java</font>
public class PublicVar { public String username = "A"; public String password = "AA"; synchronized public void setValue(String username, String password) { try { this.username = username; Thread.sleep(5000); this.password = password; System.out.println("setValue method thread name=" + Thread.currentThread().getName() + " username=" + username + " password=" + password); } catch (InterruptedException e) { e.printStackTrace(); } } //该方法前加上synchronized关键字就同步了 public void getValue() { System.out.println("getValue method thread name=" + Thread.currentThread().getName() + " username=" + username + " password=" + password); } }
<font size="2">ThreadA.java</font>
public class ThreadA extends Thread { private PublicVar publicVar; public ThreadA(PublicVar publicVar) { super(); this.publicVar = publicVar; } @Override public void run() { super.run(); publicVar.setValue("B", "BB"); } }
<font size="2">Test.java</font>
public class Test { public static void main(String[] args) { try { PublicVar publicVarRef = new PublicVar(); ThreadA thread = new ThreadA(publicVarRef); thread.start(); Thread.sleep(200);//打印结果受此值大小影响 publicVarRef.getValue(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
<font size="2">运行结果:</font>
解决办法:getValue()方法前加上synchronized关键字便可。
<font size="2">加上synchronized关键字后的运行结果:</font>
<font color="red">“可重入锁”</font>概念是:<font color="red">本身能够再次获取本身的内部锁</font>。好比一个线程得到了某个对象的锁,此时这个对象锁尚未释放,当其再次想要获取这个对象的锁的时候仍是能够获取的,若是不可锁重入的话,就会形成死锁。
<font size="2">Service.java</font>
public class Service { synchronized public void service1() { System.out.println("service1"); service2(); } synchronized public void service2() { System.out.println("service2"); service3(); } synchronized public void service3() { System.out.println("service3"); } }
<font size="2">MyThread.java</font>
public class MyThread extends Thread { @Override public void run() { Service service = new Service(); service.service1(); } }
<font size="2">Run.java</font>
public class Run { public static void main(String[] args) { MyThread t = new MyThread(); t.start(); } }
<font size="2">运行结果:</font>
另外<font color="red">可重入锁也支持在父子类继承的环境中</font>
<font size="2">Main.java:</font>
public class Main { public int i = 10; synchronized public void operateIMainMethod() { try { i--; System.out.println("main print i=" + i); Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
<font size="2">Sub.java:</font>
public class Sub extends Main { synchronized public void operateISubMethod() { try { while (i > 0) { i--; System.out.println("sub print i=" + i); Thread.sleep(100); this.operateIMainMethod(); } } catch (InterruptedException e) { e.printStackTrace(); } } }
<font size="2">MyThread.java:</font>
public class MyThread extends Thread { @Override public void run() { Sub sub = new Sub(); sub.operateISubMethod(); } }
<font size="2">Run.java:</font>
public class Run { public static void main(String[] args) { MyThread t = new MyThread(); t.start(); } }
<font size="2">运行结果:</font>
说明当存在父子类继承关系时,子类是彻底能够经过“可重入锁”调用父类的同步方法。
另外出现异常时,其锁持有的锁会自动释放。
若是父类有一个带synchronized关键字的方法,子类继承并重写了这个方法。
可是同步不能继承,因此仍是须要在子类方法中添加synchronized关键字。
参考:
《Java多线程编程核心技术》
欢迎关注个人微信公众号:"Java面试通关手册"(分享各类Java学习资源,面试题,以及企业级Java实战项目回复关键字免费领取):