Lock接口
它提供3个常用的锁
lock() : 获不到锁就就一直阻塞trylock() :获不到锁就立刻放回 或者 定时的,轮询的获取锁 lockInterruptibly() : 获不到锁时阻塞,但可接受中断信号后退出阻塞状态
ReentrantLock
实现机制
基于冲突的乐观并发策略:
如果共享数据被争用,产生了冲突,那就再进行其他的补偿措施,比如说定时的获取锁,直到成功;不需要把线程挂起,也称为非阻塞的同步
公平性
公平: 多个线程在等待同一个锁时,必须按照申请锁的时间顺序排队等待
非公平: 在锁释放时,任何一个等待锁的线程都有机会获得锁,ReentrantLock构造方法,默然是非公平的什么时候使用
当你需要可定时的和可中断的锁操作,公平队列,或者非块结构的锁,否则请使用synchronized
可中断的例子
public class MyReentrantLock { private ReentrantLock lock = new ReentrantLock(); public void write() { lock.lock(); try { long startTime = System.currentTimeMillis(); System.out.println("开始往这个buff写入数据…"); for (;;)// 模拟要处理很长时间 { if (System.currentTimeMillis() - startTime > Integer.MAX_VALUE) { break; } } System.out.println("终于写完了"); } finally { lock.unlock(); } } public void read() throws InterruptedException { lock.lockInterruptibly();// 注意这里,可以响应中断 try { System.out.println("从这个buff读数据"); } finally { lock.unlock(); } } public static void main(String args[]) { MyReentrantLock buff = new MyReentrantLock(); final Writer2 writer = new Writer2(buff); final Reader2 reader = new Reader2(buff); writer.start(); reader.start(); new Thread(new Runnable() { @Override public void run() { long start = System.currentTimeMillis(); for (;;) { if (System.currentTimeMillis() - start > 5000) { System.out.println("不等了,尝试中断"); reader.interrupt(); //此处中断读操作 break; } } } }).start(); } } class Reader2 extends Thread { private MyReentrantLock buff; public Reader2(MyReentrantLock buff) { this.buff = buff; } @Override public void run() { try { buff.read();//可以收到中断的异常,从而有效退出 } catch (InterruptedException e) { System.out.println("我不读了"); } System.out.println("读结束"); } } class Writer2 extends Thread { private MyReentrantLock buff; public Writer2(MyReentrantLock buff) { this.buff = buff; } @Override public void run() { buff.write(); } }
控制台输出:
开始往这个buff写入数据…不等了,尝试中断我不读了读结束
ReentrantReadWriteLock读写锁
特点
互斥:它使得读写操作互斥,读读操作不互斥锁降级:写线程获取写入锁后可以获取读取锁,然后释放写入锁,这样就从写入锁变成了读取锁
少写多读的例子
public class MyReadWriteLock { public static void main(String[] args) { PricesInfo pricesInfo = new PricesInfo(); Writer writer=new Writer(pricesInfo); Reader read = new Reader(pricesInfo); //写线程 Thread tw=new Thread(writer); tw.start(); //多个读线程 for (int i=0; i<5; i++){ Thread tr=new Thread(read); tr.start(); } }}//读线程class Reader implements Runnable{ private PricesInfo pricesInfo; public Reader(PricesInfo pricesInfo){ this.pricesInfo = pricesInfo; } @Override public void run() { pricesInfo.getPrice(); }}//写线程class Writer implements Runnable{ private PricesInfo pricesInfo; public Writer(PricesInfo pricesInfo){ this.pricesInfo = pricesInfo; } @Override public void run() { pricesInfo.setPrice(Math.random()*10); }}//数据实体class PricesInfo { private double price; private ReadWriteLock lock = new ReentrantReadWriteLock(); public PricesInfo(){ } //读锁 public void getPrice(){ lock.readLock().lock(); System.out.println(Thread.currentThread().getName()+ " : in read*****************************"); System.out.println(Thread.currentThread().getName()+ ": 读取数据= " + price); lock.readLock().unlock(); } //写锁 public void setPrice(double price){ lock.writeLock().lock(); try { System.out.println(Thread.currentThread().getName()+ " :in Writer=============================================="); Thread.sleep(1000); this.price = price; System.out.println(Thread.currentThread().getName()+ ":写入数据= " + price); } catch (InterruptedException e) { e.printStackTrace(); }finally { lock.writeLock().unlock(); } }}
控制台输出:
Thread-0 :in Writer==============================================Thread-0:写入数据= 3.5843085966236266Thread-3 : in read*****************************Thread-3: 读取数据= 3.5843085966236266......
Condition条件变量
通过ReentrantLock的newCondition()得到Condition对象,它用await()替换wait(),用signal()替换 notify(),用signalAll()替换notifyAll(), 实现线程间的通信;
如果是公平锁,与Condition关联的任务,以FIFO的形式获取锁,否则的话,是随机获取锁;
消费者和生产者的例子
public class MyCondition{ public static void main(String args[]){ Info info = new Info(); //启动生产者 Producer pro = new Producer(info) ; new Thread(pro).start() ; try{ Thread.sleep(100) ; }catch(InterruptedException e){ e.printStackTrace() ; } //启动消费者 Consumer con = new Consumer(info) ; new Thread(con).start() ; } } class Info{ // 定义信息类 private String name = null; private String content = null ; private boolean flag = true ; // true生产, false消费 private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); //产生一个Condition对象 public void set(String name,String content){ lock.lock(); try{ while(!flag){ condition.await() ; } this.setName(name) ; Thread.sleep(300) ; this.setContent(content) ; flag = false ; // 改变标志位,表示可以取走 System.out.println("生产者: " + this.getName() + " --> " + this.getContent()) ; condition.signal(); }catch(InterruptedException e){ e.printStackTrace() ; }finally{ lock.unlock(); } } public void get(){ lock.lock(); try{ while(flag){ condition.await() ; } Thread.sleep(300) ; System.out.println("消费者: " + this.getName() + " --> " + this.getContent()) ; flag = true ; // 改变标志位,表示可以生产 condition.signal(); }catch(InterruptedException e){ e.printStackTrace() ; }finally{ lock.unlock(); } } public void setName(String name){ this.name = name ; } public void setContent(String content){ this.content = content ; } public String getName(){ return this.name ; } public String getContent(){ return this.content ; } } /**生产者线程 */class Producer implements Runnable{ private Info info = null ; // 保存Info引用 public Producer(Info info){ this.info = info ; } public void run(){ boolean flag = true ; // 定义标记位 for(int i=0;i<10;i++){ if(flag){ this.info.set("姓名--1","内容--1") ; flag = false ; }else{ this.info.set("姓名--2","内容--2") ; flag = true ; } } } } /**消费者线程 */class Consumer implements Runnable{ private Info info = null ; public Consumer(Info info){ this.info = info ; } public void run(){ for(int i=0;i<10;i++){ this.info.get() ; } } }
AQS 和 CAS
AQS : JUC基础类
state : 获取锁的标志
NOde{} : 获取锁的线程
SHARED : 共享锁 EXCLUSIVE : 互斥锁
CLH同步队列
LockSupport.park() 和 LockSupport.unpark() :阻塞和唤醒CAS: JUC基础理论
对内存中共享数据进行操作的指令集, 自动更新共享数据, 代替了锁
内存值V,旧的预期值A,要修改的新值B。当且仅A和内存值V相同时,将内存值V修改为B,否则什么都不做
ABA问题
因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A