常用同步工具类学习
Lock&Condition实现线程同步通信
Lock比传统模式的synchronized方式更加面向对象,与生活中的锁类似,锁本事也应该是一个对象。两个线程执行的代码片段要实现同步互斥的效果,它必须用同一个Lock对象。锁是上在代表要操作的资源类的内部方法中,而不是线程代码中。
【例子】
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {
public static void main(String[] args) {
final Outputer outputer = new Outputer();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
outputer.output("德艺双馨");
}
}
}).start();// 线程1
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
outputer.output("index80");
}
}
}).start();// 线程2
}
}
// 创建一个共享类,加锁
class Outputer {
Lock lock = new ReentrantLock();
public void output(String name) {
lock.lock();// 加锁
try {
for (int i = 0; i < name.length(); i++) {
System.out.print(name.charAt(i));
}
System.out.println();
} finally {
lock.unlock();// 解锁
}
}
}
读写锁:分为读锁和写锁,多个读锁不互斥,读锁和写锁互斥,写锁和写锁互斥。如果你写的代码只读数据,可以很多人同时读,但是不能同时写,那就上读锁;如果修改数据,只能一个人在写,且不能同时读,那就上写锁。总之,读的时候上读锁,写的时候上写锁。
【读写锁模拟缓存】
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/*
* 模拟计算机缓存
*/
public class CacheDemo {
private Map<String, Object> cache = new HashMap<String, Object>();
public static void main(String[] args) {
// TODO Auto-generated method stub
}
private ReadWriteLock rwl = new ReentrantReadWriteLock();
public Object getData(String key) {
rwl.readLock().lock();// 上读锁
Object value = null;
try {
value = cache.get(key);
if (value == null) {
rwl.readLock().unlock();// 如果没有数据解读锁
rwl.writeLock().lock();// 并加上写锁
try {
if (value == null) {
value = "去数据库拿数据queryDB();";// 实际是去queryDB();
}
} finally {
rwl.writeLock().unlock();
}
rwl.readLock().lock();
}
} finally {
rwl.readLock().unlock();
}
return value;
}
}
Condition的功能类似于传统线程技术中的Object wait和Object notify的功能,在等待Condition时,允许发生“虚假唤醒”,这通常作为对基础平台语义的让步。对于大多数应用程序,这带来的实际影响很小,因为Condition总是应该在一个循环中被等待,并测试正被等待的状态声明。某个实现可以随意移除可能的虚假唤醒,但建议应用程序员总是假定这些虚假唤醒可能发生,因此总是在一个循环中等待。 一个锁内部可以有多个Condition,即有多路等待通知。
【例子】
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
* Condition实现多线程通信
*/
public class ThreeConditionCommunication {
public static void main(String[] args) {
final Business business = new Business();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 50; i++) {
business.sub2(i);
}
}
}).start();// 线程2
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 50; i++) {
business.sub3(i);
}
}
}).start();// 线程3
for (int i = 1; i <= 50; i++) {
business.main(i);// 主线程
}
}
static class Business {
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
private int shouldSub = 1;
public void sub2(int i) {
lock.lock();
try {
while (shouldSub != 2) {
try {
condition2.await();
} catch (Exception e) {
e.printStackTrace();
}
}
for (int j = 1; j <= 10; j++) {
System.out.println("sub2 thread sequence of " + j
+ ",loop of " + i);
}
shouldSub = 3;
condition3.signal();
} finally {
lock.unlock();
}
}
public void sub3(int i) {
lock.lock();
try {
while (shouldSub != 3) {
try {
condition3.await();
} catch (Exception e) {
e.printStackTrace();
}
}
for (int j = 1; j <= 20; j++) {
System.out.println("sub3 thread sequence of " + j
+ ",loop of " + i);
}
shouldSub = 1;
condition1.signal();
} finally {
lock.unlock();
}
}
public void main(int i) {
lock.lock();
try {
while (shouldSub != 1) {
try {
condition1.await();
} catch (Exception e) {
e.printStackTrace();
}
}
for (int j = 1; j <= 100; j++) {
System.out.println("main thread sequence of " + j
+ ",loop of " + i);
}
shouldSub = 2;
condition2.signal();
} finally {
lock.unlock();
}
}
}
}
Semaphere可以维护访问当前自身的的线程个数,并且提供了同步机制。使用Semaphere可以控制同时访问的资源的线程个数,例如,实现一个文件允许的并发访问数。
Semaphere实现的功能类似于厕所只有5个坑,假如有10个人要上厕所,那么只能有同时5个能够占用,当这5个人中任何1个离开后,其中在等待的另外5个人中又有1个可以占用。等待的5个人可以按随机优先级,也可以按先来后到顺序,这取决与构造Semaphere时传人的参数选项。
单个信号Semaphere对象可以实现互斥的功能,与传统锁不同的是,Semaphere可以由一个线程获得锁而另外的线程解锁,这可用于死锁恢复场合。
【例子】
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
/*
* 一个厕所5个坑,同时可以有5个人上,模拟10个人上厕所
*/
public class SemaphoreTest {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();// 线程池
final Semaphore sp = new Semaphore(5);// 同时可以有5个线程抢占,模拟一个厕所5个坑
for (int i = 0; i < 10; i++) {// 创建10个线程,模拟10个人上厕所
Runnable runnable = new Runnable() {
public void run() {
try {
sp.acquire();//没信号量可用时,将进行阻塞等
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out
.println(Thread.currentThread().getName()
+ "进入,当前可用茅坑" + sp.availablePermits()
+ "个");
System.out.println(Thread.currentThread().getName()
+ "即将离开");
sp.release();
// 下面代码有时候执行不准确,因为其没有和上面的代码合成原子单元
System.out.println(Thread.currentThread().getName()
+ "已离开,当前可用茅坑" + sp.availablePermits()
+ "个");
}
};
service.execute(runnable);
}
}
}