java多线程(二)——锁机制synchronized(同步方法)

时间:2021-11-22 13:01:00

synchronized

  Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。然而,当一个线程访问object的一个加锁代码块时,另一个线程仍然可以访问该object中的非加锁代码块。

                                                        ——以上来源百度百科

一、方法内的变量为线程安全

“非线程安全”的问题存在于“实例变量”中,如果是方法内部的私有变量,则不会存在“非线程安全”问题,所得结果就是“线程安全”的了。

 MyService类

 1 package com.mythread.www.day8.testSyn.ep1;
 2 
 3 public class MyService {
 4     public void add(String name) {
 5         try {
 6             int num = 0;
 7             if (name.equals("a")) {
 8                 num = 100;
 9                 System.out.println("a is over");
10                 Thread.sleep(1000);
11             } else {
12                 num = 200;
13                 System.out.println("b is over");
14             }
15             System.out.println(name + " num = " + num);
16         } catch (InterruptedException e) {
17             e.printStackTrace();
18         }
19     }
20 }

线程类A

 1 package com.mythread.www.day8.testSyn.ep1;
 2 
 3 public class ThreadA extends Thread {
 4     private MyService service;
 5 
 6     public ThreadA(MyService service) {
 7         super();
 8         this.service = service;
 9     }
10     
11     @Override
12     public void run() {
13         service.add("a");
14     }
15 }

线程类B

 1 package com.mythread.www.day8.testSyn.ep1;
 2 
 3 public class ThreadB extends Thread {
 4     private MyService service;
 5 
 6     public ThreadB(MyService service) {
 7         super();
 8         this.service = service;
 9     }
10     
11     @Override
12     public void run() {
13         service.add("b");
14     }
15 }

运行类

 1 package com.mythread.www.day8.testSyn.ep1;
 2 
 3 public class Run {
 4     public static void main(String[] args) {
 5         MyService service = new MyService();
 6         ThreadA threadA = new ThreadA(service);
 7         threadA.start();
 8         ThreadB threadB = new ThreadB(service);
 9         threadB.start();
10     }
11 }

结果

1 a is over
2 b is over
3 b num = 200
4 a num = 100

从运行结果来看,方法中的变量不存在非线程安全的问题,永远都是线程安全的,这事方法内部的变量是私有的特性造成的。

二、实例变量非线程安全

 MyService类

 1 package com.mythread.www.day8.testSyn.ep1;
 2 
 3 public class MyService {
 4     private int num = 0;
 5     public void add(String name) {
 6         try {
 7             
 8             if (name.equals("a")) {
 9                 num = 100;
10                 System.out.println("a is over");
11                 Thread.sleep(1000);
12             } else {
13                 num = 200;
14                 System.out.println("b is over");
15             }
16             System.out.println(name + " num = " + num);
17         } catch (InterruptedException e) {
18             e.printStackTrace();
19         }
20     }
21 }

线程类和运行类同上,运行结果

1 a is over
2 b is over
3 b num = 200
4 a num = 200

产生这个结果的原因是两个线程同事访问同一个没有同步的方法,如果两个对象同时操作对象中的实例变量,可能会造成非线程安全的问题

最简单的解决方案是在方法的前面加个synchronized同步锁

MyService类

 1 package com.mythread.www.day8.testSyn.ep1;
 2 
 3 public class MyService {
 4     private int num = 0;
 5     synchronized public void add(String name) {
 6         try {
 7             
 8             if (name.equals("a")) {
 9                 num = 100;
10                 System.out.println("a is over");
11                 Thread.sleep(1000);
12             } else {
13                 num = 200;
14                 System.out.println("b is over");
15             }
16             System.out.println(name + " num = " + num);
17         } catch (InterruptedException e) {
18             e.printStackTrace();
19         }
20     }
21 }

线程类和运行类同上,运行结果

1 a is over
2 a num = 100
3 b is over
4 b num = 200

在两个线程访问同一个对象中的同步方法时一定是线程安全的,上面的代码由于时同步访问,所以先打印出a,然后在打印出b

改一下运行类,其他类同上

 1 package com.mythread.www.day8.testSyn.ep1;
 2 
 3 public class Run {
 4     public static void main(String[] args) {
 5         MyService serviceA = new MyService();
 6         MyService serviceB = new MyService();
 7         ThreadA threadA = new ThreadA(serviceA);
 8         threadA.start();
 9         ThreadB threadB = new ThreadB(serviceB);
10         threadB.start();
11     }
12 }

结果

1 a is over
2 b is over
3 b num = 200
4 a num = 100

这是两个线程分别访问同一个类的两个不同实例的相同名称的同步方法,效果却是以异步的形式来执行的。

因为创建了两个业务对象,在系统中产生了两个锁,所以运行结果是异步的。

关键字synchronized所取得的锁都是对象锁,而不是把一段代码或者方法当作锁。

My Service类

 1 package com.mythread.www.day8.testSyn.ep1;
 2 
 3 public class MyService {
 4     synchronized public void methodA() {
 5         try {
 6             System.out.println("begin methodA threadName = " + Thread.currentThread().getName());
 7             Thread.sleep(3000);
 8             System.out.println("end methodA time = " + System.currentTimeMillis());
 9         } catch (InterruptedException e) {
10             e.printStackTrace();
11         }
12     }
13     
14     public void methodB() {
15         try {
16             System.out.println("begin methodB threadName = " + Thread.currentThread().getName());
17             Thread.sleep(3000);
18             System.out.println("end methodB time = " + System.currentTimeMillis());
19         } catch (InterruptedException e) {
20             e.printStackTrace();
21         }
22     }
23 }

线程类A

 1 package com.mythread.www.day8.testSyn.ep1;
 2 
 3 public class ThreadA extends Thread {
 4     private MyService service;
 5 
 6     public ThreadA(MyService service) {
 7         super();
 8         this.service = service;
 9     }
10     
11     @Override
12     public void run() {
13         service.methodA();
14     }
15 }

线程类B

 1 package com.mythread.www.day8.testSyn.ep1;
 2 
 3 public class ThreadB extends Thread {
 4     private MyService service;
 5 
 6     public ThreadB(MyService service) {
 7         super();
 8         this.service = service;
 9     }
10     
11     @Override
12     public void run() {
13         service.methodB();
14     }
15 }

运行类

 1 package com.mythread.www.day8.testSyn.ep1;
 2 
 3 public class Run {
 4     public static void main(String[] args) {
 5         MyService service = new MyService();
 6         ThreadA threadA = new ThreadA(service);
 7         threadA.start();
 8         ThreadB threadB = new ThreadB(service);
 9         threadB.start();
10     }
11 }

结果

1 begin methodA threadName = Thread-1
2 begin methodB threadName = Thread-2
3 end methodB time = 1458400534384
4 end methodA time = 1458400534384

在My Service的methodB前面也加上关键字synchronized

My Service类

 1 package com.mythread.www.day8.testSyn.ep1;
 2 
 3 public class MyService {
 4     synchronized public void methodA() {
 5         try {
 6             System.out.println("begin methodA threadName = " + Thread.currentThread().getName());
 7             Thread.sleep(3000);
 8             System.out.println("end methodA time = " + System.currentTimeMillis());
 9         } catch (InterruptedException e) {
10             e.printStackTrace();
11         }
12     }
13     
14     synchronized public void methodB() {
15         try {
16             System.out.println("begin methodB threadName = " + Thread.currentThread().getName());
17             Thread.sleep(3000);
18             System.out.println("end methodB time = " + System.currentTimeMillis());
19         } catch (InterruptedException e) {
20             e.printStackTrace();
21         }
22     }
23 }

结果

1 begin methodA threadName = Thread-1
2 end methodA time = 1458400619034
3 begin methodB threadName = Thread-2
4 end methodB time = 1458400622035

对比上面两次代码的运行结果。

在第一次运行时,当A线程先持有My Service的同步锁时,B线程可以已异步的方式去调用My Service对象中的非synchronized方法。

在第二次运行时,当A线程先持有My Service的同步锁时,当B想调用My Service对象中的synchronized,则需要先等A释放对象锁。

所以,synchronized锁住的时对象,而不是其他的一些东西。

三、synchronized锁重入

当一个线程获得一个对象锁后,再次请求此对象锁时是可以再次获得此对象锁的

My Service类

 1 package com.mythread.www.day8.testSyn.ep1;
 2 
 3 public class MyService {
 4     synchronized public void methodA() {
 5         System.out.println("methodA");
 6         methodB();
 7     }
 8     
 9     synchronized public void methodB() {
10         System.out.println("methodB");
11         methodC();
12     }
13     
14     synchronized public void methodC() {
15         System.out.println("methodC");
16     }
17 }

线程类

1 package com.mythread.www.day8.testSyn.ep1;
2 
3 public class MyThread extends Thread {
4     @Override
5     public void run() {
6         MyService myService = new MyService();
7         myService.methodA();
8     }
9 }

运行类

1 package com.mythread.www.day8.testSyn.ep1;
2 
3 public class Run {
4     public static void main(String[] args) {
5         MyThread myThread = new MyThread();
6         myThread.start();
7     }
8 }

结果

1 methodA
2 methodB
3 methodC

自己还可以重新获得自己的内部锁,如果不可以的话,上面的这个Demo则会造成死锁现象

Main类

 1 package com.weishiyao.learn.day4.testThread;
 2 
 3 public class Main {
 4     public int i = 10;
 5     synchronized public void operateIMainMethod() {
 6         try {
 7             i--;
 8             System.out.println("main print i=" + i);
 9             Thread.sleep(100);
10         } catch (Exception e) {
11             e.printStackTrace();
12         }
13     }
14 }

Sub类

 1 package com.weishiyao.learn.day4.testThread;
 2 
 3 public class Sub extends Main{
 4     synchronized public void operateISubMethod() {
 5         try {
 6             while (i > 0) {
 7                 i--;
 8                 System.out.println("sub print i=" + i);
 9                 Thread.sleep(100);
10                 this.operateIMainMethod();
11             }
12         } catch (Exception e) {
13             e.printStackTrace();
14         }
15     }
16 }

线程类

1 package com.weishiyao.learn.day4.testThread;
2 
3 public class MyThread  extends Thread{
4     @Override
5     public void run() {
6         Sub sub = new Sub();
7         sub.operateISubMethod();
8     }
9 }

运行类

1 package com.weishiyao.learn.day4.testThread;
2 
3 public class Run {
4     public static void main(String[] args) {
5         MyThread t = new MyThread();
6         t.run();
7     }
8 }

结果

 1 sub print i=9
 2 main print i=8
 3 sub print i=7
 4 main print i=6
 5 sub print i=5
 6 main print i=4
 7 sub print i=3
 8 main print i=2
 9 sub print i=1
10 main print i=0

当存在父子类继承关系时,子类完全可以通过父类“可重入锁”调用父类的同步方法