线程的同步之Synchronized的使用

    一、介绍       

 线程的同步:一般的并发指的就是多个线程访问同一份资源。多个线程同时访问(修改)同一份资源的话,就会有可能造成资源数据有误。

 如果多个线程访问多个不同资源,就不会造成线程同步。

 如果要解决这个问题,就需要对线程使用同步存取。java中提供了一个synchronized关键字来对方法或者某个块加锁。从而达到锁定某个区域,不可

 同时修改以免数据有误的情况。

  synchronized关键字可以锁定的部分:

  1、锁定方法:在方法上加入synchronized关键字就表明在使用该方法的时候需要获取相应的锁。

  2、锁定块:锁定块的参数需要是对象,不可是基本类型数据

        synchronized(引用类型变量 | this | 对象.class){

          //逻辑代码

        }

  

   上图表示非同步线程和同步线程的比较,可以看出非同步的时候,线程1和线程2都是在同一个时间段访问同一个transter方法,而使用了同步之后,线程2如果想调用transter方法就必须等待线程1调用完成后才可执行。


 二、实例       

这里以12306抢票代码为例来说明线程同步的synchronized关键字的使用。

1、未使用synchronized锁的情况

首先来看未使用synchronized的情况会是什么样?

抢票的线程代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Web12306 implements Runnable{
     private int num= 10 ; //总共10张票
     private boolean flag = true ;
     @Override
     public void run() {
         while (flag){
             //黄牛抢到了3    农民工抢到了1 黄牛抢到了0  程序员抢到了-1
             test1(); //线程不安全,数据不准确:结果有-1值
         }
     }
     //1、线程不安全
     public void test1(){
         if (num<= 0 ) {
             flag = false ;
             return ; //跳出循环,结束
         }
         try {
             Thread.sleep( 500 ); //模拟延时
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println(Thread.currentThread().getName()+ "抢到了" +num--);
     }
}

测试线程代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class SynDemo1 {
     public static void main(String[] args) {
         //真实角色
         Web12306 web = new Web12306();
         //代理角色
         Thread proxy1 = new Thread(web, "黄牛" );
         Thread proxy2 = new Thread(web, "程序员" );
         Thread proxy3 = new Thread(web, "农民工" );
         proxy1.start();
         proxy2.start();
         proxy3.start();
     }
}

测试结果如下:可以看出最后的结果会出现0和-1这样错误的数据。

1
2
3
4
5
6
7
8
9
10
11
12
黄牛抢到了10
农民工抢到了8
程序员抢到了9
黄牛抢到了7
农民工抢到了6
程序员抢到了5
黄牛抢到了4
农民工抢到了3
程序员抢到了2
黄牛抢到了1
农民工抢到了0
程序员抢到了-1

为什么会出现这样的数据呢?

因为现在三个线程都启动了,都是在运行状态中访问test1方法,修改其中的num值。因为他们三个会同时都会进入该方法的情况,所以修改的数据也会出现当:黄牛抢走了1,这时候农民工和程序员还在test1方法里,他俩也会对num进行--操作。所以,最后的结果就是0和-1


2、使用synchronized关键字锁定方法:

线程修改抢票代码,在test1方法上加入synchronized关键字,使该方法锁定。调用时需要先获取锁(线程安全)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Web12306 implements Runnable{
     private int num= 10 ; //总共10张票
     private boolean flag = true ;
     @Override
     public void run() {
         while (flag){
             test2(); //线程安全,数据准确
         }
     }
     //2、方法锁:加上synchronized表示线程安全的
     public synchronized void test2(){
         if (num<= 0 ) {
             flag = false ;
             return ; //跳出循环,结束
         }
         try {
             Thread.sleep( 500 ); //模拟延时
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println(Thread.currentThread().getName()+ "抢到了" +num--);
     }
}

继续使用上面的main方法测试,测试结果如下:抢票结果正确没问题。

1
2
3
4
5
6
7
8
9
10
黄牛抢到了10
黄牛抢到了9
黄牛抢到了8
黄牛抢到了7
黄牛抢到了6
黄牛抢到了5
黄牛抢到了4
黄牛抢到了3
黄牛抢到了2
农民工抢到了1


3、使用synchronized锁定代码块:锁定当前对象

继续修改抢票代码,在方法内部使用synchronized锁定块

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
class Web12306 implements Runnable{
     private int num= 10 ; //总共10张票
     private boolean flag = true ;
     @Override
     public void run() {
         while (flag){
             test3(); //线程安全,数据准确
         }
     }
     //3、锁定块:当前对象也就是Web12306
     public void test3(){
         synchronized ( this ){ //锁定当前对象
             if (num<= 0 ) {
                 flag = false ;
                 return ; //跳出循环,结束
             }
             try {
                 Thread.sleep( 500 ); //模拟延时
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             System.out.println(Thread.currentThread().getName()+ "抢到了" +num--);
         }
     }
}

继续使用上面的main方法测试,测试结果如下:抢票结果正确没问题。

1
2
3
4
5
6
7
8
9
10
黄牛抢到了10
黄牛抢到了9
黄牛抢到了8
黄牛抢到了7
黄牛抢到了6
黄牛抢到了5
黄牛抢到了4
黄牛抢到了3
黄牛抢到了2
农民工抢到了1


4、使用synchronized锁定代码块:锁定部分代码块

可以看出test3方法是使用synchronized关键字锁定了整个方法区域。那如果就只锁定一部分呢?这里假如只锁定if(num<=0)这个判断部分

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
class Web12306 implements Runnable{
     private int num= 10 ; //总共10张票
     private boolean flag = true ;
     @Override
     public void run() {
         while (flag){
             test4(); //线程不安全,数据不准确:出现-1 【锁定范围不正确】
         }
     }
     //4、使用synchronized锁定部分资源
     public void test4(){
         synchronized ( this ){
             if (num<= 0 ) {
                 flag = false ;
                 return ; //跳出循环,结束
             }
         } //只锁定到此
         try {
             Thread.sleep( 500 ); //模拟延时
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println(Thread.currentThread().getName()+ "抢到了" +num--);
     }
}

使用main方法测试结果如下:可以看出最后的结果同样也会出现0和-1这样错误的数据。

1
2
3
4
5
6
7
8
9
10
11
12
黄牛抢到了10
农民工抢到了8
程序员抢到了9
黄牛抢到了7
农民工抢到了6
程序员抢到了5
黄牛抢到了4
农民工抢到了3
程序员抢到了2
黄牛抢到了1
农民工抢到了0
程序员抢到了-1

分析下为什么会出现这样的结果?我们知道test4中只锁定了if这部分。假设现在程序num现在等于1

1.此时线程A,B,C三个线程都会进入到12行,if判断的部分。A先进来拿到了锁,判断此时num=1 。然后释放锁走到18行,try的部分

2.线程A在18行try部分并没有对num--操作。此时线程B也进入到了12行拿到了锁。也到了18行。现在18行是A,B两个线程。A往下执行拿走了num

 等线程B再去拿num的时候,num已经等于0了。

3.同理,C再去拿num的时候num已经是0-1 = -1了。


5、使用synchronized锁定部分资源:只锁定num变量

由于synchronized的参数需要是对象,所以把基本类型包装成引用类型

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
class Web12306 implements Runnable{
     private int num= 10 ; //总共10张票
     private boolean flag = true ;
     @Override
     public void run() {
         while (flag){
             test5(); //线程不安全,数据不准确:出现重复数据【锁定范围不正确】
         }
     }
     //5、使用synchronized锁定部分资源:锁定num变量
     public void test5(){
         synchronized ((Integer)num){
             if (num<= 0 ) {
                 flag = false ;
                 return ; //跳出循环,结束
             }
         }
         try {
             Thread.sleep( 500 ); //模拟延时
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println(Thread.currentThread().getName()+ "抢到了" +num--);
     }
}

使用main方法测试结果如下:可以看出最后的结果会出现重复数据(两个6)锁定资源不正确也是线程不安全的

1
2
3
4
5
6
7
8
9
10
11
12
13
黄牛抢到了10
农民工抢到了9
程序员抢到了8
农民工抢到了7
黄牛抢到了6
程序员抢到了6
黄牛抢到了5
农民工抢到了4
程序员抢到了3
黄牛抢到了2
程序员抢到了1
农民工抢到了0
黄牛抢到了-1


 三、总结       

1、synchronized关键字表示锁,可以加在方法上或者一个代码块中

   synchronized(引用类型变量 | this | 对象.class){ 

   //需要锁的区域 

   }

2、不加synchronized关键字的方法是线程不安全的

  加了synchronized表示线程安全,线程安全的话会降低效率。因为共享的资源被加了锁,会有锁等待时间

3、在加synchronized代码块的时候需要注意,注意锁的范围。

  范围太大----->会降低效率。范围太小------>线程不安全







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值