Java线程同步与单例模式的结合学习

最近接触了一个项目的业务,是web客户端执行的备份和还原操作问题,当一个客户端点击了备份或还原操作时,在其它的web客户端则不能同时进行备份或还原,直到第一个客户端执行完全(需要5个小时),才能进行备份还原操作,并且这个项目是一个重构项目,不能影响之前的业务。

在这里我们采用的是线程同步+单例的处理办法来解决的,改最少的代码最终解决问题,自己也好好的学习了一下java线程同步和单例,闲来无事,自己对线程这一块进行了整理,把自己的一点收获写下来。

 

1、 A线程在访问单例service的同步方法a的时候,其他线程能否访问service的同步方法a?同步方法b?非同步方法c?

2、 两个以上线程同时访问同一个对象的同一个变量的情况下,会出现什么情况?

3、 当service不是单例(普通实例)的情况下,A线程在访问service实例的同步方法a的时候,其他线程能否访问service实例的同步方法a?同步方法b?非同步方法c?

 

带着上面的问题,直接贴代码

下面是一个单例类,不用多做介绍: 1 package treadTest.threadAccess;
2
3 import java.util.ArrayList;
4 import java.util.List;
5
6
7 /**
8 *
9 * @author yhd
10 * 单例类
11 */
12 public class SingletonService {
13 private static SingletonService service;
14 private List<Integer> list;
15 List<Integer> newlist = new ArrayList<Integer>();
16
17
18 private SingletonService(){}
19
20
21 public static SingletonService getInstance(){
22 if(null == service){
23 service = new SingletonService();
24 }
25 return service;
26 }
27
28
29 //同步方法A
30 public synchronized void testA(){
31 try {
32 System.out.println("A 在执行" + Thread.currentThread());
33 Thread.sleep(5000);
34 } catch (InterruptedException e) {
35 e.printStackTrace();
36 }
37 }
38
39 //同步方法B
40 public synchronized void testB(){
41 try {
42 System.out.println("B 在执行" + Thread.currentThread());
43 Thread.sleep(5000);
44 } catch (InterruptedException e) {
45 e.printStackTrace();
46 }
47 }
48
49 //非同步方法C
50 public void testC(){
51 System.out.println("C 在执行" + Thread.currentThread());
52 try {
53 Thread.sleep(5000);
54 } catch (InterruptedException e) {
55 e.printStackTrace();
56 }
57 }
58
59 //非同步方法C1
60 public void testC1(){
61 System.out.println("C1 在执行" + Thread.currentThread());
62 }
63
64
65 public void setList(List<Integer> list){
66 this.list = list;
67 }
68 //非同步方法D 传入参数
69 public void testD(){
70
71 System.out.println("D 在执行"+ Thread.currentThread());
72
73 for(int num:list){
74 System.out.println(num +" "+Thread.currentThread() + this.toString());
75 newlist.add(num);
76 }
77 System.out.println(newlist.toString()+"list的长度"+newlist.size());
78 }
79
80 }

下面是一个单例线程的测试类,主要是对上面的单例进行各种线程访问方法的测试

 1 package treadTest;
 2 
 3 /**
 4  * 
 5  * @author yhd
 6  * 单例模式线程测试类
 7  */
 8 
 9 public class SingletonServiceTest extends Thread{
10     //单例模式下调用同步方法的时候,该单例会被锁住,其他线程想调用
11     //该单例的其它同步方法的时候就会进行同步,如果调用的不是单例方法则不受影响
12     SingletonService service = SingletonService.getInstance();
13     static int tag = 1;
14     
15     @Override
16     public void run() {
17         if(tag == 1){
18             service.testA();
19         }
20         else if(tag == 2){
21             service.testB();
22         }
23         else{
24             service.testC();
25         }
26     }
27     //这是使用代码块来实现的,相应的也学习了一下同步代码块和同步方法的使用
28     //这里的使用的是方法c和c1,这两个都不用同步了,因为这里已经对service同步了
29 //    public void run(){
30 //        synchronized (service) {
31 //            if(tag == 1){
32 //                service.testC();
33 //            }
34 //            else{
35 //                service.testC1();
36 //            }
37 //        }
38 //    }
39     
40     
41     
42     public static void main(String args[]){
43 
44         new SingletonServiceTest().start();
45         //第二个线程能否访问该单例的同一个同步方法A
46         new SingletonServiceTest().start();
47         //下面主要是让main线程歇一会儿,否则main执行完了才会启动子线程,导致tag为3,没有意义
48         try {
49             Thread.sleep(10);
50         } catch (InterruptedException e) {
51             e.printStackTrace();
52         }
53         tag = 2;
54         //第三个线程能否访问该单例的另外一个同步方法B
55         new SingletonServiceTest().start();
56         try {
57             Thread.sleep(10);
58         } catch (InterruptedException e) {
59             e.printStackTrace();
60         }
61         tag = 3;
62         //第四个线程能否访问该单例的另外一个非同步方法C
63         new SingletonServiceTest().start();
64     }
65 }

运行结果:

A 在执行Thread[Thread-1,5,main]
C 在执行Thread[Thread-3,5,main]
B 在执行Thread[Thread-2,5,main]
A 在执行Thread[Thread-0,5,main]

一开始是出现A和C,过5秒左右出现B,再过5秒出现A

说明:A线程在访问单例service的同步方法a的时候,其他线程不能否访问service的同步方法a和同步方法b,但是能访问非同步方法c

继续第二个问题的测试,直接上代码:

 package treadTest;
2
3 import java.util.ArrayList;
4 import java.util.List;
5
6
7 //单例模式下,多个线程调用单例的用一个非同步方法,会出现什么情况?会混乱么
8 public class ServiceTest2 extends Thread{
9 static SingletonService service = SingletonService.getInstance();
10 static List<Integer> list1;
11 static List<Integer> list2;
12
13 @Override
14 public void run() {
15 service.testD();
16 }
17
18
19 public static void main(String args[]){
20 list1 = new ArrayList<Integer>();
21 //给list1添加1-1000的数据
22 for(int i=0;i<1000;i++){
23 list1.add(i);
24 }
25
26 list2 = new ArrayList<Integer>();
27 //给list2添加1000-2000的数据
28 for(int i=1001;i<2000;i++){
29 list2.add(i);
30 }
31
32
33 //给service中的list赋list1
34 service.setList(list1);
35 //启动第一个线程
36 new ServiceTest2().start();
37
38 //同理 暂停main一会
39 try {
40 Thread.sleep(10);
41 } catch (InterruptedException e) {
42 e.printStackTrace();
43 }
44
45 //给service中的list赋list1
46 service.setList(list2);
47 //启动第一个线程
48 new ServiceTest2().start();
49
50 }
51
52
53
54 }

运行结果:

998 Thread[Thread-0,5,main]treadTest.SingletonService@61de33
999 Thread[Thread-0,5,main]treadTest.SingletonService@61de33

1747 Thread[Thread-1,5,main]treadTest.SingletonService@61de33
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ………………1538] list的长度1539 或者出异常
1748 Thread[Thread-1,5,main]treadTest.SingletonService@61de33

……

1998 Thread[Thread-1,5,main]treadTest.SingletonService@61de33
1999 Thread[Thread-1,5,main]treadTest.SingletonService@61de33
[0, 1, 2, 3, 4, 5……1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010, ……1999]list的长度1999

 

newlist并没有是单例中方法传入的多个list而混乱 跟线程调用普通类的情形没有区别,结果说明:同时访问同一个对象的同一个变量的情况下会混乱,出现了线程安全的问题,如果想要

多个线程共享一个变量,那么可以采用ThreadLocal(线程局部变量),每个线程都有该变量的一个副本,互不影响

3、验证第三个问题

直接上代码:

类似于上面的单例类,只是改成了普通类而已

 package treadTest;
2 public class Service {
3
4 //同步方法A
5 public synchronized void testA(){
6 try {
7 System.out.println("A 在执行" + Thread.currentThread());
8 Thread.sleep(5000);
9 } catch (InterruptedException e) {
10 e.printStackTrace();
11 }
12 }
13
14 //同步方法B
15 public synchronized void testB(){
16 try {
17 System.out.println("B 在执行" + Thread.currentThread());
18 Thread.sleep(5000);
19 } catch (InterruptedException e) {
20 e.printStackTrace();
21 }
22 }
23
24 //非同步方法C
25 public void testC(){
26 System.out.println("C 在执行"+ Thread.currentThread());
27 try {
28 Thread.sleep(5000);
29 } catch (InterruptedException e) {
30 e.printStackTrace();
31 }
32 }
33
34 }

非单例类的测试类如下:与上面的测试类的区别就是把类的实例化放在了run方法中,从而使得每次都new一个对象

 package treadTest;
2
3
4 //单例模式下,该单例有两个同步方法,当一个线程调用单例的其中一个同步方法A的时候,
5 //则其他的线程的这个单例能不能调用这个A方法?另外一个同步方法B呢?非同步方法呢?
6 public class ServiceTest extends Thread{
7 //单例模式下调用同步方法的时候,该单例会被锁住,其他线程想调用
8 //该单例的其它同步方法的时候就会进行同步,如果调用的不是单例方法则不受影响
9 Service service;
10 static int tag = 1;
11
12
13
14
15
16
17 @Override
18 public void run() {
19 service = new Service();
20 if(tag == 1){
21 service.testA();
22 }
23 else if(tag == 2){
24 service.testB();
25 }
26 else{
27 service.testC();
28 }
29 }
30
31 public static void main(String args[]){
32
33 new ServiceTest().start();
34 new ServiceTest().start();
35 try {
36 Thread.sleep(10);
37 } catch (InterruptedException e) {
38 e.printStackTrace();
39 }
40 tag = 2;
41 new ServiceTest().start();
42 try {
43 Thread.sleep(10);
44 } catch (InterruptedException e) {
45 e.printStackTrace();
46 }
47 tag = 3;
48 new ServiceTest().start();
49 }
50 }

运行结果:

A 在执行Thread[Thread-0,5,main]
A 在执行Thread[Thread-1,5,main]
B 在执行Thread[Thread-2,5,main]
C 在执行Thread[Thread-3,5,main]

没有丝毫的延迟,说明当service是普通类的情况下,A线程在访问service实例的同步方法a的时候,其他线程可以访问service实例的所有同步和非同步方法

总结收获:

同步方法锁定的是调用改同步方法的实例,不允许同一个实例在不同的线程中访问实例中的同步方法。比如SingletonService的service实例,当线程a访问service的同步方法a,导致service被锁定,其它的线程想访问service的同步方法b的时候就会去尝试给service上锁,但是此时线程a已经锁定了service实例,所以其它线程无法访问service的同步方法了。但是其它线程可以访问非同步方法,非同步方法并不尝试上锁。

 

当不同线程中SingletonService的不同实例来访问同步方法的时候,此时只能保证同一个实例不能在不同的线程中访问同步方法,不能拒绝不同的实例访问各自实例的同步方法,因此不同实例访问互不影响

在项目的备份还原中用的就是多线程同步+单例,当一个客户端操作的时候,单例会被锁定,其它客户端在访问这个单例的同一个或其它同步方法的时候,都会等待第一个客户只需完毕。ok就这样解决了。O(∩_∩)O~

第一次发博,愚见……

附上一道华为的面试题,挺给力的,各位慢慢赏玩吧……

 package treadTest;
2 //输出结果是什么呢?
3 public class Thread1{
4 public static synchronized void main(String args[]){
5 Thread t=new Thread(){
6 public void run(){
7 pong();
8 }
9 };
10 t.run();
11
12
13 try {
14 Thread.sleep(100);
15 } catch (InterruptedException e) {
16 e.printStackTrace();
17 }
18 System.out.print("ping");
19 }
20 static synchronized void pong(){
21 System.out.print("pong");
22 }
23
24 }

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值