对Java线程同步的认识

synchronized关键字

Java以提供关键字synchronized的形式,以防止多线程时资源冲突提供了内置支持。当任务要执行被synchronized关键字保护的代码片段时,它将检查锁是否可用,然后获取锁,执行代码,释放锁。

所有对象都自动含有单一的锁(监视器)。当在对象上调用其任意synchronized方法时,此对象都被加锁。

synchronized锁住的是括号里的对象,而不是代码。对于非static的synchronized方法,锁的对象其实就是this。

注:

  • synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类(类锁,见下面synchronized(xxx.class)的介绍)
  • synchronized methods(){}synchronized(this){}之间本质上是一样的,只是 synchronized methods(){}便于阅读理解,而synchronized(this){}可以更精确的控制冲突限制访问区域,有时候表现更高效率。
  • synchronized关键字是不能继承的,也就是派生的子类如果也有同步的要求需再次使用synchronized关键字。

synchronized方法

synchronized methods(){
    //方法体
}

由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

synchronized代码块

synchronized(syncObject) {
    /允许访问控制的代码
}

syncObject为互斥量(上锁的目标),一般为类实例或类

当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。此时其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。

同步代码块synchronized(this)与synchronized(xx.class)的区别

有两种类型的同步锁,一种是对象锁(Object lock),一种是类锁(Class lock)。

对象锁与一个具体的对象实例相关联,每个对象实例只能有一个锁。当一个线程进入一个实例方法或者synchronized(Object o){}块时对象锁被激活。

一个类锁与一个具体的类相关联,一个类只能有一个锁。当线程进入静态方法或者synchronized(X.class) {}块时类锁被激活。也就是说类锁会锁定所有该类的实例。

举个栗子(见于CSDN某评论区):

  1. 假设苹果是一个类,里面有方法吃苹果,有实例苹果1synchronized(苹果1){吃苹果}时(对象锁),此时有一个人吃苹果1,但只能一个一个的吃;
  2. 再实例化一个 实例苹果2,而且此时有两个人a、b分别吃 苹果1苹果2 ,这时使用synchronized(苹果.class) 时(类锁),当a吃苹果(苹果1)时b不能吃苹果,反之亦然。

对象锁与类锁时相互独立的,事实上对一个类来说,可能在某一线程拥有它的类锁的同时,另一个线程拥有它的实例锁。

volatile关键字

首先我们得了解什么原子性。

原子性:在单处理器系统(UniProcessor)中,能够在单条指令中完成的操作称为”原子操作”,因为中断只能发生于指令之间。原子操作是不可分割的,在直到执行完毕之间不会被任何其它任务或事件中断。因此把一个操作这种无法再次分隔和中断的特性称为原子性。
在对称多处理器(Symmetric Multi-Processor)结构中就不同了,由于系统中有多个处理器在独立地运行,即使能在单条指令中完成的操作也有可能受到干扰。

原子操作是不能被线程调度中断的操作。意味着可以利用原子性来编写无锁的代码,这些代码不需要同步。

原子性可以应用于除long和double之外的所有基本类型的“简单操作”,对于这些类型可以保证它们会被当作不可分的(原子)操作来操作内存。

定义long和double类型变量时可以使用volatile关键字,让他们获得(简单赋值和返回操作的)原子性。

使用方式:

volatile 成员变量

原子操作确保了应用中的可视性,如果将一个域申明为volatile,那么只要对这个域产生了写操作,那么所有的读操作都可以看到这个修改。即便使用了本地缓存也是如此,volatile域会被立即写到主存中,而读写操作就发生在主存中。

  • volatile关键字为域变量的访问提供了一种免锁机制
  • 使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新
  • 因此每次使用该域就要重新计算,而不是使用寄存器中的值

注意:

  • 如果你不是专家级的程序员,不要依赖于原子性来编写无锁的代码。
  • 依赖于原子性可能是不安全的,因此避免使用原子性代替同步。
  • 使用volatile而不是synchronized的唯一安全情况是类中只有一个可变的域,而第一选择和最安全的方式是采用synchronized关键字。

使用显式的lock对象

Java SE5的java.uitl.concurrent类库了有定义在java.util.concurrent.locks中的显式的互斥机制。lock对象必须被显式地创建,锁定和释放。因此,它与内建的锁形式相比,代码缺乏优雅性。但在解决某些类型的问题时更加灵活。

使用方法如下:

Lock lock = new Reentrantlock();
lock.lock();
try{
    //同步的代码段
}finally{
    //释放锁
    lock.unlock();
}

使用try catch是为了当某些异常抛出了,你可以使用finally子句以维护在正常的状态。

使用synchronized关键字时,需要的代码量更少,并且用户错误出现的可能性更低。因此通常只有在解决特殊问题时,才会使用显式lock的这种方式。例如获取锁一段时间。

sleep()方法与wait()方法

  • 从所属的类来看,sleep()方法属于Thread中的,wait()方法在Object中。

  • 从资源调度上看,wait()方法会释放对象锁(等待CPU),sleep()方法不会(占着CPU睡觉)。

  • 使用范围:
    wait()notify()notifyAll()方法只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。

//在同步代码块中调用wait()方法
synchronized(x){
     x.notify();
    //或者wait()
}

参考:
[1].Thread Synchrinization

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值