synchronized关键字(同步锁)详细解析

本文详细解析了Java中的`synchronized`关键字,通过多个代码示例(demo1至demo6)展示了其在实现线程同步、防止脏读以及子类调用父类同步方法时的作用。在面试中,这些知识点常作为考点出现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这里大部分的解释都在代码中。

demo1

package synchornizeds;

public class Demo1 {
	private int count;
	private Object o = new Object();
	
	public void  m() {
		synchronized (o) { //任何线程执行一下代码都要申请o对应的锁。此处的o是堆内存中new出来的对象,而不是声明的对象引用
			count--;
			System.out.println(Thread.currentThread().getName() + " count = " + count);
		}
	}
	
	public static void main(String[] args) {
		
	}
}

demo2:

package synchornizeds;

public class Demo2 {
	private int count;
	
	public void  m() {
		synchronized (this) { //任何线程执行一下代码都要申请this对应的锁。
			count--;
			System.out.println(Thread.currentThread().getName() + " count = " + count);
		}
		/*
		 * synchronized 关键字锁定的是一个对象 而不是一个代码块
		 * 
		 * 
		 * 打一个比方: 多个人去上厕所,只有一个马桶 ,不可能所有的人同时上吧,只能是一个一个的,当第一个人进厕所之后,它用锁将门锁住,
		 * 不让外面的人进来 ,等他结束之后,第二个人进来,继续锁上厕所的门。 这把锁就相当于this
		 * 也就是说当前对象调用自身类中的方法,需要加锁。this就是指的是当前对象。synchronized锁住的是对象,而不是执行的代码块,
		 * 就想上面的,锁,锁住的是厕所的门 而不是马桶。。。。。
		 * */
	}
}

demo3:

package synchornizeds;

public class Demo3 {
	private int count = 10;
	public synchronized void m () { //等同以demo2中的写法
		count--;
		System.out.println(Thread.currentThread().getName() + " count = " + count);
	} 
}

demo4:

package synchornizeds;

public class Demo4 {
	private static int count = 10;
	public synchronized static void m () {  //相当于锁定demo4.class
		count--;
		System.out.println(Thread.currentThread().getName() + " count = " + count);
	} 
	public static void mm() {
		synchronized(Demo4.class) { //考虑一下这里写成synchronized(this)是否可行????
			count--;
		}
		/*
		 * 这里是不可行的,为什么呢  我们都知道静态的方法或属性是不需要我们new出实例在调用的
		 * 静态的属性或方法可以直接用class调用的 
		 * 而我们在这里锁定后的是.class 没有对象  this指的是对象不是class 所以此处不能写成synchronized(this)
		 */
	}
}

demo5

这里class是实现了Runnable接口重写run方法

package synchornizeds;

public class Demo5 implements Runnable {
	private int count = 10;
	public /*synchronized*/ void run() {
		count--;
		System.out.println(Thread.currentThread().getName() + " count = " + count);
	}
	public static void main(String[] args) {
		Demo5 t = new Demo5();
		for(int i = 0;i<5;i++) {
			new Thread(t ,"thread"+i).start();
		}
	}
}
thread0 count = 8
thread2 count = 7
thread1 count = 8
thread4 count = 5
thread3 count = 5

这里解释一些为什么会有重复的呢,就比如说5吧。当线程3减1之后应该为输出6,但是还没有等这个线程输出结果
下一个线程就执行了减操作,然后上一个线程才开始打印的,这个时候打印的值是减了2的值,应该是7-2 = 5

添加了synchronized关键字之后:

thread0 count = 9
thread3 count = 8
thread2 count = 7
thread1 count = 6
thread4 count = 5

这里加上锁之后,线程插入的问题就解决了。

demo6

package synchornizeds;

public class Demo6 {
	public synchronized void m1() {
		System.out.println(Thread.currentThread().getName() + "m1 start...");
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + "m1 end...");
	}
	
	public void m2() {
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + "m2 ...");
	}
	
	public static void main(String[] args) {
		Demo6 t = new Demo6();
		new Thread(()->t.m1(),"t1").start();
		new Thread(()->t.m2(),"t2").start();
		
		/*new Thread(t::m1,"t1").start();
		 * new Thread(t::m2,"t2").start();
		 */
	}
}

结果:

t1m1 start...
t2m2 ...
t1m1 end...

结论:在synchronized方法执行的过程中非同步方法也可以执行

就比如 一个人在上厕所,另一个人要在厕所外面扫地,两者之间是不需要竞争那把锁的。

线程之间脏读的问题(面试考点)

package synchornizeds;

import java.util.concurrent.TimeUnit;
/*
 * 对业务写方法加锁
 * 对业务读方法不加锁
 * 容易产生藏读现象
 */
public class Account {
	String name;
	double balance;
	
	public synchronized void set(String name,double balance) {
		this.name = name;
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		this.balance = balance;
	}
	
	public double getBalance(String name) {
		return this.balance;
	}
	
	public static void main(String[] args) {
		Account a = new Account();
		new Thread(()->a.set("xiaoming",100)).start();
		
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(a.getBalance("xiaoming"));
		
		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(a.getBalance("xiaoming"));
	}
}
结果:
0.0
100.0

这里造成数据脏读的问题的原因在上面的demo中也有说到,就是在同步方法执行的同时,非同步方法可以执行。
当加锁的写操作执行的同时,允许没有加锁的方法进行数据的读取。

解决方法就是在读方法上也加上锁

demo7 子类的同步方法调用父类的同步方法 (面试考点)

package synchornizeds;

import java.util.concurrent.TimeUnit;

/*
 * 一个同步方法可以调用另一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候依然会得到该对象的锁
 * 也就是synchronized获得的锁是可重入的
 * 这里是继承中可能发生的情形 子类的同步方法调用父类的同步方法
 * 
 */
public class Demo8 {
	synchronized void m() {
		System.out.println("m start ..");
		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		new DD().m();
	}
}

	class DD extends Demo8{
		@Override
		synchronized void m() {
			System.out.println("child m start ");
			super.m();
			System.out.println("child m end ");
		}
	}
	
	

结果:
child m start 
m start ..
child m end 

子类的同步方法调用父类的同步方法是完全允许的!

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值