Java关键字synchronized详解

本文详细解析Java中synchronized关键字的用法,包括同步方法和同步块,以及它们在多线程环境下的作用机制。同时,文章还提供了一些技巧以确保在使用synchronized时数据的一致性和安全性。
synchronized 关键字,代表这个方法加锁,相当于不管哪一个线程A每次运行到这个方法时,都要检查有没有其它正在用这个方法的线程B(或者C D等),有的话要等正在使用这个方法的线程B(或者C D)运行完这个方法后再运行此线程A,没有的话,直接运行 它包括两种用法:synchronized 方法和 synchronized 块。
[b]1. synchronized 方法:[/b]

  通过在方法声明中加入 synchronized关键字来声明 synchronized 方法。如:

  
public synchronized void accessVal(int newVal);


  synchronized 方法控制对类成员变量的访问:每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为 synchronized)。

  在 Java 中,不光是类实例,每一个类也对应一把锁,这样我们也可将类的静态成员函数声明为 synchronized ,以控制其对类的静态成员变量的访问。

  synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率,典型地,若将线程类的方法 run() 声明为 synchronized ,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何 synchronized 方法的调用都永远不会成功。当然我们可以通过将访问类成员变量的代码放到专门的方法中,将其声明为 synchronized ,并在主方法中调用来解决这一问题,但是 Java 为我们提供了更好的解决办法,那就是 synchronized 块。

[b]2. synchronized 块:[/b]

  通过 synchronized关键字来声明synchronized 块。语法如下:

  
synchronized(syncObject) {

  //允许访问控制的代码

  }


  synchronized 块是这样一个代码块,其中的代码必须获得对象 syncObject (如前所述,可以是类实例或类)的锁方能执行,具体机制同前所述。由于可以针对任意代码块,且可任意指定上锁的对象,故灵活性较高。

[b]对synchronized(this)的一些理解[/b]

  一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

  二、当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。

  三、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的除synchronized(this)同步代码块以外的部分。

  四、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。

  五、以上规则对其它对象锁同样适用


[b]synchronized的一个简单例子[/b]

public class TextThread
{

/**
* @param args
*/
public static void main(String[] args)
{
// TODO 自动生成方法存根
TxtThread tt = new TxtThread();
new Thread(tt).start();
new Thread(tt).start();
new Thread(tt).start();
new Thread(tt).start();
}
}

class TxtThread implements Runnable
{
int num = 100;
String str = new String();
public void run()
{
while (true)
{
synchronized(str)
{
if (num>0)
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
e.getMessage();
}
System.out.println(Thread.currentThread().getName()+ "this is "+ num--);
}
}
}
}
}


上面的例子中为了制造一个时间差,也就是出错的机会,使用了Thread.sleep(10)

Java对多线程的支持与同步机制深受大家的喜爱,似乎看起来使用了synchronized关键字就可以轻松地解决多线程共享数据同步问题。到底如何?――还得对synchronized关键字的作用进行深入了解才可定论。

总的说来,synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。

[b]在进一步阐述之前,我们需要明确几点:[/b]

A.无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。

B.每个对象只有一个锁(lock)与之相关联。

C.实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。

接着来讨论synchronized用到不同地方对代码产生的影响:

假设P1、P2是同一个类的不同对象,这个类中定义了以下几种情况的同步块或同步方法,P1、P2就都可以调用它们。


[b]1. 把synchronized当作函数修饰符时,示例代码如下:[/b]

Public synchronized void methodAAA()

{

//….

}


这也就是同步方法,那这时synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法对象。也就是说,当一个对象P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的 Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法。

上边的示例代码等同于如下代码:

public void methodAAA()

{

synchronized (this) // (1)

{

//…..

}

}


(1)处的this指的是什么呢?它指的就是调用这个方法的对象,如P1。可见同步方法实质是将synchronized作用于object reference。――那个拿到了P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱:(

[b]2.同步块,示例代码如下:[/b]

public void method3(SomeObject so)

{

synchronized(so)

{
//…..
}

}


这时,锁就是so这个对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁:

class Foo implements Runnable
{

private byte[] lock = new byte[0]; // 特殊的instance变量

Public void methodA()
{

synchronized(lock) { //… }

}

//…..

}


注:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。

[b]3.将synchronized作用于static 函数,示例代码如下:[/b]

Class Foo
{

public synchronized static void methodAAA() // 同步的static 函数
{
//….
}

public void methodBBB()
{

synchronized(Foo.class) // class literal(类名称字面常量)

}
}


代码中的methodBBB()方法是把class literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。

记得在《Effective Java》一书中看到过将 Foo.class和 P1.getClass()用于作同步锁还不一样,不能用P1.getClass()来达到锁这个Class的目的。P1指的是由Foo类产生的对象。

可以推断:如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized 的instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。A方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。

小结如下:

搞清楚synchronized锁定的是哪个对象,就能帮助我们设计更安全的多线程程序。

[b]
还有一些技巧可以让我们对共享资源的同步访问更加安全:[/b]

1. 定义private 的instance变量+它的 get方法,而不要定义public/protected的instance变量。如果将变量定义为public,对象在外界可以绕过同步方法的控制而直接取得它,并改动它。这也是JavaBean的标准实现方式之一。

2. 如果instance变量是一个对象,如数组或ArrayList什么的,那上述方法仍然不安全,因为当外界对象通过get方法拿到这个instance 对象的引用后,又将其指向另一个对象,那么这个private变量也就变了,岂不是很危险。 这个时候就需要将get方法也加上synchronized同步,并且,只返回这个private对象的clone()――这样,调用端得到的就是对象副本的引用了
还有,比较常用的就有:Collections.synchronizedMap(new HashMap()),当然这个MAP就是生命在类中的全局变量,就是一个 线程安全的HashMap,web的application是全web容器公用的,所以要使用线程安全来保证数据的正确。
<think>嗯,用户之前问过关于Java中排序和转换集合为Map的问题,现在又问synchronized关键字的使用详解。看来他们可能在处理多线程的问题,或者在项目中遇到了线程安全的挑战。需要详细解释synchronized的不同用法和应用场景。 首先,用户可能知道synchronized的基本概念,但需要更深入的理解,比如方法同步和代码块同步的区别。他们可能想知道什么时候用哪种方式更好。比如实例方法锁的是对象,静态方法锁的是类,这个区别很重要,尤其是在单例模式或者静态数据的情况下。 然后,用户可能对锁的作用范围不太清楚,容易混淆对象锁和类锁。比如同一个类的不同实例是否会影响同步,或者静态方法和实例方法之间会不会互相阻塞。需要举例说明不同情况下的锁行为,帮助用户理解。 另外,用户可能想知道synchronized的底层原理,比如Monitor机制,以及它在JVM中的实现方式。这部分内容可以提升他们对锁机制的理解,但需要避免过于深入复杂的细节,保持简洁明了。 还有,用户可能关心synchronized的性能问题,比如锁的粒度控制不当会导致性能下降。需要提到优化建议,比如缩小同步代码块的范围,使用更细粒度的锁,或者考虑其他并发工具如ReentrantLock。 另外,常见问题部分需要覆盖用户可能遇到的疑惑,比如可重入性、锁升级过程(偏向锁到重量级锁),以及死锁的避免方法。这些点都是实际开发中容易遇到的难点,解释清楚可以帮助用户避免陷阱。 最后,要确保回答结构清晰,步骤明确,从基本用法到底层原理,再到注意事项,逐步深入。同时结合代码示例,让用户更容易理解和应用。需要检查示例的正确性,确保没有语法错误,并且注释详细,帮助用户理解每部分的作用。</think>好的,以下是对Java中`synchronized`关键字的详细解析,结合代码示例和关键场景说明: --- ### **1. `synchronized`的核心作用** `synchronized`用于实现**线程同步**,确保多个线程对共享资源的访问安全。它通过**隐式锁(Monitor)** 机制实现,锁对象可以是实例对象或类对象。 --- ### **2. 基本用法** #### **(1) 同步实例方法** 锁对象是**当前实例**: ```java public class Counter { private int count = 0; // 锁住当前实例 public synchronized void increment() { count++; } } ``` #### **(2) 同步静态方法** 锁对象是**类的Class对象**: ```java public class Counter { private static int count = 0; // 锁住Counter.class public static synchronized void increment() { count++; } } ``` #### **(3) 同步代码块** **手动指定锁对象**(更灵活): ```java public class Counter { private final Object lock = new Object(); private int count = 0; public void increment() { synchronized (lock) { // 显式指定锁对象 count++; } } } ``` --- ### **3. 关键特性** #### **(1) 可重入性** - 同一线程可重复获取同一个锁,避免死锁: ```java public synchronized void methodA() { methodB(); // 允许调用,因为锁已持有 } public synchronized void methodB() { // ... } ``` #### **(2) 锁的作用范围** | 类型 | 锁对象 | 影响范围 | |---------------------|-------------------|----------------------------------------| | 实例方法 | 当前实例 | 同一实例的其他同步方法 | | 静态方法 | 类的Class对象 | 所有实例的同步静态方法 | | 代码块 `synchronized(obj)` | 指定对象`obj` | 所有使用`obj`作为锁的代码块 | --- ### **4. 底层原理** - **JVM Monitor 模型**:每个对象关联一个Monitor(通过`ObjectMonitor`实现),包含计数器、等待队列等。 - **字节码实现**: - 进入同步块时执行`monitorenter`指令,尝试获取锁。 - 退出时执行`monitorexit`指令,释放锁。 - **锁升级优化**(JDK 1.6+): - **偏向锁** → **轻量级锁** → **重量级锁**(根据竞争情况动态调整)。 --- ### **5. 典型应用场景** #### **(1) 线程安全的单例模式** ```java public class Singleton { private static Singleton instance; private Singleton() {} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } ``` #### **(2) 保护共享资源** ```java public class BankAccount { private double balance; public synchronized void deposit(double amount) { balance += amount; } public synchronized void withdraw(double amount) { balance -= amount; } } ``` --- ### **6. 注意事项与优化** #### **(1) 避免锁粒度过大** - **不推荐**:同步整个方法导致性能下降: ```java public synchronized void processData() { // 大量非共享资源操作... } ``` - **推荐**:缩小同步范围: ```java public void processData() { // 非同步操作... synchronized (this) { // 仅保护共享资源 } } ``` #### **(2) 死锁预防** - 避免嵌套锁请求: ```java // 线程1 synchronized (lockA) { synchronized (lockB) { ... } } // 线程2 synchronized (lockB) { synchronized (lockA) { ... } // 可能死锁 } ``` #### **(3) 替代方案** - 高并发场景可考虑: - `ReentrantLock`(更灵活的锁机制) - `StampedLock`(乐观读锁) - 并发容器(如`ConcurrentHashMap`) --- ### **7. 常见问题** #### **Q1: `synchronized`和`ReentrantLock`的区别?** | 特性 | `synchronized` | `ReentrantLock` | |--------------------|------------------------------------|--------------------------------------| | 锁获取方式 | 隐式获取/释放 | 显式调用`lock()`/`unlock()` | | 可中断性 | 不支持 | 支持`lockInterruptibly()` | | 公平锁 | 非公平 | 可配置公平/非公平 | | 条件变量 | 通过`wait()`/`notify()`实现 | 通过`Condition`对象实现 | #### **Q2: 静态同步方法和实例同步方法是否互斥?** - **不互斥**:静态方法锁的是类对象,实例方法锁的是实例对象,二者无竞争关系。 --- ### **总结** `synchronized`是Java中最基础的线程同步工具,适用于大多数低竞争场景。开发时应根据需求选择锁粒度,并注意避免死锁和性能瓶颈。对于复杂场景,可结合其他并发工具优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值