目标:编写程序实现把数加1000减1000后打印出来。使用两个线程,一个读线程,一个写线程。
计算机读取数据时的操作顺序:
Iload
Icompute
Istore
1.如果两个写线程同时操作一个数字,会造成冲突(第一个写线程计算结果写入前,第二个写线程读取了之前的数据,因此第二个写线程写入计算数据时,没有包含第一个写线程的结果),需要加入一个锁2.(已经封装在synchronized关键字中了),这样一个写线程只有拿到锁才能操作数字。
3.但是读线程没有锁,读的时候同时写线程运行就会读不到最新数据,4.而常见的是写操作全部做完,再运行读线程。因此需要加一个写线程结束的判断标志,给读线程也加一个锁,拿到锁后才能读数据,5.并且读线程拿到锁的时候不能阻塞线程,以避免造成死锁(读线程拿到锁后等待写线程完成,而写线程无限等待读线程释放锁)。
public class FifthAttempt{
private int number=0;
private final Object lock=new Object();
private bookean writeCompleted=false;
public void read(){
synchronized(lock){
while(!writeCompleted){
try{
lock.wait();
}
catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(number);
}
}
}
public void write(int n){
synchronized(lock){
number+=n;
}
}
@Test
public void test() throws InterruptedException{
new Thread(() -> {
for(int i=0;i<1000;i++){
write(1);
}
System.out.println("加1000已完成");
writeCompleted=true;
synchronized(lock){
lock.notifyAll();
}
}).start();
new Thread(() -> {
for(int i=0;i<1000;i++){
write(-1);
}
System.out.println("减1000已完成");
writeCompleted=true;
synchronized(lock){
lock.notifyAll();
}
}).start();
//防止读写线程同时访问数字
Thread.sleep(1000);
read();
}
}
6.synchronized关键字太容易造成死锁,因此使用Java5引入的并发工具包java.util.concurrent,7.设置最长获取时间,超过时间不再等待获取锁。
8.concurrent包还用类Condition改造了wait(),notify()为await(),signal(),可以给等待唤醒设置最长时间,超过时间自动唤醒。
public class EighthAttempt{
private int number=0;
private final ReentrantLock lock=new ReentrantLock();
private final condition=lock.newCondition();
private boolean isCompleted=false;
public void read(){
lock.lock();
while(!IsCompleted){
try{
condition.await();
}
catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(number);
}
}
public void write(int n){
if(lock.tryLock(1, TimeUnit.SECONDS)){
lock.lock();
number+=n;
lock.unlock();
}
}
@Test
public void test() throws InterruptedException{
new Thread(() -> {
for(int i=0;i<1000;i++){
write(1);
}
System.out.println("加1000已完成");
isCompleted=true;
lock.lock();
condition.signal();
lock.unlock();
}).start();
new Thread(() -> {
for(int i=0;i<1000;i++){
write(-1);
}
System.out.println("减1000已完成");
isCompleted=true;
lock.lock();
condition.signal();
lock.unlock();
}).start();
//防止读写线程同时访问数字
Thread.sleep(1000);
read();
}
}
9.使用ReadWriteLock,读线程是可以同时操作数据的。当有写线程时,其他线程不能读也不能写;当没有写线程时,其他线程(读线程)可以同时操作数据。
10. 但是,ReadWriteLock读线程的同时是不允许线程的,必须等所有线程读完后才允许修改,称为悲观锁。也可以性能与稳定兼备:乐观地估计读取时不会写入,读取后再做一个检查,如果读取后没有写入,就直接使用读取的数据,乐观的估计为我们提高了效率;如果有写入,就使用悲观锁重新读取数据。检查的方式是StampedLock读取前记录一个戳,写入后修改戳,读取完成后判断这个版本号是否被修改。
public class TenthAttempt{
private int number=0;
private final StampedLock lock=new StampedLock();
public void read(){
long stamp=lock.tryOptimisticRead();
if(!lock.validate(stamp)){
stamp=lock.readLock();
System.out.println(number);
lock.unlockRead(stamp);
}
else {
System.out.println(number);
}
}
public void write(int n){
long stamp=lock.writeLock();
number+=n;
lock.unlockWrite(stamp);
}
@Test
public void test() throws InterruptedException{
new Thread(() -> {
for(int i=0;i<1000;i++){
write(1);
}
System.out.println("加1000已完成");
}).start();
new Thread(() -> {
for(int i=0;i<1000;i++){
write(-1);
}
System.out.println("减1000已完成");
}).start();
//防止读写线程同时访问数字
Thread.sleep(1000);
read();
}
}
参考:mp.weixin.qq.com/s/z1aMiMerT…