java 同步 异步 (2)-- synchronized锁住的是代码还是对象

本文详细解析了Java中synchronized关键字的作用及使用方法,包括如何控制线程同步,防止多个线程同时执行同一段代码。通过实例演示了synchronized关键字如何通过锁定对象来实现线程同步,并解释了非静态synchronized方法与静态synchronized方法的区别。文章最后提供了关于synchronized关键字使用的最佳实践,以及如何合理地使用synchronized来提高代码并发性能。


在Java中,synchronized关键字是用来控制线程同步的,就是在多线程的环境下,控制synchronized代码段不被多个线程同时执行。synchronized既可以加在一段代码上,也可以加在方法上。

关键是,不要认为给方法或者代码段加上synchronized就万事大吉,看下面一段代码:

class Sync {

	public synchronized void test() {
		System.out.println("test开始..");
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("test结束..");
	}
}

class MyThread extends Thread {

	public void run() {
		Sync sync = new Sync();
		sync.test();
	}
}

public class Main {

	public static void main(String[] args) {
		for (int i = 0; i < 3; i++) {
			Thread thread = new MyThread();
			thread.start();
		}
	}
}

运行结果:
test开始..
test开始..
test开始..
test结束..
test结束..
test结束..

可以看出来,上面的程序起了三个线程,同时运行Sync类中的test()方法,虽然test()方法加上了synchronized,但是还是同时运行起来,貌似synchronized没起作用。 

将test()方法上的synchronized去掉,在方法内部加上synchronized(this):

public void test() {
	synchronized(this){
		System.out.println("test开始..");
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("test结束..");
	}
}

运行结果:
test开始..
test开始..
test开始..
test结束..
test结束..
test结束..

一切还是这么平静,没有看到synchronized起到作用。 

实际上,synchronized(this)以及非static的synchronized方法(至于static synchronized方法请往下看),只能防止多个线程同时执行同一个对象的同步代码段。

回到本文的题目上:synchronized锁住的是代码还是对象。答案是:synchronized锁住的是括号里的对象,而不是代码。对于非static的synchronized方法,锁的就是对象本身也就是this。

当synchronized锁住一个对象后,别的线程如果也想拿到这个对象的锁,就必须等待这个线程执行完成释放锁,才能再次给对象加锁,这样才达到线程同步的目的。即使两个不同的代码段,都要锁同一个对象,那么这两个代码段也不能在多线程环境下同时运行。

所以我们在用synchronized关键字的时候,能缩小代码段的范围就尽量缩小,能在代码段上加同步就不要再整个方法上加同步。这叫减小锁的粒度,使代码更大程度的并发。原因是基于以上的思想,锁的代码段太长了,别的线程是不是要等很久,等的花儿都谢了。当然这段是题外话,与本文核心思想并无太大关联。

再看上面的代码,每个线程中都new了一个Sync类的对象,也就是产生了三个Sync对象,由于不是同一个对象,所以可以多线程同时运行synchronized方法或代码段。

为了验证上述的观点,修改一下代码,让三个线程使用同一个Sync的对象

class MyThread extends Thread {

	private Sync sync;

	public MyThread(Sync sync) {
		this.sync = sync;
	}

	public void run() {
		sync.test();
	}
}

public class Main {

	public static void main(String[] args) {
		Sync sync = new Sync();
		for (int i = 0; i < 3; i++) {
			Thread thread = new MyThread(sync);
			thread.start();
		}
	}
}

运行结果:
test开始..
test结束..
test开始..
test结束..
test开始..
test结束..

可以看到,此时的synchronized就起了作用。 

那么,如果真的想锁住这段代码,要怎么做?也就是,如果还是最开始的那段代码,每个线程new一个Sync对象,怎么才能让test方法不会被多线程执行。 

解决也很简单,只要锁住同一个对象不就行了。例如,synchronized后的括号中锁同一个固定对象,这样就行了。这样是没问题,但是,比较多的做法是让synchronized锁这个类对应的Class对象

class Sync {

	public void test() {
		synchronized (Sync.class) {
			System.out.println("test开始..");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("test结束..");
		}
	}
}

class MyThread extends Thread {

	public void run() {
		Sync sync = new Sync();
		sync.test();
	}
}

public class Main {

	public static void main(String[] args) {
		for (int i = 0; i < 3; i++) {
			Thread thread = new MyThread();
			thread.start();
		}
	}
}

运行结果:
test开始..
test结束..
test开始..
test结束..
test开始..
test结束..

上面代码用synchronized(Sync.class)实现了全局锁的效果

最后说说static synchronized方法,static方法可以直接类名加方法名调用,方法中无法使用this,所以它锁的不是this,而是类的Class对象,所以,static synchronized方法也相当于全局锁,相当于锁住了代码段。

<think>好的,我现在需要答用户关于Javasynchronized关键字修饰方法和修饰代码块的区别的问题。首先,我得确认自己对这方面的知识掌握程度,如果有不确定的地方,可能需要查阅资料来确保答的准确性。 用户之前询问过如何使用Lambda表达式为TreeSet设置Comparator,现在转向了多线程同步的问题,看来用户在学习Java的不同方面。我需要用清晰的结构来解释两者的区别,可能包括定义、使用方式、锁对象、粒度控制、性能影响等方面。 首先,synchronized的基本概念。synchronized用于实现线程同步,确保多个线程在访问共享资源时的正确性。修饰方法和代码块都是实现同步的方式,但具体实现上有差异。 接下来,区分修饰方法和代码块的不同点: 1. **修饰方法**: - 实例方法:锁的是当前实例对象(this)。 - 静态方法:锁的是当前类的Class对象- 整个方法体都是同步的,可能影响性能,尤其是在方法体较大或执行时间较长时。 2. **修饰代码块**: - 需要显式指定锁对象,可以是任意对象实例或类对象-同步代码块内的代码,其他代码可以并发执行,提高了灵活性,减少锁的持有时间,提升性能。 然后,考虑用户可能的疑惑点,比如锁的对象是什么,为什么选择代码块而不是方法,或者相反。需要举例说明,比如使用不同的锁对象可能导致不同的同步效果,或者如何避免死锁等。 还要注意,是否需要提到其他相关概念,比如可重入锁、锁的粒度、以及synchronized在JVM中的实现(如monitor enter和exit)?不过用户的问题比较基础,可能不需要深入到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; } } } } ``` --- 通过合理选择同步方式,可以平衡线程安全与性能需求。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值