简介
Java并发编程这个领域中synchronized关键字一直都是元老级的角色,很久之前很多人都会称它为“重量级锁”。但是,在JavaSE 1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后变得在某些情况下并不是那么重了。
变量安全性
“非线程安全”问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在“非线程安全”问题,所得结果也就是“线程安全”的了。
如果两个线程同时操作对象中的实例变量,则会出现“非线程安全”,解决办法就是在方法前加上synchronized关键字即可。
多个对象多个锁
HasSelfPrivateNum.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
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(); } } }
|
ThreadA.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
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"); } }
|
ThreadB.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
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"); } }
|
Run.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
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();
} }
|
运行结果:
synchronized关键字1/1.png)
a num=100停顿一会才执行
上面实例中两个线程ThreadA和ThreadB分别访问同一个类的不同实例的相同名称的同步方法,但是效果确实异步执行。
这是因为synchronized取得的锁都是对象锁,而不是把一段代码或方法当做锁。所以在上面的实例中,哪个线程先执行带synchronized关键字的方法,则哪个线程就持有该方法所属对象的锁Lock,那么其他线程只能呈等待状态,前提是多个线程访问的是同一个对象。本例中很显然是两个对象。
在本例中创建了两个HasSelfPrivateNum类对象,所以就产生了两个锁。当ThreadA的引用执行到addI方法中的runThread.sleep(2000)语句时,ThreadB就会“乘机执行”。所以才会导致执行结果如上图所示(备注:由于runThread.sleep(2000),“a num=100”停顿了两秒才输出)
synchronized方法与锁对象
通过上面我们知道synchronized取得的锁都是对象锁,而不是把一段代码或方法当做锁。如果多个线程访问的是同一个对象,哪个线程先执行带synchronized关键字的方法,则哪个线程就持有该方法,那么其他线程只能呈等待状态。如果多个线程访问的是多个对象则不一定,因为多个对象会产生多个锁。
那么我们思考一下当多个线程访问的是同一个对象中的非synchronized类型方法会是什么效果?
答案是:会异步调用非synchronized类型方法,解决办法也很简单在非synchronized类型方法前加上synchronized关键字即可。
脏读
发生脏读的情况实在读取实例变量时,此值已经被其他线程更改过。
PublicVar.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
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(); } } public void getValue() { System.out.println("getValue method thread name=" + Thread.currentThread().getName() + " username=" + username + " password=" + password); } }
|
ThreadC.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
public class ThreadC extends Thread {
private PublicVar publicVar;
public ThreadC(PublicVar publicVar) { super(); this.publicVar = publicVar; }
@Override public void run() { super.run(); publicVar.setValue("B", "BB"); } }
|
Test.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
public class Test { public static void main(String[] args) { try { PublicVar publicVarRef = new PublicVar(); ThreadC thread = new ThreadC(publicVarRef); thread.start();
Thread.sleep(200);
publicVarRef.getValue(); } catch (InterruptedException e) { e.printStackTrace(); }
} }
|
运行结果:
synchronized关键字1/2.png)
getValue()方法前加上synchronized关键字后的运行结果:
synchronized关键字1/3.png)
synchronized锁重入
“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。
Service.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
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"); }
}
|
MyThread.java
1 2 3 4 5 6 7 8 9 10 11 12 13
|
public class MyThread extends Thread { @Override public void run() { Service service = new Service(); service.service1(); }
}
|
Run2.java
1 2 3 4 5 6 7 8 9 10 11
|
public class Run2 { public static void main(String[] args) { MyThread t = new MyThread(); t.start(); } }
|
运行结果:
synchronized关键字1/4.png)
另外可重入锁也支持在父子类继承的环境中
Main.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
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) { e.printStackTrace(); } } }
|
Sub.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
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(); } } }
|
MyThread2.java:
1 2 3 4 5 6 7 8 9 10 11 12 13
|
public class MyThread2 extends Thread { @Override public void run() { Sub sub = new Sub(); sub.operateISubMethod(); }
}
|
Run3.java:
1 2 3 4 5 6 7 8 9 10 11
|
public class Run3 { public static void main(String[] args) { MyThread2 t = new MyThread2(); t.start(); } }
|
运行结果:
synchronized关键字1/5.png)
说明当存在父子类继承关系时,子类是完全可以通过“可重入锁”调用父类的同步方法。
另外出现异常时,其锁持有的锁会自动释放。
同步不具有继承性
如果父类有一个带synchronized关键字的方法,子类继承并重写了这个方法。
但是同步不能继承,所以还是需要在子类方法中添加synchronized关键字。
原文地址:https://blog.csdn.net/qq_34337272/article/details/79655194