一,jdk1.5的上锁机制和解锁机制
传统的方法是通过synchronized给代码块上锁,jdk1.5之后提供了显示的锁机制,通过创建ReentrantLock对象:Lock lock = new ReentrantLock();获得一个锁,
然后调用ReentrantLock类的lock()方法上锁,unLock()方法解锁。
代码中给出了两种上锁的方式,注意注释部分。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {
public static void main(String[] args) {
Res r = new Res();
init(r);
}
private static void init(final Res r) {
//创建一个线程
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r.output("zhangsan");
}
}
}).start();
//创建第二个线程
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r.output("lisi");
}
}
}).start();
}
static class Res {
//先创建一个锁
private final Lock lock = new ReentrantLock();
public void output(String name) {
//传统上锁的方法使用synchronized
//synchronized (Res.class) {
//使用jdk1.5的线程上锁机制
lock.lock();//上锁
try {
for (int i = 0; i < name.length(); i++) {
System.out.print(name.charAt(i));//打印每一个字母,循环名字的长度次,结果打印出的是名字
}
System.out.println();
//}
} finally {
lock.unlock();//解锁,因为使用lock()方法上锁后如果锁内的代码出现问题,比如异常,那么这个锁将永远
//无法解除,而在finally中,可以避免这个问题。
}
}
}
}
二,读写锁:
当要对共享的资源进行读写操作的时候,可以同时多个线程进行读操作,但是读的时候不允许别的线程进来写,写的时候也不允许别别的线程来写,写的时候不允许别的线程
进来读。
示例:(读写锁的使用)
import java.util.Random;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/*
* 创建一个类,类中提供的有读数据和写数据的方法,这两个方法共享同一个数据对象,创建多个线程,
* 分别读和写数据,使用读写锁解决线程安全问题。
*/
public class ReadAndWriteLock {
public static void main(String[] args) {
final Queue q = new Queue();
for(int i=0;i<3;i++) {
//创建三个线程,读数据
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
q.getData();
}
}
}).start();
//创建三个线程设置数据
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
q.setData(new Random().nextInt(10000));
}
}
}).start();
}
}
static class Queue {
private Object data = null;//该data是共享的数据,只能有一个线程能够写该数据,但是可以同时又多个线程可以读该数据
//创建读写锁,在读方法上上读锁,读的时候不能写,在写方法上上写锁,写的时候不能读,也不能别的线程进来写
ReadWriteLock rwl = new ReentrantReadWriteLock();//读写所
public void getData() {
rwl.readLock().lock();//上读锁
try {
System.out.println(Thread.currentThread().getName()
+ "start get data");
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "had get data :" + data);
} finally {
rwl.readLock().unlock();
}
}
public void setData(Object data) {
rwl.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "begin set data");
Thread.sleep(new Random().nextInt(1000));
this.data = data;
System.out.println(Thread.currentThread().getName() + "has set data :" + data);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
rwl.writeLock().unlock();
}
}
}
}
读写锁的使用:设计一个缓存系统。
原理:当用户来请求数据时,先通过缓存系统,缓存系统然后去数据库里面取。当缓存系统取数据后,缓存起来,当下次用户在请求相同的数据时,就直接从缓存系统里面直接取,二不需要再从数据库中取,就可以提高效率。
示例:通过map集合存取键值,用户通过请求(键)获取数据(值),当缓存的有时,就不去数据库再次获取。
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class CacheSystem {
//创建一个map集合存储缓存 信息,缓存在存储设备中都是键值对的形式存在的。
private Map<String, Object> cache = new HashMap<>();
public static void main(String[] args) {
}
//一般的程序员直接在该方法上面加一个锁就可以了,就是面试官要的答案,但是使用jdk1.5线程机制则体现水平
private ReadWriteLock rwl = new ReentrantReadWriteLock();
public Object getData(String key) {
rwl.readLock().lock();//当线程进去读的时候上一个读锁
Object value;
try {
value = cache.get(key);
if(value== null) {
//当发现值为空时,就将解除读锁,上一个写锁,写入数据
rwl.readLock().unlock();
rwl.writeLock().lock();
try {
/*
* 下面还要在判断一次的原因是:当多个线程一开始获取数据时,没有,也就是上面一个value为null满足,
* 这时候读锁被解,最先读的那个线程上写锁,然后其余线程阻塞在这里,这时候这个线程进去写入数据,写
* 完后释放写锁,之前阻塞的线程获取写锁,进入后如果不判断value是不是为空,那么会重复获取数据,实
* 际是访问数据库,这时候就没有意义了。
*/
if (value == null) {
value = "abc";//这里实际是取queryDB;
}
} finally {
rwl.writeLock().unlock();
}
rwl.readLock().lock();
}
}finally {
rwl.readLock().unlock();
}
return value;
}
}
三,jdk1.5提供了显式的锁机制,也可以实现线程间的通信。使用显示锁机制实现通信,使用的是Condition类对象。
方法:
1,创建Lock的子类对象:Lock lock = new ReentrantLock();
2,通过Lock对象获取Condition对象,返回用来与此 Lock 实例一起使用的 Condition 实例。
Condition t1 = lock.newCondition();可以创建多个Condition用来等待(await()方法)和唤醒(signal()方法)指定的Condition对象。也就是说方法之间的同步,可以每个方法都占有一个Condition对象,这样线程执行完该方法后就可以通过不同的Condition对象唤醒指定的线程。传统的线程间通信是先通过synchronized给方法上锁,当一个线程执行完某个方法后,别的多个线程在等待时(wait()方法),这个线程通过notify()方法只能唤醒某一个在等待的线程,如果唤醒多个,则使用notifyAll()方法,唤醒所有的线程,而不是唤醒需要被唤醒的线程来执行它该执行的线程。而Condition对象则是唤醒应该醒的线程,不该醒的则继续等待。
示例:三个线程之间的通信。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
* 需求:子线程循环10次,接着主线程循环100次,接着子线程又循环10,子线程在循环10次,如此反复执行50次。
* 分析:将要用到的共同数据(包括同步锁)或共同算法的若干个方法归结到同一个类身上,这种方式体现了高类聚和程序的健壮性。
*
* 改进一下该程序,让三个线程之间通信。当第一个子线程执行完后,第二个子线程执行,执行完后主线程执行,并且之后按照这个
* 顺序轮换执行。
*/
public class ConditionDemo {
public static void main(String[] args) {
//创建共同类的对象
final Business business = new Business();
//子线程执行100次
new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<50;i++) {
business.childThread();
}
}
}).start();
//创建第二个子线程,让三个线程之间进行通信
new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<50;i++) {
business.childThread2();
}
}
}).start();
//主线程执行50次
for(int i=0;i<50;i++) {
business.mainThread();
}
}
//传统的线程通信室通过synchronized在方法上面上锁,然后使用wait()和notify()等待和唤醒处于等待的线程。
//在jdk1.5提供了Condition类,可以明确的指定唤醒哪个等待的线程
static class Business {
final Lock lock = new ReentrantLock();
//创建三个Condition对象,标志三个线程的等待与唤醒
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
//boolean flag = true;//该标志代表是不是该子线程执行循环了,一开始是子线程先执行
int shuldSub = 1;//一开始执行50次循环的子线程执行
//子线程循环10次的代码
public /*synchronized*/ void childThread() {
lock.lock();
try {
//判断sulldSub的值,如果不是等于1,则等待,否则循环开始
while (shuldSub != 1) {
try {
//this.wait();
condition1.await();//调用该方法的线程等待
} catch (Exception e) {
e.printStackTrace();
}
}
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()
+ " loop of " + (i + 1));
}
shuldSub = 2;//循环结束后,把标志置为2,然后唤醒第二个子线程(执行20次循环的)
//this.notify();
condition2.signal();
} finally {
lock.unlock();
}
}
//子线程2,让子线程2循环20次
public /*synchronized*/ void childThread2() {
lock.lock();
try {
//判断shuldSub是不是等于2,不是则condition2等待,否则执行循环
while (shuldSub != 2) {
try {
//this.wait();
condition2.await();
} catch (Exception e) {
e.printStackTrace();
}
}
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()
+ " loop of " + (i + 1));
}
shuldSub = 3;//执行完后,将标志置为3,轮到主线程执行了,唤醒主线程的等待
//this.notify();
condition3.signal();
} finally {
lock.unlock();
}
}
//主线程执行循环100次的方法
public /*synchronized*/ void mainThread() {
lock.lock();
try {
while (shuldSub != 3) {
try {
//this.wait();
condition3.await();
} catch (Exception e) {
e.printStackTrace();
}
}
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()
+ " loop of " + (i + 1));
}
shuldSub = 1;
//this.notify();
condition1.signal();//唤醒等待的condition1
} finally {
lock.unlock();
}
}
}
}