synchronized同步方法
Posted aishangjava
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了synchronized同步方法相关的知识,希望对你有一定的参考价值。
当多个线程同时访问同一对象中的实例变量时,就会出现非线程安全性,从而导致脏读取,即所检索的数据发生更改。线程安全性意味着所获得的实例变量的值是同步的。
方法内的变量是线程安全的
方法中的变量是线程安全的。非线程安全问题存在于实例变量中。如果它是一个方法中的私有变量,就不会有非线程安全问题。实例如下:
1 class HasMethodPrivateNum { 2 public void addI(String username){ 3 try { 4 int num=0; 5 if(username.equals("a")){ 6 num=100; 7 System.out.println("a set over"); 8 Thread.sleep(2000); 9 }else{ 10 num=200; 11 System.out.println("b set over"); 12 } 13 System.out.println(username+" num = "+num); 14 } catch (InterruptedException e) { 15 e.printStackTrace(); 16 } 17 } 18 } 19 20 class ThreadA extends Thread { 21 private HasMethodPrivateNum numRef; 22 public ThreadA(HasMethodPrivateNum numRef){ 23 super(); 24 this.numRef=numRef; 25 } 26 27 @Override 28 public void run() { 29 super.run(); 30 numRef.addI("a"); 31 } 32 } 33 34 class ThreadB extends Thread { 35 private HasMethodPrivateNum numRef; 36 public ThreadB(HasMethodPrivateNum numRef){ 37 super(); 38 this.numRef=numRef; 39 } 40 41 @Override 42 public void run() { 43 super.run(); 44 numRef.addI("b"); 45 } 46 } 47 48 public class Run { 49 public static void main(String[] args) { 50 HasMethodPrivateNum numRef=new HasMethodPrivateNum(); 51 ThreadA threadA=new ThreadA(numRef); 52 threadA.start(); 53 ThreadB threadB=new ThreadB(numRef); 54 threadB.start(); 55 } 56 }
输出结果:
a set over b set over b num = 200 a num = 100
可以看出,该方法中的变量不存在非线性安全问题,并且是线程安全的。
实例变量非线程安全
实例变量是非线程安全的。如果多个线程联合访问对象中的实例变量,则可能出现非线程安全性问题。如果线程访问的对象中有多个实例变量,则运行结果可能会交叉,如果只有一个实例变量,则可能存在覆盖。在这种情况下,您需要将synchronized关键字添加到操作实例变量的方法中。当多个线程访问同一对象中的同步方法时,它必须是线程安全的。
修改上面的代码以将变量作为类中的成员变量放在第一类的addI()方法中:
1 class HasSelfPrivateNum { 2 private int num=0; 3 synchronized public void addI(String username){ 4 try { 5 if(username.equals("a")){ 6 num=100; 7 System.out.println("a set over"); 8 Thread.sleep(2000); 9 }else{ 10 num=200; 11 System.out.println("b set over"); 12 } 13 System.out.println(username+" num = "+num); 14 } catch (InterruptedException e) { 15 e.printStackTrace(); 16 } 17 } 18 }
测试结果如下:
1 a set over 2 b set over 3 b num = 200 4 a num = 200
可以发现,得到的结果是存在线程安全问题的。当为addI()方法加上synchronized关键字之后,测试结果如下:
1 a set over 2 a num = 100 3 b set over 4 b num = 200
可以发现,不存在线程安全问题了。
synchronized关键字获取所有对象锁的锁,而不是使用一段代码或方法作为锁。当多个线程访问同一个对象时,哪个线程首先使用关键字执行方法,哪个线程持有方法所属对象的锁,而其他线程只能等待。但是,如果多个线程访问多个对象,JVM将创建多个锁。
当对象分别具有同步方法a和异步方法b、线程A和线程B访问方法a和方法B时,线程A首先持有对象的锁,但是线程B可以异步调用对象的异步方法B。但是如果两个方法都是同步的,那么当A访问方法a时,它已经持有对象的Lock锁。当B线程调用对象的另一个同步方法时,它也需要等待,即同步。示例代码如下:
1 class MyObject { 2 synchronized public void methodA(){ 3 try { 4 System.out.println("begin methodA in thread: "+Thread.currentThread().getName()); 5 Thread.sleep(5000); 6 System.out.println("end methodA in time:"+System.currentTimeMillis()); 7 } catch (InterruptedException e) { 8 e.printStackTrace(); 9 } 10 } 11 12 public void methodB(){ 13 try { 14 System.out.println("begin methodB in thread: "+Thread.currentThread().getName()+" time:"+System.currentTimeMillis()); 15 Thread.sleep(5000); 16 System.out.println("end methodB"); 17 } catch (InterruptedException e) { 18 e.printStackTrace(); 19 } 20 } 21 22 } 23 24 class ThreadA extends Thread{ 25 private MyObject object; 26 public ThreadA(MyObject object){ 27 super(); 28 this.object=object; 29 } 30 31 @Override 32 public void run() { 33 super.run(); 34 object.methodA(); 35 } 36 } 37 38 class ThreadB extends Thread{ 39 private MyObject object; 40 public ThreadB(MyObject object){ 41 super(); 42 this.object=object; 43 } 44 45 @Override 46 public void run() { 47 super.run(); 48 object.methodB(); 49 } 50 } 51 52 public class Run { 53 public static void main(String[] args) { 54 MyObject object=new MyObject(); 55 ThreadA a=new ThreadA(object); 56 a.setName("A"); 57 ThreadB b=new ThreadB(object); 58 b.setName("B"); 59 a.start(); 60 b.start(); 61 } 62 }
测试结果:
begin methodA in thread: A begin methodB in thread: B time:1544263806800 end methodB end methodA in time:1544263811800
如您所见,线程A首先获得对象对象的锁,但是线程B仍然异步调用异步方法。将同步关键字添加到MultB()之后,测试结果如下:
begin methodA in thread: A end methodA in time:1544264023516 begin methodB in thread: B time:1544264023516 end methodB
可以看到,A线程先得到object的锁,B线程如果此时调用objcet中的同步方法需要等待。
脏读
同步关键字可用于调用相同方法时同步多个线程。虽然分配是同步的,但是当取值时,即当读取实例变量时,该值已被其他线程更改,可能会发生脏读取。因此,还需要同步方法来读取数据。
锁重入
同步锁重入:当使用synchronized时,当线程获得对象锁时,当再次请求对象锁时,它可以再次获得对象的锁。也就是说,他们可以再次获得自己的内部锁。当线程获取对象的锁时,不会释放锁,并且它希望获取对象的锁。如果不允许锁重新进入,就会发生死锁。示例代码:
1 class Service { 2 synchronized public void service1(){ 3 System.out.println("service1"); 4 service2(); 5 } 6 7 synchronized public void service2(){ 8 System.out.println("service2"); 9 service3(); 10 } 11 12 synchronized public void service3(){ 13 System.out.println("service3"); 14 } 15 } 16 class MyThread extends Thread{ 17 @Override 18 public void run() { 19 Service service=new Service(); 20 service.service1(); 21 } 22 } 23 public class Run { 24 public static void main(String[] args) { 25 MyThread t=new MyThread(); 26 t.start(); 27 } 28 }
测试结果:
service1
service2
service3
父子继承环境中还支持可重入锁。当存在父子类继承关系时,子类可以通过“可重新引入的锁”调用父类的同步方法。示例代码如下:
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(); } } } class Sub extends Main{ public synchronized void operateISubMethod() { try { while(i>0){ i--; System.out.println("sub print i="+i); Thread.sleep(100); this.operateIMainMethod(); } } catch (InterruptedException e) { e.printStackTrace(); } } } class MyThread extends Thread { @Override public void run() { Sub sub=new Sub(); sub.operateISubMethod(); } } public class Run { public static void main(String[] args) { MyThread t=new MyThread(); t.start(); } }
测试结果为:
sub print i=9 main print i = 8 sub print i=7 main print i = 6 sub print i=5 main print i = 4 sub print i=3 main print i = 2 sub print i=1 main print i = 0
当线程执行异常代码时,它所持有的锁会自动释放。
同步不具有继承性
如果父类的方法是同步方法,但是子类在不添加同步关键字的情况下重写方法,那么子类的方法在调用子类的方法时仍然不是同步方法,并且需要将同步关键字添加到子类的E方法。
以上是关于synchronized同步方法的主要内容,如果未能解决你的问题,请参考以下文章
JAVA高并发程序设计学习:Synchronized同步代码块具体使用方法