synchronized的8种场景

synchronized

对于Java中的同步方法,一直以来是一个比较难以理解的问题

同步锁的作用主要有以下三个:
(1)确保线程互斥的访问同步代码
(2)保证共享变量的修改能够及时可见
(3)有效解决重排序问题。

从语法上讲,Synchronized总共有三种用法:
(1)修饰普通方法 锁是当前实例对象
(2)修饰静态方法 锁是当前类的class对象
(3)修饰代码块 锁是括号里面的对象

该锁具有可重入体现性,同一个对象里面可以多次使用synchronized。

但是在互斥的过程中还有以下小的细节需要注意,以下是常用的8中情况,当然这里只列出了synchronized修饰普通方法和静态类,修饰代码块没有列出。

线程操作资源类常见的8种情况:

*1 标准访问,请问先打印邮件还是短信
*2 暂停4秒钟在邮件方法,请问先打印邮件还是短信
*3 新增普通sayHello方法,请问先打印邮件还是hello
*4 两部手机,请问先打印邮件还是短信
*5 两个静态同步方法,同一部手机,请问先打印邮件还是短信
*6 两个静态同步方法,2部手机,请问先打印邮件还是短信
*7 1个静态同步方法,1个普通同步方法,同一部手机,请问先打印邮件还是短信
*8 1个静态同步方法,1个普通同步方法,2部手机,请问先打印邮件还是短信

资源类
class Phone
{
    
    
    public  synchronized void sendEmail()throws Exception
    {
    
    
        System.out.println("*****sendEmail");
    }
    public synchronized void sendSMS()throws Exception
    {
    
    
        System.out.println("*****sendSMS");
    }
    public void sayHello()throws Exception
    {
    
    
        System.out.println("*****sayHello");
    }
}

情况1:

Phone phone = new Phone();
      //线程1
      new Thread(() -> {
    
    
            try
            {
    
    
                phone.sendEmail();
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        },"A").start();

        //Thread.sleep(100);
       //线程2
           new Thread(() -> {
    
    
            try
            {
    
    
                phone.sendSMS();
                //phone.sayHello();
                //phone2.sendSMS();
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        },"B").start();

最终输出结果:
*****sendEmail
*****sendSMS

或者:
*****sendSMS
*****sendEmail

对于这个结论也许对于初学者你有点不信,可以自行实验。
我们要理解的是:当线程启动之后,并且执行了start方法,并不是立马执行里面的代码,并不是说threadA 代码在前面,就一定是先调用A的方法,具体调用时机是由操作系统来自行判断的。如果都知道多线程就是线程之间来回切换工作,如果想要严格按照顺序执行,请看情况2

情况2

改造下发送邮件的方法,在里面暂停3秒中

   public  synchronized void sendEmail()throws Exception
    {
    
    
             Thread.sleep(3000);
             System.out.println("*****sendEmail");
    }

    main方法中不变,查看执行结果:
    执行了n多次,都是
    *****sendEmail
    *****sendSMS

类比生活中的例子:
手机的功能很多,我们就列举出来 发短信,发邮件这两种。
对于独占类型的操作,类似这两种,一部手机同一时间不可能既在发短信又在发邮件(杠精可能会说我做定时任务不就可以了,杠精圆润的走开),这里发短信,发邮件这个操作就可以类比成synchronized的方法。

情况1,2的得出的结论:

1.一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,
其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一一个线程去访问这些synchronized方法
2.synchronized锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其它的synchronized方法。

情况3

增加一个普通方法

  public void say()throws Exception
    {
    
    
        System.out.println("*****hello world");
    }
    
   main方法中线程A中执行sendEmail并且sleep(3),线程B中执行say这个普通的方法。
   执行结果:
   *****hello world
   *****sendEmail

继续类比手机的案例:
普通方法就类似于后台进程在检测网络,他执行的时候不需要单独占有整个手机,类似于后台检测网络,流量这些小行为。

结论3

普通方法后发现和同步锁无关,不影响同步锁内的方法执行。

情况4

class Phone
{
    
    
    public  synchronized void sendEmail()throws Exception
    {
    
    
        TimeUnit.SECONDS.sleep(3);
        System.out.println("*****sendEmail");
    }
    public synchronized void sendSMS()throws Exception
    {
    
    
        System.out.println("*****sendSMS");
    }
    public void say()throws Exception
    {
    
    
        System.out.println("*****hello world");
    }
}

      new Thread(() -> {
    
    
            try
            {
    
    
                phone.sendEmail();
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        },"A").start();

        Thread.sleep(100);

        new Thread(() -> {
    
    
            try
            {
    
    
                //phone.sendSMS();
                //phone.say();
                phone2.sendSMS();
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        },"B").start();

打印结果:
在这里插入图片描述
这个其实也好理解:
对于1,2得出的结论,锁的是对象,两步手机相当于两个对象实例,他们之间是互不影响的。
因此即使线程A执行的sendEmail有休眠3秒,先执行的是线程B中的方法。

结论4

换成两个对象后,不是同一把锁了,对象之间不影响执行同步方法。

情况5

使用static修饰 synchronized

    public  static synchronized void sendEmail()throws Exception
    {
    
    
        TimeUnit.SECONDS.sleep(3);
        System.out.println("*****sendEmail");
    }
    public static synchronized void sendSMS()throws Exception
    {
    
    
        System.out.println("*****sendSMS");
    }
      Phone phone = new Phone();
      new Thread(() -> {
    
    
            try
            {
    
    
                phone.sendEmail();
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        },"A").start();

        Thread.sleep(100);

        new Thread(() -> {
    
    
            try
            {
    
    
                phone.sendSMS();
              
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        },"B").start();

在这里插入图片描述
使用static修饰了synchronized之后,锁就不是当前的this对象了,而是当前类。
说白了类就是一个模板,对象是这个模板具体的实现。因此对于模板被锁住了,同一个对象执行的时候,还是和案例2中一样。

情况6

phone类保持和情况5一样,现在换两个对象。分别执行发邮件和发短信方法。
     Phone phone = new Phone();
        Phone phone2 = new Phone();

        new Thread(() -> {
    
    
            try
            {
    
    
                phone.sendEmail();
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        },"A").start();

        Thread.sleep(100);

        new Thread(() -> {
    
    
            try
            {
    
    
                phone2.sendSMS();
                //phone2.say();
                //phone2.sendSMS();
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        },"B").start();

既然是锁的是类模板,即使是两个类同一时间还是只能同一个访问。

情况7

Phone类保持5一样,main方法中:

       Phone phone = new Phone();
        new Thread(() -> {
    
    
            try
            {
    
    
                phone.sendEmail();
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        },"A").start();

        Thread.sleep(100);

        new Thread(() -> {
    
    
            try
            {
    
    
                phone.say();
            
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        },"B").start();

    }

执行结果:
在这里插入图片描述
这里,就涉及到两把锁了,一个是当前实例对象this的锁,一个是当前类的锁,他们互不影响,因此还是按照顺序执行,哪个先得到锁哪个先运行。

情况8

    Phone phone = new Phone();
        Phone phone2 = new Phone();

        new Thread(() -> {
    
    
            try
            {
    
    
                phone.sendEmail();
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        },"A").start();

        Thread.sleep(100);

        new Thread(() -> {
    
    
            try
            {
    
    
                //phone2.sendSMS();
                phone2.say();
                //phone2.sendSMS();
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        },"B").start();

在这里插入图片描述
现在虽然是两个不同的锁,但是他们需要的锁还是不一样的,同情况7因此还是各自独立互不影响。

总结

    • synchronized实现同步的基础:Java中的每一个对象都可以作为锁。
    • 具体表现为以下3种形式。
    • 对于普通同步方法,锁是当前实例对象,锁的是当前对象this,
    • 对于同步方法块,锁是Synchonized括号里配置的对象。
    • 对于静态同步方法,锁是当前类的Class对象。

猜你喜欢

转载自blog.csdn.net/abc8125/article/details/109154005