线程同步基础读书笔记

使用synchronzied实现同步方法

使用synchronzied关键字来控制一个方法的并发访问。如果一个对象已用synchronized关键字声明,

那么只有一个执行线程允许访问它。在Java中,同一个对象的临界区,在同一时间只允许一个被访问。

但是静态方法则有不同的行为。用synchronized关键字声明的静态方法,同时只能够被一个线程访问,但是

其他线程可以访问这个对象的非静态方法,如果这两个方法都改变了相同的数据,将会出现数据不一致的错误!

如果线程A正在执行一个同步方法syncMethodA(),线程B要执行这个对象的其他同步方法syncMethodB(),线程B将被阻塞直到线程A访问完。

但如果线程B访问的是同一个类的不同对象,那么两个线程都不会阻塞。


可以递归调用被synchronized声明的方法。当线程访问一个对象的同步方法时,它还可以调用这个对象的其他的同步方法,也包含正在执行的方法,而不必再次去获取这个方法的访问权。


我们可以通过synchronized关键字来保护代码块的访问,方法的其余部分保持在synchronized代码块之外,以获取更好的性能。通常来说,我们使用this关键字来引用正在执行的方法所属的对象。

synchronized(this){

}

Synchronized锁类与锁对象的区别:更多详细参考 http://my.oschina.net/leoson/blog/106834

对象锁&类锁
对象锁
当一个对象中有synchronized method或synchronized block的时候调用此对象的同步方法或进入其同步区域时,就必须先获得对象锁。如果此对象的对象锁已被其他调用者占用,则需要等待此锁被释放
同步静态方法/静态变量互斥体
由于一个class不论被实例化多少次,其中的静态方法和静态变量在内存中都只由一份。所以,一旦一个静态的方法被申明为synchronized。此类所有的实例化对象在调用此方法,共用同一把锁,我们称之为类锁。一旦一个静态变量被作为synchronized block的mutex。进入此同步区域时,都要先获得此静态变量的对象锁
类锁
由上述同步静态方法引申出一个概念,那就是类锁。其实系统中并不存在什么类锁。当一个同步静态方法被调用时,系统获取的其实就是代表该类的类对象的对象锁
在程序中获取类锁
可以尝试用以下方式获取类锁
synchronized (xxx.class) {...}
synchronized (Class.forName("xxx")) {...}
同时获取2类锁
同时获取类锁和对象锁是允许的,并不会产生任何问题,但使用类锁时一定要注意,一旦产生类锁的嵌套获取的话,就会产生死锁,因为每个class在内存中都只能生成一个Class实例对象。

使用非依赖属性实现同步

当使用synchronized关键字来保护代码时,必须把对象引用作为传人参数。通常情况下,使用this关键字来引用执行方法所属的对象,也可以使用其他的对象进行引用。一般来说,这些对象就是为这个目的创建的。例如,在类中有两个非依赖属性,它们被多个线程共享,但是同一时刻只允许一个线程访问一个属性变量,其他线程访问另一个属性变量。

例如:

private long variable1 = 0;

private long variable2 = 0;

private Object control1 = new Object();

private Object control2 = new Object();


public setVariable1(long value){

synchronized(control1){

variable1  = value;

}

}


public setVariable2(long value){

synchronized(control2){

variable2  = value;

}

}


在同步代码块中使用条件

例如在某个缓冲区是一个共享数据结构,必须使用同步机制控制对它的访问,例如使用synchronized关键字,但是会受到更多的限制。如果缓冲区是满的,生产者就不能再放入数据,如果缓冲区是空的,消费者就不能读取数据,这个就是条件!如果有条件限制则可以使用wait(),notify(),notifyAll()这组方法。


wait()方法被调用后,调用线程就会进入休眠状态并且释放锁,直到其他线程调用相同对象的notify或者notifyAll,这个线程才会(notify的话只是有可能会)重新进入执行队列,等待获取锁,当这个线程再次接管锁后可以执行wait后面的内容(接管锁这个动作会等待锁被其他线程释放)。

Java中的wait()为什么总是放在while中,而不是if?

答:这是由于其它线程(与你的wait()完全无关的其它线程)会调用notifyAll(),而这个线程并不是你的wait()所共同协作的那个线程。因此,每一次被唤醒,你都要检查一次你wait()等待的条件。因而要用while。

notify与notifyAll的区别

notify()和notifyAll()都是Object对象用于通知处在等待该对象的线程的方法。两者的最大区别在于:

notifyAll使所有原来在该对象上等待被notify的线程统统退出wait的状态,变成等待该对象上的锁,一旦该对象被解锁,他们就会去竞争。
notify则文明得多他只是选择一个wait状态线程进行通知,并使它获得该对象上的锁,但不惊动其他同样在等待被该对象notify的线程们,当第一个线程运行完毕以后释放对象上的锁此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,继续处在wait状态,直到这个对象发出一个notify或notifyAll,它们等待的是被notify或notifyAll,而不是锁。

wait与sleep的区别

第一种解释:

功能差不多,都用来进行线程控制,他们最大本质的区别是:sleep()不释放同步锁,wait()释放同步缩.   
    
还有用法的上的不同是:sleep(milliseconds)可以用时间指定来使他自动醒过来,如果时间不到你只能调用interreput()来强行打断;wait()可以用notify()直接唤起.

第二种解释:

sleep是Thread类的静态方法。sleep的作用是让线程休眠制定的时间,在时间到达时恢复,也就是说sleep将在接到时间到达事件事恢复线程执行,例如:

try{
System.out.println("I'm going to bed");
Thread.sleep(1000);
System.out.println("I wake up");
}
catch(IntrruptedException e) {
}


wait是Object的方法,也就是说可以对任意一个对象调用wait方法,调用wait方法将会将调用者的线程挂起,直到其他线程调用同一个对象的notify方法才会重新激活调用者,例如:


//Thread 1

try{
obj.wait();//suspend thread until obj.notify() is called
}
catch(InterrputedException e) {
}

第三种解释:

这两者的施加者是有本质区别的. 
sleep()是让某个线程暂停运行一段时间,其控制范围是由当前线程决定,也就是说,在线程里面决定.好比如说,我要做的事情是 "点火->烧水->煮面",而当我点完火之后我不立即烧水,我要休息一段时间再烧.对于运行的主动权是由我的流程来控制.

而wait(),首先,这是由某个确定的对象来调用的,将这个对象理解成一个传话的人,当这个人在某个线程里面说"暂停!",也是 thisOBJ.wait(),这里的暂停是阻塞,还是"点火->烧水->煮饭",thisOBJ就好比一个监督我的人站在我旁边,本来该线程应该执行1后执行2,再执行3,而在2处被那个对象喊暂停,那么我就会一直等在这里而不执行3,但这个流程并没有结束,我一直想去煮饭,但还没被允许, 直到那个对象在某个地方说"通知暂停的线程启动!",也就是thisOBJ.notify()的时候,那么我就可以煮饭了,这个被暂停的线程就会从暂停处继续执行.


其实两者都可以让线程暂停一段时间,但是本质的区别是一个线程的运行状态控制,一个是线程之间的通讯的问题

在java.lang.Thread类中,提供了sleep(),
而java.lang.Object类中提供了wait(), notify()和notifyAll()方法来操作线程
sleep()可以将一个线程睡眠,参数可以指定一个时间。
而wait()可以将一个线程挂起,直到超时或者该线程被唤醒。
    wait有两种形式wait()和wait(milliseconds).
综上所述,sleep和wait的区别有:
  1,这两个方法来自不同的类分别是Thread和Object
  2,最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
  3,wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在
    任何地方使用
   synchronized(x){
      x.notify()
     //或者wait()
   }
   4,sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常

使用锁实现同步

Java提供了同步代码块的另一种机制,它是比synchronized关键字更强大也更灵活的机制。这种机制基于Lock接口及其实现类(例如ReentrantLock),提供了更多的好处。


此处加入使用锁与使用synchronized的区别(转载而来)。

1.ReentrantLock拥有Synchronized相同的并发性和内存语义,此外还多了锁投票,定时锁等候和中断锁。

   等候线程A和B都要获取对象O的锁定,假如A获取了对象O锁,B将等待A释放对O的锁定:

如果使用Synchronized,如果A不释放,B将一直等下去,不能被中断

如果使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断线程,而干别的事情

    ReentrantLock获取锁定的三种方式:

lock(),如果获取了锁则立即返回,如果别的线程持有锁,当前线程一直处于休眠状态,直到获取锁

        tryLock(),如果获取了锁并立即返回true,如果别的线程正持有锁,立即返回false

        tryLock(long timeout, Timeunit unit), 如果获取了锁定并立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果                     获取了锁定,就返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就会返回true,如果等                       待超时,立即返回false

lockInterruptibly(),若果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断。

2.Syncronized是在JVM层面上实现的,不但可以通过一些监控工具监控Synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中

3.在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReentrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是RenntrantLock的性能能维持在常态。

4.Lock接口允许分离读和写操作,允许多个读线程和只有一个写线程。

使用读写锁同步数据访问

锁机制最大的改进之一就是ReadWriteLock接口和它的唯一实现类ReentrantReadWriteLock。这个类有两个锁,一个是读操作锁,另一个是写操作锁。使用读操作锁时可以允许多个线程同时访问,但是使用写操作锁时只允许一个线程进行。在一个线程执行写操作时,其他线程不能够执行读操作。

ReentrantReadWriteLock.readLock()/ReentrantReadWriteLock.writeLock()。

在同一线程中,持有读锁后,不能直接调用写锁的lock方法 ,否则会造成死锁。

修改锁的公平性

ReentrantLock和ReentrantReadWriteLock类的构造器都含有一个布尔参数fair,它允许你控制这个两个类的行为。默认fair值是false,它称为非公平模式(Non-Fair Mode)。公平锁是选择等待时间最长的线程来获得锁。

在锁中使用多条件

与Synchronized中使用wait(),notify(),notifyAll()类似

在锁中使用Condition接口 ReentrantLock.newCondition(),使用相应的await(),singal(),singalAll()方法,唤醒只唤醒对应条件await的线程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值