Java关键字synchronized的使用

今天我们来聊一聊java关键字synchronized。相信大家对这个关键字,一定不是很陌生,synchronized是我们在开发过程中保证多线程安全的一种重要手段,它的机制就是典型的同步互斥,保证在多个线程的时候,只有一个线程能访问共享数据,其他线程只有等待当前线程访问结束后才能访问。
那什么是线程安全呢?
简单的说就是当多个线程同时访问这个对象都能得到正确的结果。
我们先来看看下面这段代码。

      List<Thread> threads = new ArrayList<>();
      for(int i = 0;i < 10;i++){
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    synMethod();
                }
            },"thread"+i);
            thread.start();
            threads.add(thread);
      }

     for(Thread thread : threads){
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    }
   Log.v("MyThreadText",count+"");
        
        
   private void synMethod() {
        for (int k = 0; k < 1000; k++) {
            count++;
            Log.v("MyThreadText",Thread.currentThread().getName()+"   k:" +k + "    count:"+count);
        }
    }

在上面的代码,我们可以看到我们开启了10个线程去执行count++操作,如果没有多线程的话,我们知道最后count的值一定是10000。
根据打印结果:
在这里插入图片描述
在这里插入图片描述
我们可以分析出上面十个线程都已经完成了1000次的循环,但是我们的打印并不如我们所料的是10000而是一个小于10000的值。
因为i++这个操作并不是原子性的(原子性就是操作不可中断),导致最后的结果并不是我们想要的,这就是我们所说的线程不安全。那我们如何实现线程安全呢,那就要用到我们今天的主角了,synchronized关键字。
现在我们来改造上面的代码,实现线程同步。

  private synchronized void synMethod() {
        for (int k = 0; k < 1000; k++) {
            count++;
            Log.v("MyThreadText",Thread.currentThread().getName()+"   k:" +k + "    count:"+count);
        }
    }

改造上面的代码后,我在看我们的打印输出:在这里插入图片描述
就可以正确的得到我们想要的值了。
为什么synchronized能够实现同步?
我们知道我们写的java代码,最后都会编译class文件运行在虚拟机上(Android中编译成dex文件,dex文件是根据class文件演变而来的,去除了class文件中的冗余信息,效率更高,适合运行在移动设备上。)synchronized在经过编译后,会在同步块前后分别形成monitorenter和monitorexit这两个字节码指令。这个两个字节码呢,都需要一个reference类型参数来指明锁定和解锁的对象。
在执行monitor指令时,首先去尝试获取对象的锁,如果这个对象没有被锁定,或者当前线程已经拥有了这个对象的锁,把锁的计数+1,在执行monitorexit指令时,会将计数器减一,当计数器为0时,锁就被释放了。
如果获取锁失败,那么当前线程就要阻塞,直到其他线程释放这个锁为止。

你以为到这就结束了?还没有。
下面我们再来看几段代码:

    private void synMethod() {
        synchronized (this){
            for (int k = 0; k < 1000; k++) {
                count++;
                Log.v("MyThreadText",Thread.currentThread().getName()+"   k:" +k + "    count:"+count);
            }
        }
    }
    private void synMethod() {
        synchronized (Test.class){
            for (int k = 0; k < 1000; k++) {
                count++;
                Log.v("MyThreadText",Thread.currentThread().getName()+"   k:" +k + "    count:"+count);
            }
        }
    }
private void synMethod() {
        synchronized (object){
            for (int k = 0; k < 1000; k++) {
                count++;
                Log.v("MyThreadText",Thread.currentThread().getName()+"   k:" +k + "    count:"+count);
            }
        }
    }

可以发现我们修改了synchronized的位置,但是无一例外,上面的几种写法,都能保证我们实现同步得到正确的值。
上面几种写法唯一不同的是,我们的synchronized关键字修饰的东西不一样。我们的synchronized可以修饰方法,代码块。
具体可以细分位以下几种场景:
1.synchronized 类的实例方法;
2.synchronized 类的静态方法;
3.synchronized 类的class对象;
4.synchronized this(当前类的实例);
5.synchronized obj(任意的实例对象);
第一种场景 synchronized 关键字锁住的是当前类的实例对象。
第二种场景 synchronized 关键字锁住的当前的类对象。
第三种场景 synchronized 关键字锁住的还是当前的类对象。
第四种场景 锁住的当前类的实例。
第五种场景 锁住的任意的实例对象。
注意如果锁主的是类对象的话,尽管new出多个实例,但他们仍然属于同一个类依然会被锁住。
以上五种场景我们主要分为三种情况去讨论:
1锁住当前类的实例,2锁住当前类的类对象,3锁住其他对象的实例
这三种情况有什么区别呢?
下面我们来看代码:
场景1:

  Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synMethod();
            }
        },"thread1");

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synMethod2();
            }
        },"thread2");

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Log.v("MyThreadText",count+"");


  private void synMethod() {
        synchronized (this){
            for (int k = 0; k < 1000; k++) {
                count++;
                Log.v("MyThreadText",Thread.currentThread().getName()+"   k:" +k + "    count:"+count);
            }
        }
    }

    private synchronized void synMethod2() {
        for (int k = 0; k < 1000; k++) {
            count++;
            Log.v("MyThreadText",Thread.currentThread().getName()+"   k:" +k + "    count:"+count);
        }
    }

在这里插入图片描述

场景2:

 Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synMethod();
            }
        },"thread1");

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synMethod2();
            }
        },"thread2");

        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                synMethod3();
            }
        },"thread3");

        thread1.start();
        thread2.start();
        thread3.start();
        try {
            thread1.join();
            thread2.join();
            thread3.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Log.v("MyThreadText",count+"");



        
     private void synMethod() {
        synchronized (object){
            for (int k = 0; k < 5; k++) {
                count++;
                Log.v("MyThreadText",Thread.currentThread().getName()+"   k:" +k + "      count:"+count);
            }
        }
     }

    private synchronized void synMethod2() {
        for (int k = 0; k < 5; k++) {
            count++;
            Log.v("MyThreadText",Thread.currentThread().getName()+"   k:" +k + "    count:"+count);
        }
    }

    public  void synMethod3() {
        synchronized (SynchronizedActivity.class){
            for (int k = 0; k < 5; k++) {
                count++;
                Log.v("MyThreadText",Thread.currentThread().getName()+"   k:" +k + "    count:"+count);
            }
        }
    }

在这里插入图片描述
场景3:

 Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synMethod();
            }
        },"thread1");

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synMethod3();
            }
        },"thread2");


        thread1.start();
        thread2.start();
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Log.v("MyThreadText",count+"");
        
  private  synchronized static void synMethod() {
            for (int k = 0; k < 5; k++) {
                count++;
                Log.v("MyThreadText",Thread.currentThread().getName()+"   k:" +k + "    count:"+count);
            }
    }

    public  void synMethod3() {
        synchronized (SynchronizedActivity.class){
            for (int k = 0; k < 5; k++) {
                count++;
                Log.v("MyThreadText",Thread.currentThread().getName()+"   k:" +k + "    count:"+count);
            }
        }
    }

在这里插入图片描述
我们可以看到场景一和场景三,都是当一个线程的任务执行完在去执行另一个线程的任务,执行的虽然是不同的方法,但是都是执行的count++操作,这种情况下还是能得到我们想要的结果。场景二,我们可以看到三个线程是交叉执行的,在这种情况下,虽然我们都是执行的count++,但我们不能保证最后的结果就是我们想要的,因为它们并不同步。

为什么会导致这种情况的发生呢?在场景一和场景三中,我们获取的都是同一个锁。场景一中当我们的Thread2在执行method2方法时,获取到了当前类的实例的锁,在任务没有执行完前,不会去释放这个锁,而我们的Thead1要执行method1的方法时,首先回去尝试获取当前类的实例锁,但是现在锁还没有被释放,就会去等待。等Thread2任务执行完了释放锁,Thread1获取锁,然后执行任务,执行完之后在释放锁。场景三同理,我们获取都是类锁。
场景二,因为我们在执行method1,method2,method3所获取的锁时不一样的,同时执行这三个方法是互不影响的。执行method1的时候我们获取的是object这个实例对象的锁,执行method2的时候我们获取的当前类的实例锁,执行method3的时候我们获取的是类锁。
三把锁互不干涉。有兴趣的朋友可以去试试,验证一下。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值