1.使用 synchronized实现同步方法
如果一个对象已经用synchronized声明,那么只允许一个执行线程访问它,如果其他线程试图访问这个对象的其他方法,它将被挂起,直到第一个线程执行完正在运行的方法。被synchronized声明的方法就是临界区。
对于非静态的方法被synchronized修饰后,同一时间内只能有一个线程访问这个对象的synchronized方法。即,每一个synchronized方法都控制着类成员变量的访问,要想访问被synchronized声明的方法,必须先获得该示例对象的锁,否则将会被挂起,这就保证了同一个实例的所有非静态synchronized的方法在同一个时刻只能有一个在执行。
而对于用synchronized声明的静态方法,同时只能被一个执行线程访问,但是其他线程可以访问这个对象的synchronized声明的非静态方法。必须谨慎这一点,当静态与非静态方法涉及到共享数据的时候就会导致数据不一致的问题。
使用synchronized关键子会减低程序的性能,因此只能在并发的情境中需要修改共享数据的地方使用它。
可以递归调用被synchronized声明的方法。当线程访问一个对象的同步方法时,他还可以调用这个对象的其他同步方法,也包含正在执行的方法,而不用再次获取方法的访问权。
我们可以使用synchronized关键字保护代码块(而不是整个方法的访问)。方法的其余部分保持在代码块之外,同时以保证synchronized代码块中的代码尽可能短,以获取更高的性能。
synchronized(this){ //java code; }
当使用synchronized关键字保护代码块时,必须把对象引用做为传入参数,通常情况下使用this,但是也可以使用其他对象(非依赖对象)进行控制:
比如:
Object o = new Object(); synchronized(o){ //java code; }
o即:与本类无关的对象。
2.在代码块中使用条件
public synchronized void set(){ while(storage.size()==maxSize){ try{ wait(); }catch(InterruptedException e){ e.printStackTrace(); } } }
注意:被唤醒之后还要继续执行while中的条件判定。
3.wait()与notify()、notifyAll()
首先,调用一个Object的wait与notify/notifyAll的时候,必须保证调用代码对该Object是同步的,也就是说必须在作用等同于synchronized(obj){......}的内部才能够去调用obj的wait与notify/notifyAll三个方法,否则就会报错:
java.lang.IllegalMonitorStateException:current thread not owner
在调用wait的时候,线程自动释放其占有的对象锁,同时不会去申请对象锁。当线程被唤醒的时候,它才再次获得了去获得对象锁的权利。
所以,notify与notifyAll没有太多的区别,只是notify仅唤醒一个线程并允许它去获得锁,notifyAll是唤醒所有等待这个对象的线程并允许它们去获得对象锁,只要是在synchronied块中的代码,没有对象锁是寸步难行的。其实唤醒一个线程就是重新允许这个线程去获得对象锁并向下运行。
notifyAll,虽然是对每个wait的对象都调用一次notify,但是这个还是有顺序的,每个对象都保存这一个等待对象链,调用的顺序就是这个链的顺序。其实启动等待对象链中各个线程的也是一个线程,在具体应用的时候,需要注意一下。
wait(),notify(),notifyAll()不属于Thread类,而是属于Object基础类,也就是说每个对像都有wait(),notify(),notifyAll()的功能。因为都个对像都有锁,锁是每个对像的基础,当然操作锁的方法也是最基础了。
wait():
等待对象的同步锁,需要获得该对象的同步锁才可以调用这个方法,否则编译可以通过,但运行时会收到一个异常:IllegalMonitorStateException。
调用任意对象的 wait() 方法导致该线程阻塞,该线程不可继续执行,并且该对象上的锁被释放。
notify():
唤醒在等待该对象同步锁的线程(只唤醒一个,如果有多个在等待),注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。
调用任意对象的notify()方法则导致因调用该对象的 wait()方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行)。
notifyAll():
唤醒所有等待的线程,注意唤醒的是notify之前wait的线程,对于notify之后的wait线程是没有效果的。
4.使用锁实现同步
这种对同步的控制比synchronized更强大也二更灵活。这种机制基于Lock接口及其实现类(例如:ReeNtrantLOCK),Lock实现了更复杂的临界区结构(即:控制的获取和释放不出现在一个块中)。tryLock()的实现提供了更多的作用。通过返回值得知是否有其他线程正在使用这个锁保护的代码块,返回true:线程获取了锁。false:表示没有获取锁。
ReentrantLock类也允许递归调用。
使用读写锁实现同步数据访问。ReadWriteLock接口和它的唯一实现类ReentrantReadWriteLock。这个类有两个锁,一个是读操作锁另一个是写操作锁。使用读操作锁可以允许多个线程同时访问,但是使用写操作锁时只能允许一个线程进行。
private ReadWriteLock lock = new ReadWriteLock (); //获取读操作锁 lock.readLock().lock(); //释放读操作锁 lock.readLock().unlock(); //获取写操作锁 lock.writeLock().lock(); //释放写操作锁 lock.writeLock().unlock();
5.修改锁的公平性
ReentrantLock和ReentrantReadWriteLock类的构造器都含有一个布尔参数fair,为false即非公平模式,在非公平模式下当有很多线程在等待锁时,锁将随机选择一个来访问临界区。而为True时,则选择等待时间最长的获得临界区。
6.在锁中使用多条件
一个锁可能关联一个或者多个条件,这些条件通过Condition接口声明。目的是允许线程获取锁并且查看等待的某一个条件是否满足,Condition提供了挂起线程和唤起线程的机制。
用法如下:
ReentrantLock Lock = new ReentrantLock(); Condition line = lock.newCondition(); Condition space = lock.newCondition(); while(buffer.size()==maxSize){ space.await(); } line.signalAll(); while(buffer.size()==0){ line.await(); } space.signalAll();
注意:唤醒后还需要进行while循环。
版权声明:本文为博主原创文章,未经博主允许不得转载。