synchronized同步方法
Posted moruoxuan
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了synchronized同步方法相关的知识,希望对你有一定的参考价值。
一、方法内的变量是线程安全的
“非线程安全”问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在“非线程安全”问题,所得结果也就是“线程安全”的了
二、实例变量非线程安全
如果多个线程共同访问1个对象中的实例变量,则有可能出现“非线程安全”问题
用线程访问的对象中如果有多个实例变量,则运行的结果有可能出现交叉的情况。
如果对象仅有一个实例变量,则有可能出现覆盖的情况
public 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) { e.printStackTrace(); } } public static void main(String[] args) { HasSelfPrivateNum numRef = new HasSelfPrivateNum(); ThreadA a = new ThreadA(numRef); a.start(); ThreadB b = new ThreadB(numRef); b.start(); } } 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=200
两个线程同时访问num,出现了值覆盖
解决办法是方法前面加上synchronized
//在方法前面加上了 synchronized
synchronized 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) {
e.printStackTrace();
}
}
运行结果
a set over! a num=100 b set over! b num=200
- 运行结果是期望结果。
结论:在两个线程访问同一个对象中的同步方法时一定是线程安全的
三、多个对象多个锁
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!"); Thread.sleep(2000); } else { num = 200; System.out.println("b set over!"); } System.out.println(username + " num=" + num); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { HasSelfPrivateNum numRef1 = new HasSelfPrivateNum(); HasSelfPrivateNum numRef2 = new HasSelfPrivateNum(); ThreadA a = new ThreadA(numRef1); a.start(); ThreadB b = new ThreadB(numRef2); b.start(); } } 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
上面是两个线程分别访问同一个类的两个不同实例的相同名称的同步方法,效果确是以异步运行的
解释:这个示例由于创建了2个业务对象,在系统中产生了2个锁,所以运行结果是异步的,打印效果是先打印b后打印a
为什么使用了synchronized打印顺序却不是同步的,是交叉的?
关键字synchronized取得的锁是对象锁,而不是把一段代码或方法(函数)当做锁,所以在上面的示例中,哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所有属性的锁Lock,那么其他线程只能呈等待状态,前提是多个线程访问的是同一个对象。但是如果是多个线程访问多个对象,则JVM会创建多个锁。上面的示例就是创建了2个HasSelfPrivateNum.java类的对象,所以就会产生出2个锁。
四、synchronized方法与锁对象
public class MyObject { synchronized public void methodA(){ try { System.out.println("begin methodA threadName=" + Thread.currentThread().getName()); Thread.sleep(5000); System.out.println("end endTime="+System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } } public void methodB(){ try { System.out.println("begin methodB threadName=" + Thread.currentThread().getName() + "begin time=" + System.currentTimeMillis()); Thread.sleep(5000); System.out.println("end"); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { MyObject object = new MyObject(); ThreadC c = new ThreadC(object); c.setName("C"); ThreadD d = new ThreadD(object); d.setName("D"); c.start(); d.start(); } } class ThreadC extends Thread{ private MyObject object; public ThreadC(MyObject object){ super(); this.object = object; } @Override public void run(){ super.run(); object.methodA(); } } class ThreadD extends Thread{ private MyObject object; public ThreadD(MyObject object){ super(); this.object = object; } @Override public void run(){ super.run(); object.methodB(); } }
运行结果:
begin methodA threadName=C begin methodB threadName=Dbegin time=1536586015881 end endTime=1536586020882 end
把MyObject中的methodB方法前面加上synchronized
synchronized public void methodB(){ try { System.out.println("begin methodB threadName=" + Thread.currentThread().getName() + "begin time=" + System.currentTimeMillis()); Thread.sleep(5000); System.out.println("end"); } catch (InterruptedException e) { e.printStackTrace(); } }
运行结果:
begin methodA threadName=C end endTime=1536586138271 begin methodB threadName=Dbegin time=1536586138272 end
上述两个运行结果结论:
1)C线程先持有object对象的Lock锁,D线程可以以异步的方式调用object对象中的非synchronized类型的方法
2)C线程先持有object对象的Lock锁,D线程如果在这时调用对象中的synchronized类型的方法则需要等待,也就是同步
五、脏读(dirtyRead)
在赋值的时候进行了同步,但是在取值时有可能出现一些意想不到的状况,这种情况就会出现脏读。脏读这种情况是在读取实例变量时,此值已经被其他线程更改的过了
public class PublicVar { private String username = "a"; private String password = "aa"; synchronized public void setValue(String username, String password) { try { this.username = username; Thread.sleep(1000); this.password = password; System.out.println("setValue method thread name = " + Thread.currentThread().getName() + ", username=" + username + ",password=" + password); } catch (InterruptedException e) { e.printStackTrace(); } } public void getValue() { System.out.println("getValue method thread name = " + Thread.currentThread().getName() + ", username=" + username + ",password=" + password); } public static void main(String[] args) { try { PublicVar v = new PublicVar(); ThreadAA a = new ThreadAA(v); a.start(); Thread.sleep(100); v.getValue(); } catch (InterruptedException e) { e.printStackTrace(); } } } class ThreadAA extends Thread { private PublicVar publicVar; public ThreadAA(PublicVar publicVar) { this.publicVar = publicVar; } @Override public void run() { publicVar.setValue("b", "bb"); } }
运行结果
getValue method thread name = main, username=b,password=aa setValue method thread name = Thread-0, username=b,password=bb
此结果出现了脏读
接下来把getValue()方法加上同步
synchronized public void getValue() { System.out.println("getValue method thread name = " + Thread.currentThread().getName() + ", username=" + username + ",password=" + password); }
运行结果:
setValue method thread name = Thread-0, username=b,password=bb getValue method thread name = main, username=b,password=bb
未出现脏读
出现这种状况就是四中说的synchronized占用锁是占用的是对象
六、synchronized 锁重入
关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时可以再次得到该对象的锁,这也证明了一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远可以得到锁的,如果不可重入锁,就会造成死锁。可重入锁也支持在父子继承环境中(存在父子继承关系时,子类可以通过“可重入锁”调用父类的同步方法)
七、出现异常、锁自动释放
当一个线程执行代码出现异常时,其所持有的锁会自动释放
八、同步不具有继承性
子类重写了父类的synchronized方法,但是子类没有加synchronized,那子类的方法依然是非同步的方法,父类是同步的方法
以上是关于synchronized同步方法的主要内容,如果未能解决你的问题,请参考以下文章
JAVA高并发程序设计学习:Synchronized同步代码块具体使用方法