synchronized的三种应用方式
1、修饰实例方法:获取的是实例对象的锁;
2、修饰静态方法:获取的是类对象的锁;
3、修饰代码块:可以指定锁定的对象,this表示获取实例对象的锁,类名.Class表示获取类对象的锁;
具体获取哪种对象锁的原理:
Synchronize关键字经过编译之后,会在同步块的前后分别形成monitorenter和monitorexit两个字节码指令,这两个字节码都需要一个reference类型的参数来指明要锁定和解锁的对象。如果Java程序中的synchronize明确指定了对象参数,那就是这个对象的reference;如果没有明确指定,那就根据synchronize修饰的是实例方法还是类方法(static修饰的方法),去获取对应的实例对象或Class对象来作为锁的对象。
实例对象锁与类对象锁的区别:
当synchronized作用于静态方法时,其锁就是当前类的class对象锁,由于静态成员不专属于任何一个实例对象,是类成员,所以当线程A访问一个实例对象的非静态同步方法,而线程B访问同一个实例对象的静态同步方法,由于线程A获得的锁是实例对象锁,而线程B获得的锁是类对象锁,所以它们之间不会发生互斥现象。
同步块和同步方法的区别
1、同步方法就是在方法前加关键字synchronized;同步块则是在方法内部使用synchronized大括号;
2、同步块需要指定锁定对象,同步方法中,非静态的同步方法是锁定实例对象,静态的同步方法是锁定类对象;
Synchronized与ReentrantLock的区别
两者都是阻塞式线程,都可以实现线程同步,性能上的比较要看jdk版本,在jdk比较低的版本ReentrantLock的性能会更好,但随着jdk的优化,两者在性能上的差别已经不大了。
1、等待可中断:当持有锁的线程长期不释放锁的时候,正在等待的线程可以放弃等待,处理其他事情。
2、公平锁:公平锁指多个线程同时等待同一个锁时,必须按照申请锁的时间顺序依次获得锁;非公平锁不保证这种优先级顺序,当锁释放时会随机被其他等待的线程获取;ReentrantLock和Synchronize默认都是非公平锁,但是ReentrantLock可以设置为公平锁。
3、锁绑定多个条件:一个ReentrantLock对象可以同时绑定多个对象。Condition条件对象的作用类似于Synchronize的wait()、notify()和notifyAll(),主要用于线程间的通信,对应于await()、signal()和signalAll()方法。
4、Synchronized是基于JVM的同步锁,JVM会帮我们自动释放锁;Lock是通过代码实现的,Lock要求我们手动调用unLock()释放锁,必须在finally语句中释放,否则如果被锁住的代码块抛出异常,锁就有可能永远得不到释放。
线程安全
不可变性是保证线程安全的最简单、最纯粹的方法,final关键字就能保证被修饰的变量或方法具有不可变性,比如String对象就是典型的不可变对象,无论调用substring()、replace()方法都不会改变String的值,只会构造出一个新的String对象而已,包括大数据类型Integer、Long、Double等都具有不可变性,在多线程下的共享数据时能保证线程安全。
补充
1、在考虑性能方面,最好使用同步块来减少锁定范围提高并发效率;
2、同步块可以指定需要锁定的对象,使用起来比较灵活;
3、当一个线程正在访问一个对象的synchronized实例方法时,该线程就获得了对象锁,由于一个对象只有一把锁,所以其它线程无法获取对象锁,也就无法访问该对象的其它synchronized方法,但是还可以访问其它非synchronized方法;
4、持有一个对象级别锁不会阻止该线程被交换出来,也不会阻塞其他线程访问同一示例对象中的非synchronized代码。也就是说当线程A持有一个对象级别锁(即进入了synchronized修饰的代码块或方法中)时,线程也有可能被交换出去,即被切换到线程B去执行该对象的代码的时间,但是线程B也只能执行非同步代码(没有用synchronized修饰),当执行到同步代码时,便会被阻塞,此时可能线程规划器又让A线程运行,A线程继续持有对象级别锁,当A线程退出同步代码时(即释放了对象级别锁),如果B线程此时再运行,便会获得该对象级别锁,从而执行synchronized中的代码。
5、synchronized关键字经过编译后,会在同步块的前后分别形成monitorenter和monitorexit两个字节码指令,在执行monitorenter指令时,首先要尝试获取对象的锁,如果获得了锁,把锁的计数器加1,相应地,在执行monitorexit指令时会将锁计数器减1,当计数器为0时,锁便被释放了。由于synchronized同步块对同一个线程是可重入的,因此一个线程可以多次获得同一个对象的互斥锁,同样,要释放相应次数的该互斥锁,才能最终释放掉该锁。
参考
《Android进阶之光》 第4章 多线程编程
《深入理解Java虚拟机》
网友评论