Java Synchronized用法和多线程调式

Synchronzied的两个用法:

对象锁

包括方法锁(默认锁对象为this当前实例对象)和同步代码块锁(自己指定锁对象)

  • 代码块形式:手动指定锁对象

    @Override
    public void run() {
        synchronized (this) {
            System.out.println("我是对象锁的代码块形式。我是" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 运行结束。");
        }
    }
    

    当业务度比较复杂的时候需要手动创建对象锁,如下

    public class SynchronizedObjectCodeBlock2 implements Runnable{
    
    static SynchronizedObjectCodeBlock2 instance = new SynchronizedObjectCodeBlock2();
    Object lock1 = new Object();
    Object lock2 = new Object();
    
    @Override
    public void run() {
        synchronized (lock1) {
            System.out.println("我是对象锁lock1的代码块形式。我是" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " lock1部分运行结束。");
        }
    
        synchronized (lock2) {
            System.out.println("我是对象锁lock2的代码块形式。我是" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " lock2部分运行结束。");
        }
    }
    
    public static void main(String[] args) {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){
    
        }
        System.out.println("finished");
    }
    

}
```

  • 方法锁形式:synchronized修饰普通方法,锁对象默认为this

    /**
     * 对象锁,方法锁形式
     */
    public class SynchronizedObjectMethod3 implements Runnable{
    
        static SynchronizedObjectMethod3 instance = new SynchronizedObjectMethod3();
        @Override
        public void run() {
            method();
        }
        public static void main(String[] args) {
            Thread th1 = new Thread(instance);
            Thread th2 = new Thread(instance);
            th1.start();
            th2.start();
            while (th1.isAlive() || th2.isAlive()){
    
            }
            System.out.println("finished");
        }
        public synchronized void method() {
            System.out.println("我的对象锁的方法修饰符形式,我是" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 运行结束。");
        }
    }
    

类锁

指synchronized修饰静态的方法或指定锁为Class对象,Java类可能有很多个对象,但是只有一个Class对象;本质上所谓的类锁,不过是Class对象的锁而已。类锁只能在同一时刻被一个对象拥有

  • 静态方法锁,Synchronized加在static方法上

    /**
     * 类锁的第一种形式,static形式
     */
    public class SynchronizedClassStatic4 implements Runnable{
    
        static SynchronizedClassStatic4 instance1 = new SynchronizedClassStatic4();
        static SynchronizedClassStatic4 instance2 = new SynchronizedClassStatic4();
        @Override
        public void run() {
            method();
        }
        public static void main(String[] args) {
            Thread th1 = new Thread(instance1);
            Thread th2 = new Thread(instance2);
            th1.start();
            th2.start();
            while (th1.isAlive() || th2.isAlive()){
    
            }
            System.out.println("finished");
        }
        public static synchronized void method() {
            System.out.println("我是类锁的第一种形式:static,我是" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 运行结束。");
        }
    }
    
  • Synchronized(*.class)代码块

    /**
     * 类锁的第二种形式,class
     */
    public class SynchronizedClassClass5 implements Runnable{
    
        static SynchronizedClassClass5 instance1 = new SynchronizedClassClass5();
        static SynchronizedClassClass5 instance2 = new SynchronizedClassClass5();
        @Override
        public void run() {
            method();
        }
        public static void main(String[] args) {
            Thread th1 = new Thread(instance1);
            Thread th2 = new Thread(instance2);
            th1.start();
            th2.start();
            while (th1.isAlive() || th2.isAlive()){
    
            }
            System.out.println("finished");
        }
        public void method() {
           synchronized (SynchronizedClassClass5.class){
               System.out.println("我是类锁的第二种形式:synchronized(*.class),我是" + Thread.currentThread().getName());
               try {
                   Thread.sleep(3000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               System.out.println(Thread.currentThread().getName() + " 运行结束。");
           }
        }
    }
    

多线程调试—看线程生命周期

在这里插入图片描述
右键断点就可以查看到相应的线程,选择All可以查看到所有的线程,Frames下面可以查看对应线程
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
如上所示线程1是Runnable,线程2是blocked

多线程访问同步方法的7种情况(面试常考)

  • 两个线程同时访问一个对象的同步方法:方法锁(锁生效)

  • 两个线程访问的是两个对象的同步方法:锁无效(两个对象的锁),使用类锁

  • 两个线程访问的是synchronized的静态方法:锁生效(类锁)

  • 同时访问同步方法与非同步方法:

     /**
     * 只作用于当前方法,不会影响到其他方法
     */
    public synchronized void method1() {
        System.out.println("我是加锁的方法,我是" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " 运行结束。");
    }
    
    public void method2() {
        System.out.println("我是没加锁的方法,我是" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " 运行结束。");
    }
    
  • 访问同一个对象的不同的普通同步方法:锁生效

    /**
     * 同时访问同一个类的不同的普通同步方法
     *   虽然synchronized没有指定锁对象,但是本质上都是this(instance)同一个对象锁
     */
    public class SynchronizedDifferentMethod7 implements Runnable{
    
        static SynchronizedDifferentMethod7 instance = new SynchronizedDifferentMethod7();
        @Override
        public void run() {
            if(Thread.currentThread().getName().equals("Thread-0")){
                method1();
            } else {
                method2();
            }
        }
        public static void main(String[] args) {
            Thread th1 = new Thread(instance);
            Thread th2 = new Thread(instance);
            th1.start();
            th2.start();
            while (th1.isAlive() || th2.isAlive()){
    
            }
            System.out.println("finished");
        }
    
    
        public synchronized void method1() {
            System.out.println("我是加锁的方法method1,我是" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 运行结束。");
        }
    
        public synchronized void method2() {
            System.out.println("我是加锁的方法method2,我是" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 运行结束。");
        }
    }
    
  • 同时访问静态synchronized和非静态的synchronized方法:锁无效

    /**
     * 同时访问静态synchronize的非静态的synchronized方法。
     *   类锁和对象锁不是锁住的同一个对象,相互不影响
     */
    public class SynchronizedStaticAndNormal8 implements Runnable{
    
        static SynchronizedStaticAndNormal8 instance = new SynchronizedStaticAndNormal8();
        @Override
        public void run() {
            if(Thread.currentThread().getName().equals("Thread-0")){
                method1();
            } else {
                method2();
            }
        }
        public static void main(String[] args) {
            Thread th1 = new Thread(instance);
            Thread th2 = new Thread(instance);
            th1.start();
            th2.start();
            while (th1.isAlive() || th2.isAlive()){
    
            }
            System.out.println("finished");
        }
    
    
        public static synchronized void method1() {
            System.out.println("我是静态加锁的方法1,我是" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 运行结束。");
        }
    
        public synchronized void method2() {
            System.out.println("我是非静态加锁的方法2,我是" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 运行结束。");
        }
    }
    
  • 方法抛异常后,会释放锁

    /**
     * 方法抛出异常后,会释放锁。展示不抛出异常前和抛出异常后的对比
     *      一旦抛出异常,第二个线程会立刻进入同步方法,意味着锁已经释放
     */
    public class SynchronizedException9 implements Runnable{
    
        static SynchronizedException9 instance = new SynchronizedException9();
        @Override
        public void run() {
            if(Thread.currentThread().getName().equals("Thread-0")){
                method1();
            } else {
                method2();
            }
        }
        public static void main(String[] args) {
            Thread th1 = new Thread(instance);
            Thread th2 = new Thread(instance);
            th1.start();
            th2.start();
            while (th1.isAlive() || th2.isAlive()){
    
            }
            System.out.println("finished");
        }
    
        /**
         * 抛出异常后,JVM释放锁
         */
        public synchronized void method1() {
            System.out.println("我是静态加锁的方法1,我是" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            throw new RuntimeException();
    //        System.out.println(Thread.currentThread().getName() + " 运行结束。");
        }
    
        public synchronized void method2() {
            System.out.println("我是非静态加锁的方法2,我是" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 运行结束。");
        }
    }
    
    

以上七种情况,可以概况如下:

  • 一把锁只能被一个线程获取,没有拿到锁的线程必须等待(对应第1、5种情况)
  • 每个实例都对应有自己的一把锁,不同实例之间互不影响;但是锁对象是*.class以及synchronized修饰的的是static方法的时候,所有对象共用一把类锁(对应第2、3、4、6种情况)
  • 无论是方法正常运行完毕或者方法抛出异常,都会释放锁(对应第7种情况)

Synchronized的特点

锁的类型:自旋锁、互斥锁、可重入锁、中断锁、读写锁
Synchronized是可重入锁,什么是可重入,指的是同一线程的外层函数获取到锁之后,内层函数可以直接再次获取该锁。其原理是加锁次数计数器,在线程拿到锁之后,可以继续持有这把锁,但是对于JVM来说,不是那么简单。我怎么知道该什么时候释放,我拿到这把锁,反复拿了5次,那退出的时候该做什么。

  • JVM负责跟踪对象被加锁的次数;
  • 有个monitor计数器,线程第一次给对象加锁的时候,计数变为1.每当这个相同的线程在此对象上再次获得锁时,计数会递增。
  • 任务结束离开,则会执行monitorexit,计数递减,当计数为0的时候,锁完全被释放

Synchronized的粒度:线程而非调用(用3种情况来说明和pthread的区别)pthread是linux中调用为粒度。

  • 情况1:证明同一个方法是可重入的。

    public class SynchronizedRecursion10 {
        int a = 0 ;
        public static void main(String[] args) {
            SynchronizedRecursion10 instance = new SynchronizedRecursion10();
            instance.method1();
        }
        private synchronized void method1() {
            System.out.println("这是method1, a = " + a);
            if( a == 0) {
                a ++ ;
                method1();
            }
        }
    }
    
  • 情况2:证明可重入不要求是同一个方法。

    /**
     * 可重入粒度测试:调用类内另外的方法
     */
    public class SynchronizedOtherMethod11 {
        public static void main(String[] args) {
            SynchronizedOtherMethod11 instance = new SynchronizedOtherMethod11();
            instance.method1();
        }
        private synchronized void method1() {
            System.out.println("我是method1");
            method2();
        }
        private synchronized void method2() {
            System.out.println("我是method2");
        }
    }
    
  • 情况3:证明可重入不要求是同一个类中的

    /**
     * 可重入粒度测试:调用父类的方法
     */
    public class SynchronizedSuperClass12 {
    
        public synchronized void doSomething() {
            System.out.println("我是父类方法");
        }
    }
    class TestClass extends SynchronizedSuperClass12 {
        @Override
        public synchronized void doSomething() {
            System.out.println("我是子类方法");
            super.doSomething();
        }
        public static void main(String[] args) {
            TestClass testClass = new TestClass();
            testClass.doSomething();
        }
    }
    

以上情况说明只要是需要的锁依然是手中的这把锁,他的可重入性就会显现出来,就不需要显示的释放锁或者重新获取锁

Synchronized性质:不可中断。一旦这个锁已经被别人获得了,如果我还想获得,我只能选择等待或者阻塞,直到别的线程释放这个锁。如果别人永远不释放锁,那么我只能永远地等待下去。相比之下,Lock类,拥有中断的能力,第一点,如果我觉得我等的时间太长了,有权中断现在已经获取到锁的线程的执行;第二点,如果我觉得我等待的时间太长了不想在等了,也可以退出。

Synchronized关键字的缺陷

	1)效率低:锁的释放情况少,试图获得锁时不能设定超时、不能中断一个正在试图获得锁的线程
	
	2)当线程获取到锁,在执行过程中,其他线程也想用该锁时,只能等待当前线程释放。释放情况少,体现在两种情
	况:执行完毕、异常(JVM将锁释放),如果要等待IO这种耗时操作或者sleep,又不会中途释放锁,其他线程只能干
	巴巴的等着,非常影响程序执行的效率。需要一种机制,遏制让一直在等待,又影响其他线程执行的这些情况。Lock
	锁可以
	
	3)不够灵活(读写锁更灵活):加锁和释放锁的时机单一,每个锁仅有单一的条件(某个对象),可能是不够的
	
	4)无法知道是否成功获得到锁

Synchronized使用的注意点

1、锁对象不能为空:因为锁信息是保存在对象头中的,若对象是空的,那还哪来的对象头。

2、作用域不宜过大:虽然被synchronized 包裹的代码是线程安全的,但包裹的范围过大会造成程序执行效率低。

3、避免死锁。

如何选择Lock和synchronized关键字

1、建议都不使用,可以使用java.util.concurrent包中的Automic类、countDownLatch等类

2、优先使用现成工具,如果没有就优先使用synchronized关键字,好处是写劲量少的代码就能实现功能。如果需要灵活
的加解锁机制,则使用Lock接口

加锁和释放锁原理

现象

每一个类的实例对应着一把锁,而每一个synchronized方法都必须首先获得调用该方法的类的实例的锁,才能执行。否则线程就会阻塞,而方法一旦执行,就会独占这把锁,直到该方法返回,或者抛出异常,才将锁释放。释放之后,其他被阻塞的线程才能获得这把锁,重新进入可执行的状态

深入JVM看字节码:反编译,monitor指令

概况
	
	synchronize用的锁是java对象头里的一个字段(每一个对象都有一个对象头,对象头可以存储很多信息,其中有一
	部分就是用来存储synchronize关键字的锁)
	
    细节:当线程访问代码块的时候必须要得到这把锁,退出整个代码块或者抛出异常的时候必须会释放锁,在JVM规范
	中对于jVM实现原理,已经有了说明,它的进入锁和释放锁是基于moniter对象来实现同步方法和同步代码块的,
	Monditor对象主要是两个指令,一个是Monditorenter(插入到同步代码块开始的位置),和Monditorexit(退
	出,插入到方法结束的时候和退出的时候),jvm的规范要求每一个enter必须要要有exit和他对应,但是可能一个
	moniterenter对应多个Monditorexit,退出的时机包括方法结束和抛出异常。每一个对象都有一个Monditor和他
	关联,并且moniter被持有后,就会处于锁定状态,当线程执行到Monditorenter指令时,会尝试获取这个对象对应
	的Monditor的所有权,也是尝试获取对象的锁。
	
反编译字节码
	(1)目录定义到类所在的文件夹
	(2)在命令行,java类编程class对象,使用javac
	(3)javap 把class文件解读成字节码文件,java  -verbose:加入verbose参数,事无巨细的所有的信息都打印
	出来
	
详细解读Monditorenter和Monditorexit指令:
	Monditorenter和Monditorexit在执行的时候会使对象的锁计数加1或者减1,和操作系统中的PV操作(多线程对临
	界资源的访问)很像,每一个对象都和一个Monditor相关联,一个moditor的locksware只能被一个线程在同一时间
	获得,一个线程在尝试获得与这个对象关联的Monditor所有权的时候(Monditorenter),只会发生以下三种情况之
	一
	(1)Monditor计数器为0,意味着目前还还没有被获得,这个线程就会立刻获得,然后把计数器加1,之后别人再想
	进来就会看到信号,知道它已经被其他线程所持有,加1,意味着当前线程是这个Moditor持有者。(成功获得锁)
	
	(2)当前对象拿到锁的所有权,又重入了,就会导致计数器累加,会变成2,3……随着重入的次数而增加(已经有了这
	把锁再次重入的情况)
	
	(3)Monditor已经被其他线程所持有了,当前线程再次获取就会得到现在无法获取的信号,就会进入阻塞状态,知
	道Moditor的计数器变为0,才会再次尝试获取这个锁	
	
Monditorexit指令
	作用,释放Monditor(Monditor可以理解为锁)的所有权,前提是已经拥有了锁的所有权,释放的过程就是将
	Monditor的计数器减1,减完之后变成0就意味着当前线程不在拥有对Monditor的所有权,就是解锁,如果减完之后
	不是0,意味着刚才是可重入进来的,所以还继续持有这把锁,最终减到0之后,不仅意味着释放锁了,还意味着刚才
	被阻塞的线程,会再次尝试获取对该把锁的所有权
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值