synchronized方法和代码块的理解

本文探讨了Java中多线程环境下同步机制的使用方法,包括synchronized关键字的应用及其对方法和代码块的影响,通过具体代码示例验证了不同场景下同步的效果。

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

     熟悉 Java 的多线程的一般都知道会有数据不一致的情况发生,比如两个线程在操作同一个类变量时,而保护数据不至于错乱的办法就是让方法同步或者代码块同步。同步时非原子操作就得同步,比如一个简单的 1+1 运算也该同步,以保证一个代码块或方法成为一个原子操作。

对于给在多线程环境中可能会造成数据破坏的方法,做法有两种,以及一些疑问

1. 当一个线程访问了对象的synchronized或synchronized(xxx)代码块时,非同步方法是否会阻塞?

2.不论是静态的或非静态的方法都加上 synchronized 关键字,那静态的方法和非静态的方法前加上 synchronized 关键字有区别吗?

3. 或者在可疑的代码块两旁用 synchronized(this) 或 synchronized(someObject) 包裹起来,而选用 this 还是某一个对象--someObject,又有什么不同呢?
4. 对方法加了 synchronized 关键字或用 synchronized(xxx) 包裹了代码,就一定能避免多线程环境下的数据破坏吗?

5. 对方法加 synchronized 关键字与用 synchronized(xxx) 同步代码块两种规避方法又有什么分别和联系呢?

为了理解上面的问题,我们还得了解基本的东西,就是监视区域:从 Java 对线程同步的原理上说起, Java 直接在语言级上支持多线程的,在多线程环境中我们要小心的数据是:

1) 保存在堆中的实例变量
2) 保存在方法区中的类变量。
现实点说呢就是某个方法会触及到的同一个变量,如类变量或单态实例的实例变量。避免冲突的最容易想到的办法就是同一时刻只让一个线程去执行某段代码块或方法,于是我们就要给一段代码块或整个方法体标记出来,被保护的代码块或方法体在 Java 里叫做监视区域(Monitor Region)。

知道上面的针对上面的问题,我们通过下面代码进行逐一验证:

1)对于多个线程并发访问对象来说,非同步方法不会阻塞。

2)静态和非静态方法加上synchronized的区分是,监视区域的范围,非静态方法监视范围是对象级的,而非静态方法监视范围是类级的,和我们平常认识的类方法及实例方法一样。

代码说明:

public class Test3 {

	private static int flag = 1;
	
	public  synchronized static void accum(int time){
		flag++;
		try{
			Thread.sleep(time);
		}catch(InterruptedException e){
			e.printStackTrace();
		}
		flag--;
		System.out.println("Thread: " + Thread.currentThread().getName()+ " /Current flag: " + flag);
	}
	
	public void acc(){
		System.out.println("非同步方法:"+flag);
	}
	
	public synchronized void ac(){
		flag--;
		System.out.println("ac:"+flag);
	}
	
	public static void  main(String[] args){
		/*final Test3 test = new Test3(); ---同一个对象  监视范围为对象的线程可以正常阻塞
		new Thread("one"){
			public void run(){
				test.accum(2000);
			}
		}.start();
		new Thread("two"){
			public void run(){
				test.accum(10);
			}
		}.start();*/
		/*new Thread("tt"){
			public void run(){
				test.acc();
			}
		}.start();*/
		new Thread("one"){
			public void run(){
				new Test3().accum(2000);
			}
		}.start();
		new Thread("two"){
			public void run(){
				new Test3().accum(100);
			}
		}.start();
		new Thread("tt"){
			public void run(){
				new Test().acc();
			}
		}.start();
	}
}


运行结果:

非同步方法:1
Thread: one /Current flag: 1 //等待2秒出现
Thread: two /Current flag: 1 

去掉static标识,运行结果如下:

非同步方法:1
Thread: two /Current flag: 2
 

Thread: one /Current flag: 1  //等待2秒出现

3. 或者在可疑的代码块两旁用 synchronized(this) 或 synchronized(someObject) 包裹起来,而选用 this 还是某一个对象--someObject,又有什么不同呢?

验证代码如下:

private static int flag = 1;
	private static Object obj = new Object();
	public   void accum(int time){
		synchronized(this){  ------
		flag++;
		try{
			Thread.sleep(time);
		}catch(InterruptedException e){
			e.printStackTrace();
		}
		flag--;
		System.out.println("Thread: " + Thread.currentThread().getName()+ " /Current flag: " + flag);
		}
		
	}
	
	public void acc(){
		System.out.println("非同步方法:"+flag);
	}
	
	public  void ac(){
		synchronized(this){
		flag--;
		}
		System.out.println("ac:"+flag);
	}
	
	public static void  main(String[] args){
		new Thread("one"){
			public void run(){
				new Test2().accum(1000);
			}
		}.start();
		new Thread("two"){
			public void run(){
				new Test2().accum(20);
			}
		}.start();
		
	}
运行结果:

Thread: two /Current flag: 2 //证明第2个线程没有被阻塞
Thread: one /Current flag: 1

-----对于synchronized(this)来说,其监视区域为对象,但上例中因为都是用new出来的对象,所以不同对象的监视区域不同,所以第二个线程不会被第一个线程阻塞掉

synchronized(this){  把改行代码改成synchronized(object)


运行结果如下:
Thread: one /Current flag: 1 //2秒后出现,证明第2个线程在等待中 
Thread: two /Current flag: 1
synchronized(XX.class) XX来实际的类
运行结果和synchronized(object)一样,证明了其监视区域为类级
4. 对方法加了 synchronized 关键字或用 synchronized(xxx) 包裹了代码,就一定能避免多线程环境下的数据破坏吗?
---上例已经回答了该问题,如果在new出来的不同对象上,并不能保证数据的原子性,只有监视对象为类级时才可以保证

5. 对方法加 synchronized 关键字与用 synchronized(xxx) 同步代码块两种规避方法又有什么分别和联系呢?

----给方法加个关键字 synchronized 其实就是相当于把方法中的所有代码行框到了 synchronized(xxx) 块中。同步肯定会影响到效率,这也是大家知道的,因为它会造成方法调用的等待。方法中有些代码可能是线程安全的,所以可不用包裹在 synchronized(xxx) 中,提高线程安全的额外代码执行效率。

---以上是我对ynchronized 关键字与用 synchronized(xxx) 同步代码块的一些理解~


PS:一直认为我们所接触的技术都来源于生活,一直想用生活的语言表达出来,无奈水平有限。




<think>好的,我现在需要回答用户关于Java中synchronized关键字修饰方法修饰代码块的区别的问题。首先,我得确认自己对这方面的知识掌握程度,如果有不确定的地方,可能需要查阅资料来确保回答的准确性。 用户之前询问过如何使用Lambda表达式为TreeSet设置Comparator,现在转向了多线程同步的问题,看来用户在学习Java的不同方面。我需要用清晰的结构来解释两者的区别,可能包括定义、使用方式、锁对象、粒度控制、性能影响等方面。 首先,回顾synchronized的基本概念。synchronized用于实现线程同步,确保多个线程在访问共享资源时的正确性。修饰方法代码块都是实现同步的方式,但具体实现上有差异。 接下来,区分修饰方法代码块的不同点: 1. **修饰方法**: - 实例方法:锁的是当前实例对象(this)。 - 静态方法:锁的是当前类的Class对象。 - 整个方法体都是同步的,可能影响性能,尤其是在方法体较大或执行时间较长时。 2. **修饰代码块**: - 需要显式指定锁对象,可以是任意对象实例或类对象。 - 只同步代码块内的代码,其他代码可以并发执行,提高了灵活性,减少锁的持有时间,提升性能。 然后,考虑用户可能的疑惑点,比如锁的对象是什么,为什么选择代码块而不是方法,或者相反。需要举例说明,比如使用不同的锁对象可能导致不同的同步效果,或者如何避免死锁等。 还要注意,是否需要提到其他相关概念,比如可重入锁、锁的粒度、以及synchronized在JVM中的实现(如monitor enterexit)?不过用户的问题比较基础,可能不需要深入到JVM层面,但简要提及锁的粒度对性能的影响是有必要的。 另外,可能存在的误区需要澄清,比如是否所有方法都需要同步,或者过度同步导致的性能问题。例如,在实例方法上加synchronized,如果多个线程操作不同的实例,并不会互斥,而静态方法则会锁住整个类,所有实例的访问都会同步。 最后,总结两者的适用场景,帮助用户根据具体情况选择使用方式。例如,代码块适合需要更细粒度控制的场景,而修饰方法则代码更简洁,适合整个方法都需要同步的情况。 在组织回答时,按照用户要求的“结构清晰,逐步解决问题”,可能需要分点列出区别,并给出代码示例,说明各自的使用场景注意事项。同时,确保语言简洁明了,避免技术术语过多,必要时进行解释。 现在需要验证一下自己的理解是否正确: - 修饰实例方法时,锁是对象实例;修饰静态方法时,锁是类对象。而代码块需要指定锁对象,可以是任意对象,比如某个实例变量或者类变量。 - 代码块允许更细粒度的控制,比如在方法内部只有部分代码需要同步,其余部分可以异步执行,从而减少线程阻塞时间,提高效率。 - 正确的示例代码,比如同步方法对比同步代码块,展示不同的锁对象使用方式。 可能还需要提到,使用代码块时,可以选择更小的锁范围,减少竞争,提高并发性能。比如,如果同步方法中的某些代码不需要同步,那么使用代码块可以避免不必要的等待。 总结之后,确保回答覆盖了用户的问题,没有遗漏关键点,并且示例代码正确无误,能够帮助用户理解两者的区别。</think>在Java中,`synchronized`关键字修饰方法修饰代码块的主要区别体现在**锁的范围**、**灵活性****性能优化**等方面。以下是具体对比: --- ### 一、修饰方法(Method Level) #### 1. 语法格式 ```java // 实例方法 public synchronized void method() { // 方法体 } // 静态方法 public static synchronized void staticMethod() { // 方法体 } ``` #### 2. 核心特点 - **锁对象**: - 实例方法:锁是当前对象实例(`this`)。 - 静态方法:锁是类的`Class`对象(如`MyClass.class`)。 - **同步范围**:整个方法体被同步。 - **适用场景**:简单场景,方法整体需要同步。 #### 3. 示例 ```java public class Counter { private int count = 0; // 实例方法同步(锁this) public synchronized void increment() { count++; } // 静态方法同步(锁Counter.class) public static synchronized void log() { System.out.println("Static method"); } } ``` --- ### 二、修饰代码块(Block Level) #### 1. 语法格式 ```java public void method() { // 非同步代码 synchronized (lockObject) { // 需要同步代码块 } // 其他代码 } ``` #### 2. 核心特点 - **锁对象**:可自由指定(如`this`、任意对象实例或`Class`对象)。 - **同步范围**:仅代码块内的逻辑被同步。 - **灵活性**:可缩小锁的粒度,减少线程阻塞时间。 - **适用场景**:需要精细控制同步范围,或使用非`this`锁的情况。 #### 3. 示例 ```java public class Counter { private int count = 0; private final Object lock = new Object(); // 自定义锁对象 public void increment() { // 非同步代码(可并发执行) synchronized (lock) { // 仅同步关键操作 count++; } // 其他非同步代码 } } ``` --- ### 三、关键区别总结 | **对比维度** | **修饰方法** | **修饰代码块** | |--------------------|--------------------------------------|-------------------------------------| | **锁对象** | 隐式:实例方法锁`this`,静态方法锁`Class` | 显式:可指定任意对象 | | **同步范围** | 整个方法体 | 仅代码块内部 | | **性能** | 可能较差(锁持有时间长) | 更优(减少锁占用时间) | | **灵活性** | 低 | 高(可自由控制锁范围对象) | | **代码可读性** | 简单直接 | 需要明确锁对象范围 | --- ### 四、使用建议 1. **优先选择代码块**: 当只有部分代码需要同步时,使用代码块缩小锁范围,提升并发性能。 2. **避免锁冲突**: 若需同步多个无关资源,应为不同资源定义独立的锁对象,避免全局锁竞争。 3. **静态方法慎用**: 静态方法的锁是类级别的,所有实例共享同一锁,可能导致意外阻塞。 --- ### 五、典型场景示例 #### 场景1:缩小锁粒度(提高性能) ```java public class DatabaseService { private List<String> cache = new ArrayList<>(); private final Object cacheLock = new Object(); // 缓存专用锁 public void updateCache(String data) { // 非同步代码(如数据预处理) synchronized (cacheLock) { // 仅同步缓存更新操作 cache.add(data); } } } ``` #### 场景2:避免全局锁(减少竞争) ```java public class BankAccount { private int balance; private final Object balanceLock = new Object(); // 独立锁 public void transfer(BankAccount target, int amount) { // 同步代码块:分别锁定当前账户目标账户 synchronized (this.balanceLock) { synchronized (target.balanceLock) { this.balance -= amount; target.balance += amount; } } } } ``` --- 通过合理选择同步方式,可以平衡线程安全与性能需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值