1. Using reading and writing lock
Reading lock can make a data be read concurrently, but block writing thread. writeLock block all other reading and writing thread.
public class LockDemo {
private Map<String, Object> cacheData = new HashMap<String, Object>();// this is data
private ReadWriteLock rwl = new ReentrantReadWriteLock();
//this method to get data, if not in the map, we should get access to DB, need write it back to map
public Object getData(String key){
rwl.readLock().lock();
try{
Object cache = cacheData.get(key);
if(cache == null){
rwl.readLock().unlock();
rwl.writeLock().lock(); //other running read thread will lock here
try{
if(cache == null){//use this sentanse again to make sure other thread won't wirte again,after the
//first thread wirte the data back.
cache = "aaaaa"; // go to DB to get data.
}
}finally{
rwl.writeLock().unlock();
}
rwl.readLock().lock();
}
return cache;
}finally{
rwl.readLock().unlock();
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
}
}
A easier way to do this is just add synchronized key word in the definition of method.
2. Conditions
condition = lock.newCondition();
condition has method like condition.await(), and condition.signal(). it has the same function as object.wait() and object.notify(). But condition is more powerful, for it can create many conditions based on one Lock().
For example, if we want to let thread 1, run 10 times, then thread 2 run 20 times, and then thread2 run 100 times, then goes back to thread 1. to make this loop run 30 times. we can't realize it by simply using wait() and notify. we can use Lock and conditions instead.
Business.java
public class Business {
//lock and its conditions
private Lock lock = new ReentrantLock();
Condition conjob1 = lock.newCondition(); //condition that job1 should start
Condition conjob2 = lock.newCondition(); //condition that job2 should start
Condition conjob3 = lock.newCondition(); //condition that job3 should start
private int flag = 1;//by default, the first job start first
//job1, the thread runs 10 times
public void job1(){
lock.lock();
try{
while(flag != 1){ //first job should wait
conjob1.await();
}
for(int i=1;i<=10;i++){
System.out.println("job 1------------- "+i);
}
//after finish job1, it should notify job2
flag = 2;
conjob2.signal();
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
//job2, the thread runs 20 times
public void job2(){
lock.lock();
try{
while(flag != 2){ //second job should wait
conjob2.await();
}
for(int i=1;i<=20;i++){
System.out.println("job 2------------- "+i);
}
//after finish job2, it should notify job3
flag = 3;
conjob3.signal();
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
//job3, the thread runs 30 times
public void job3(){
lock.lock();
try{
while(flag != 3){ //third job should wait
conjob3.await();
}
for(int i=1;i<=30;i++){
System.out.println("job 3------------- "+i);
}
//after finish job1, it should notify job2
flag = 1;
conjob1.signal();
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
}
main.java
static Business business = new Business();
public static void main(String[] args) {
// TODO Auto-generated method stub
Runnable run1 = new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=1;i<30;i++){
business.job1();
}
}
};
//runable2
Runnable run2 = new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=1;i<30;i++){
business.job2();
}
}
};
Runnable run3 = new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=1;i<30;i++){
business.job3();
}
}
};
Thread t1 = new Thread(run1);
Thread t2 = new Thread(run2);
Thread t3 = new Thread(run3);
t1.start();
t2.start();
t3.start();
}
}
2.2 using Lock and condition to create a bundedBuffer( producer and consumer model)
Producers produce tokens at a solid speed and put the token into an array, and the consumer take out a token at a time and consume it. Create such a bundebuffer so that the producer thread will wait while the array is full, and the consumer thread will also wait while the array is empty.
class BoundedBuffer {
final Lock lock = new ReentrantLock(); //one lock
final Condition notFull = lock.newCondition(); //condition to see if it's full
final Condition notEmpty = lock.newCondition(); //condition to see if it's empty
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length) //if it's full
notFull.await(); //not full conditon doesn't apply, so it wait
items[putptr] = x; //if it's not full, we put this token into a place
if (++putptr == items.length) putptr = 0; //if put pointer goes to the end, we move it to the begining
++count; //after put, the total amount would increase
notEmpty.signal(); //signal a not empty condition, if there are any, becase we put one inside, we can make it not empty
} finally {
lock.unlock(); //unlock() in case there are some exception thrown
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0) //if it's empty, we can't take, so we have to wait
notEmpty.await(); // so notEmpty make us waiting here
Object x = items[takeptr]; //otherwise ,we take one item out of the array
if (++takeptr == items.length) takeptr = 0; //if the take point goes to the end, we move it to the front
--count; //after take, the total amount would decrease one
notFull.signal(); // notify the not full condition
return x;
} finally {
lock.unlock();
}
}
}