目录
3.6 同时访问静态的synchronized和非静态synchronized方法
1.Synchronized简介
Synchronized的作用:能够保证在同一时刻最多只有一个线程执行该段代码,以达到保证并发的安全的效果。
Synchronized被Java原生支持,是最基本的互斥同步手段。
2.Synchronized的两种用法(对象锁和类锁)
2.1对象锁
对象锁:包括方法锁(默认锁对象为this当前实例对象)和同步代码块锁(自己指定锁对象)。
对象锁分为两种形式:
- 代码块形式:手动指定锁对象;
- 方法锁形式:Synchronized修饰普通方法,锁对象默认为this。
2.1.1 代码块形式
/**
* 对象锁示例1:代码块形式
* */
public class SynchronizedObjectCodeBlock2 implements Runnable{
static SynchronizedObjectCodeBlock2 instance = new SynchronizedObjectCodeBlock2();
@Override
public void run() {
synchronized (this){
System.out.println("我是对象锁的代码块形式,我叫"+ Thread.currentThread().getName());
try {
/** 休眠3秒 */
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
}
}
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");
}
}
输出:
我是对象锁的代码块形式,我叫Thread-0
Thread-0运行结束
我是对象锁的代码块形式,我叫Thread-1
Thread-1运行结束
finished
注释 synchronized (this){ }
/**
* 对象锁示例1:代码块形式
* */
public class SynchronizedObjectCodeBlock2 implements Runnable{
static SynchronizedObjectCodeBlock2 instance = new SynchronizedObjectCodeBlock2();
@Override
public void run() {
// synchronized (this){
System.out.println("我是对象锁的代码块形式,我叫"+ Thread.currentThread().getName());
try {
/** 休眠3秒 */
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
}
//}
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");
}
}
输出:
我是对象锁的代码块形式,我叫Thread-1
我是对象锁的代码块形式,我叫Thread-0
Thread-1运行结束
Thread-0运行结束
finished
则同时运行和结束。
public class SynchronizedObjectCodeBlock3 implements Runnable{
static SynchronizedObjectCodeBlock3 instance = new SynchronizedObjectCodeBlock3();
Object lock = new Object();
@Override
public void run() {
synchronized (lock){
System.out.println("我是对象锁的代码块形式,我叫"+ Thread.currentThread().getName());
try {
/** 休眠3秒 */
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
}
}
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");
}
}
输出:
我是对象锁的代码块形式,我叫Thread-0
Thread-0运行结束
我是对象锁的代码块形式,我叫Thread-1
Thread-1运行结束
finished
修改代码,定义两个锁对象
public class SynchronizedObjectCodeBlock3 implements Runnable{
static SynchronizedObjectCodeBlock3 instance = new SynchronizedObjectCodeBlock3();
Object lock1 = new Object();
Object lock2 = new Object();
@Override
public void run() {
synchronized (lock1){
System.out.println("我是lock1,我叫"+ Thread.currentThread().getName());
try {
/** 休眠3秒 */
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 {
/** 休眠3秒 */
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");
}
}
输出:
我是lock1,我叫Thread-1
Thread-1lock1运行结束
我是lock2,我叫Thread-1
我是lock1,我叫Thread-0
Thread-1lock2运行结束
Thread-0lock1运行结束
我是lock2,我叫Thread-0
Thread-0lock2运行结束
finished
因为用的是不同的锁,
我是lock2,我叫Thread-1
我是lock1,我叫Thread-0
这两句是同时打印出来的,是并行执行。
如果将lock2改为lock1:
public class SynchronizedObjectCodeBlock3 implements Runnable{
static SynchronizedObjectCodeBlock3 instance = new SynchronizedObjectCodeBlock3();
Object lock1 = new Object();
Object lock2 = new Object();
@Override
public void run() {
synchronized (lock1){
System.out.println("我是lock1,我叫"+ Thread.currentThread().getName());
try {
/** 休眠3秒 */
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"lock1运行结束");
}
synchronized (lock1){
System.out.println("我是lock2,我叫"+ Thread.currentThread().getName());
try {
/** 休眠3秒 */
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");
}
}
输出:
我是lock1,我叫Thread-0
Thread-0lock1运行结束
我是lock2,我叫Thread-0
Thread-0lock2运行结束
我是lock1,我叫Thread-1
Thread-1lock1运行结束
我是lock2,我叫Thread-1
Thread-1lock2运行结束
finished
用同一把锁则是串行执行。
小结:
以上代码使用synchronized的对象锁方式,使用了this或者自定义一把锁。
2.1.2 方法锁形式
使用synchronized修饰方法,新建类SynchronizedObjectMethod3.class:
public class SynchronizedObjectMethod3 implements Runnable{
static SynchronizedObjectMethod3 instance1 = new SynchronizedObjectMethod3();
@Override
public void run() {
method();
}
public synchronized void method(){
System.out.println("我是对象锁的方法修饰形式,我叫"+Thread.currentThread().getName());
try {
/** 休眠3秒 */
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
}
public static void main(String[] args) {
Thread t1 = new Thread(instance1);
Thread t2 = new Thread(instance1);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){
}
System.out.println("finished");
}
}
输出:
我是对象锁的方法修饰形式,我叫Thread-0
Thread-0运行结束
我是对象锁的方法修饰形式,我叫Thread-1
Thread-1运行结束
finished
由于使用synchronized修饰了method()方法,所以即使开了两个线程,也只是一个一个地执行。
2.2 类锁
Java类可能有很多个对象,但只有1个Class对象。
类锁:指Synchronized修饰静态的方法或指定为Class对象。类锁的本质就是,Class对象的锁。
- 形式1:synchronized 加在 static 方法上;
- 形式2:synchronized(*.class)代码块。
使用类锁之后,类锁只能在同一时刻被一个对象拥有。
2.2.1 static形式
新建 SynchronizedClassStatic4.class,不在method()前面加上static:
/**类锁的第一种形式:static形式 */
public class SynchronizedClassStatic4 implements Runnable{
static SynchronizedClassStatic4 instanceA = new SynchronizedClassStatic4();
static SynchronizedClassStatic4 instanceB = new SynchronizedClassStatic4();
@Override
public void run() {
method();
}
public synchronized void method(){
System.out.println("我是类锁的第一种形式,我叫"+Thread.currentThread().getName());
try {
/** 休眠3秒 */
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
}
public static void main(String[] args) {
Thread t1 = new Thread(instanceA);
Thread t2 = new Thread(instanceB);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){
}
System.out.println("finished");
}
}
输出结果:
我是类锁的第一种形式,我叫Thread-0
我是类锁的第一种形式,我叫Thread-1
Thread-0运行结束
Thread-1运行结束
finished
两个线程几乎同时开始,同时结束,意味着它们没有做到同步,它们是并行执行。
再在method()前面加上static:
/**类锁的第一种形式:static形式 */
public class SynchronizedClassStatic4 implements Runnable{
static SynchronizedClassStatic4 instanceA = new SynchronizedClassStatic4();
static SynchronizedClassStatic4 instanceB = new SynchronizedClassStatic4();
@Override
public void run() {
method();
}
public static synchronized void method(){
System.out.println("我是类锁的第一种形式,我叫"+Thread.currentThread().getName());
try {
/** 休眠3秒 */
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
}
public static void main(String[] args) {
Thread t1 = new Thread(instanceA);
Thread t2 = new Thread(instanceB);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){
}
System.out.println("finished");
}
}
输出:
我是类锁的第一种形式,我叫Thread-0
Thread-0运行结束
我是类锁的第一种形式,我叫Thread-1
Thread-1运行结束
finished
小结:
当遇到需要全局同步方法的情况,就需要public static synchronized void method() 这样的形式进行同步保护。
2.2.2 static形式
新建SynchronizedClassClass5.class :
/** 类锁的第二种形式: */
public class SynchronizedClassClass5 implements Runnable{
static SynchronizedClassClass5 instanceC = new SynchronizedClassClass5();
static SynchronizedClassClass5 instanceD = new SynchronizedClassClass5();
@Override
public void run() {
method();
}
private void method(){
synchronized (SynchronizedClassClass5.class){
System.out.println("我是类锁的第二种形式: synchronized(*.class),我叫"+Thread.currentThread().getName());
try {
/** 休眠3秒 */
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
}
}
public static void main(String[] args) {
Thread t1 = new Thread(instanceC);
Thread t2 = new Thread(instanceD);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){
}
System.out.println("finished");
}
}
输出:
我是类锁的第二种形式: synchronized(*.class),我叫Thread-0
Thread-0运行结束
我是类锁的第二种形式: synchronized(*.class),我叫Thread-1
Thread-1运行结束
finished
小结:
无论run()跑的是哪一个实例,只要同步代码块中 synchronized (SynchronizedClassClass5.class) ,也就说大家共用SynchronizedClassClass5.class这个对象,即便是不同的实例,也要一个一个按顺序地进行串行访问。
3. 多线程访问同步方法的7种情况
1)两个线程同时访问一个对象的同步方法;
2)两个线程访问的是两个对象的同步方法;
3)两个线程访问的是synchronized的静态方法;
4)同时访问同步方法与非同步方法;
5)访问同一个类的不同的普通同步方法;
6)同时访问静态的synchronized和非静态synchronized方法;
7) 方法抛出异常后,会释放锁吗?
3.1 两个线程同时访问一个对象的同步方法
对应前一节 SynchronizedObjectMethod3.class ,同一个实例的两个不同线程。
public class SynchronizedObjectMethod3 implements Runnable{
static SynchronizedObjectMethod3 instance1 = new SynchronizedObjectMethod3();
@Override
public void run() {
method();
}
public synchronized void method(){
System.out.println("我是对象锁的方法修饰形式,我叫"+Thread.currentThread().getName());
try {
/** 休眠3秒 */
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
}
public static void main(String[] args) {
Thread t1 = new Thread(instance1);
Thread t2 = new Thread(instance1);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){
}
System.out.println("finished");
}
}
输出:
我是对象锁的方法修饰形式,我叫Thread-0
Thread-0运行结束
我是对象锁的方法修饰形式,我叫Thread-1
Thread-1运行结束
finished
小结:
两个线程同时访问一个对象的同步方法,在争抢同一把锁,必然某一时刻只有一个人拥有,其他人等待,串行执行。
3.2 两个线程访问的是两个对象的同步方法
对应上一节 SynchronizedObjectCodeBlock2.class 的改进版,两个不同的实例:
public class SynchronizedObjectCodeBlock2 implements Runnable{
static SynchronizedObjectCodeBlock2 instance01 = new SynchronizedObjectCodeBlock2();
static SynchronizedObjectCodeBlock2 instance02 = new SynchronizedObjectCodeBlock2();
@Override
public void run() {
synchronized (this){
System.out.println("我是对象锁的代码块形式,我叫"+ Thread.currentThread().getName());
try {
/** 休眠3秒 */
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
}
}
public static void main(String[] args) {
Thread t1 = new Thread(instance01);
Thread t2 = new Thread(instance02);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){
}
System.out.println("finished");
}
}
输出:
我是对象锁的代码块形式,我叫Thread-1
我是对象锁的代码块形式,我叫Thread-0
Thread-1运行结束
Thread-0运行结束
finished
小结:
当两个线程访问的是两个对象的同步方法,这时 synchronized 方法不起作用,和普通方法一样,因为锁的是不同的实例,所以同时开始同时结束,并行执行。
3.3 两个线程访问的是synchronized的静态方法
对应 SynchronizedClassStatic4.class ,虽然有两个不同实例,但是 method() 是静态的,它们对应是锁就是同一把。
/**类锁的第一种形式:static形式 */
public class SynchronizedClassStatic4 implements Runnable{
static SynchronizedClassStatic4 instanceA = new SynchronizedClassStatic4();
static SynchronizedClassStatic4 instanceB = new SynchronizedClassStatic4();
@Override
public void run() {
method();
}
public static synchronized void method(){
System.out.println("我是类锁的第一种形式,我叫"+Thread.currentThread().getName());
try {
/** 休眠3秒 */
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
}
public static void main(String[] args) {
Thread t1 = new Thread(instanceA);
Thread t2 = new Thread(instanceB);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){
}
System.out.println("finished");
}
}
输出:
我是类锁的第一种形式,我叫Thread-0
Thread-0运行结束
我是类锁的第一种形式,我叫Thread-1
Thread-1运行结束
finished
小结:
两个线程访问的是synchronized的静态方法,锁生效,一个一个地执行。
3.4 同时访问同步方法与非同步方法
新建SynchronizedYesAndNo6.class :
/** 同时访问同步方法和非同步方法 */
public class SynchronizedYesAndNo6 implements Runnable{
static SynchronizedYesAndNo6 instance6 = new SynchronizedYesAndNo6();
@Override
public void run() {
if (Thread.currentThread().getName().equals("Thread-0")){
method1();
}else {
method2();
}
}
public synchronized void method1(){
System.out.println("我是加锁的方法1。我叫"+Thread.currentThread().getName());
try {
/** 休眠3秒 */
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
}
public void method2(){
System.out.println("我是不加锁的方法2。我叫"+Thread.currentThread().getName());
try {
/** 休眠3秒 */
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
}
public static void main(String[] args) {
Thread t1 = new Thread(instance6);
Thread t2 = new Thread(instance6);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){
}
System.out.println("finished");
}
}
输出
我是不加锁的方法2。我叫Thread-1
我是加锁的方法1。我叫Thread-0
Thread-0运行结束
Thread-1运行结束
finished
小结:
同时访问同步方法与非同步方法,则非同步方法不受影响。线程1和0几乎同时开始,同时结束 。说明synchronized只作用与它修饰的,其他没用它修饰则根本不受影响。
3.5 访问同一个类的不同的普通同步方法
新建 SynchronizedDifferentMethod7.class
/** 访问同一个类的不同的普通同步方法 */
public class SynchronizedDifferentMethod7 implements Runnable{
static SynchronizedDifferentMethod7 instance7 = new SynchronizedDifferentMethod7();
@Override
public void run() {
if (Thread.currentThread().getName().equals("Thread-0")){
method1();
}else {
method2();
}
}
public synchronized void method1(){
System.out.println("我是加锁的方法1。我叫"+Thread.currentThread().getName());
try {
/** 休眠3秒 */
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 {
/** 休眠3秒 */
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
}
public static void main(String[] args) {
Thread t1 = new Thread(instance7);
Thread t2 = new Thread(instance7);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){
}
System.out.println("finished");
}
}
输出:
我是加锁的方法1。我叫Thread-0
Thread-0运行结束
我是加锁的方法2。我叫Thread-1
Thread-1运行结束
finished
小结:
访问同一个类的不同的普通同步方法,默认指定了this作为同一把锁。对于同一个实例来讲,method1()和method2()拿到的实例是一样的,没法同时运行,所以是串行执行。
3.6 同时访问静态的synchronized和非静态synchronized方法
新建 SynchronizedStaticAndNormal8.class 类:
/** 同时访问静态的synchronized和非静态synchronized方法 */
public class SynchronizedStaticAndNormal8 implements Runnable{
static SynchronizedStaticAndNormal8 instance8 = new SynchronizedStaticAndNormal8();
@Override
public void run() {
if (Thread.currentThread().getName().equals("Thread-0")){
method1();
}else {
method2();
}
}
public synchronized static void method1(){
System.out.println("我是静态加锁的方法1。我叫"+Thread.currentThread().getName());
try {
/** 休眠3秒 */
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 {
/** 休眠3秒 */
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
}
public static void main(String[] args) {
Thread t1 = new Thread(instance8);
Thread t2 = new Thread(instance8);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){
}
System.out.println("finished");
}
}
输出:
我是静态加锁的方法1。我叫Thread-0
我是非静态加锁的方法2。我叫Thread-1
Thread-0运行结束
Thread-1运行结束
finished
小结:
method()方法,有static修饰时,锁的对象是.class对象,没有static修饰时,锁的对象是这个对象的实例本身this,两把锁不一样,本身没有冲突。这里输出结果几乎同时开始同时结束。
3.7 方法抛出异常后,会释放锁吗
展示不抛出异常前和抛出异常后的对比:一旦抛出异常,第二个线程会立刻进入同步方法,意味着锁已经释放。
新建 SynchronizedException9.class :
/** 方法抛出异常后,会释放锁吗 */
public class SynchronizedException9 implements Runnable{
static SynchronizedException9 instance9 = new SynchronizedException9();
@Override
public void run() {
if (Thread.currentThread().getName().equals("Thread-0")){
method1();
}else {
method2();
}
}
public synchronized void method1(){
System.out.println("我是静态加锁的方法1。我叫"+Thread.currentThread().getName());
try {
/** 休眠3秒 */
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 {
/** 休眠3秒 */
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
}
public static void main(String[] args) {
Thread t1 = new Thread(instance9);
Thread t2 = new Thread(instance9);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){
}
System.out.println("finished");
}
}
输出:
我是静态加锁的方法1。我叫Thread-0
Exception in thread "Thread-0" 我是非静态加锁的方法2。我叫Thread-1
java.lang.RuntimeException
at SynchronizedException9.method1(SynchronizedException9.java:24)
at SynchronizedException9.run(SynchronizedException9.java:10)
at java.lang.Thread.run(Thread.java:748)
Thread-1运行结束
finished
小结:
方法抛出异常后,由JVM帮忙释放这把锁,才能让下一个想要获得这把锁的线程拿到。
3.8 7种情况总结
3点核心思想:
- 一把锁只能同时被一个线程获取,没有拿到锁的线程必须等待(对应第1、5种情况);
- 每个实例都对应有自己的一把锁,不同实例之间互不影响;例外:锁对象是*.class 以及 synchronized 修饰的是static 方法的时候,所有对象共用同一把类锁(对应第2、3、4、6种情况);
- 无论是方法正常执行完毕或者方法抛出异常,都会释放锁(对应第7种情况)。
4. synchronized的性质
4.1 可重入
什么是可重入:指的是同一线程的外层函数获得锁之后,内层函数可以直接再次获取该锁。
好处:避免死锁,提升封装性。
粒度:
- 证明同一个方法是可重入的;
- 证明可重入不要求是同一个方法;
- 证明可重入不要求是同一个类中的。
4.2 证明同一个方法是可重入的
新建SynchronizedRecursion10.class :
/** 可重入粒度测试:递归调用本方法 */
public class SynchronizedRecursion10 {
int a = 0;
private synchronized void method1(){
System.out.println("这是method1,a = " + a);
if (a==0){
a++;
method1();
}
}
public static void main(String[] args) {
SynchronizedRecursion10 synchronizedRecursion10 = new SynchronizedRecursion10();
synchronizedRecursion10.method1();
}
}
输出:
这是method1,a = 0
这是method1,a = 1
4.3 证明可重入不要求是同一个方法
新建 SynchronizedOtherMethod11.class :
/** 可重入粒度测试:调用类内另外的方法 */
public class SynchronizedOtherMethod11 {
public synchronized void method1(){
System.out.println("我是method1");
method2();
}
public synchronized void method2(){
System.out.println("我是method2");
}
public static void main(String[] args) {
SynchronizedOtherMethod11 s = new SynchronizedOtherMethod11();
s.method1();
}
}
输出:
我是method1
我是method2
4.4 证明可重入不要求是同一个类中的
新建 SynchronizedSuperClass12.class :
/** 证明可重入不要求是同一个类中的 */
public class SynchronizedSuperClass12 {
public synchronized void doSomething(){
System.out.println("我是父类方法");
}
}
class TestClass extends SynchronizedSuperClass12{
public synchronized void doSomething(){
System.out.println("我是子类方法");
super.doSomething();
}
public static void main(String[] args) {
TestClass s = new TestClass();
s.doSomething();
}
}
输出:
我是子类方法
我是父类方法
4.5 加锁和释放锁的原理
新建 SynchronizedToLock13.class :
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SynchronizedToLock13 {
Lock lock = new ReentrantLock();
public synchronized void method1(){
System.out.println("我是Synchronized形式的锁");
}
public void method2(){
lock.lock(); //调用锁,锁住
try{
System.out.println("我是lock形式的锁");
}finally {
lock.unlock(); //释放锁
System.out.println("锁已释放");
}
}
public static void main(String[] args) {
SynchronizedToLock13 s = new SynchronizedToLock13();
s.method1();
s.method2();
}
}
输出:
我是Synchronized形式的锁
我是lock形式的锁
锁已释放
4.6 可重入原理:加锁次数计数器
- JVM负责跟踪对象被加锁的次数;
- 线程第一次给对象加锁的时候,计数变为1。每当这个相同的线程在此对象上再次获得锁时,计数会递增;
- 每当任务离开时,计数递减,当计数为0时,锁被完全释放。
4.7 可见性原理:Java内存模型
5. synchronized的缺陷
1)效率低:锁的释放情况少、试图获得锁时不能设定超时、不能中断一个正在试图获得锁的线程。
2)不够灵活(读写锁更灵活):加锁和释放的时机单一,每个锁仅有单一的条件(某个对象),可能是不够的。
3)无法知道是否成功获取到锁。
6.synchronized常见面试题
1)使用注意点:锁对象不能为空、作用域不宜过大、避免死锁;
2)如何选择 Lock 和 synchronized 关键字?
答:如果可以,既不使用Lock也不使用synchronized关键字,而是使用java.util包中各种各样的类。如果没有就先用synchronized
3) 多线程访问同步方法的各种具体情况
参考前面第3节。
延伸:
1)多个线程等待同一个synchronized锁的时候,JVM如何选择下一个获取锁的是哪个线程?
2)synchronized 使得同时只有一个线程可以执行,性能较差,有什么办法可以提升性能?
答:优化使用范围,使用其他类型的lock例如读写锁。
3)如何更灵活地控制锁的获取和释放?
答:可以试着自行实现一个lock接口。
4)什么是锁的升级?降级?什么是JVM里的偏斜锁?轻量级锁?重量级锁?
7.总结
一句话介绍synchronized:
JVM会自动通过使用monitor来加锁和解锁,保证了同时只有一个线程可以执行指定代码,从而保证了线程安全,同时具有可重入性和不可中断的性质。