前言:
一、线程的互斥
(一)传统互斥:synchronized 关键字
1、synchronized(对象) 代码块需要锁定同一个对象,一般会锁定业务类对象,即synchronized(this)即可。
2、如静态方法互斥,则需要锁定内存中的字节码对象,即synchronized(XXX.class)。3、synchronized 方法锁定的也是对象。
/** * synchronized 方法 * 其实锁定的也是Outputer 对象 * @param name */ public synchronized void output1(String name) { int len = name.length(); for (int i = 0; i < len; i++) { System.out.print(name.charAt(i)); } System.out.println(); }
(二)juc互斥:Lock
1、Lock 比传统线程模型中的 synchronized 方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象。
Lock lock = new ReentrantLock();
2、两个线程执行的代码片段要实现同步互斥的效果,它们必须使用同一个锁对象。
3、锁(Lock)是上在要操作的资源的类的内部方法中,而不是线程代码中。
package com.newThread; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * * LockAndCondition.java * * @title Lock和Condition线程同步(替代synchronized) * @description * @author SAM-SHO * @Date 2014-8-23 */ public class LockAndCondition { /** * @param args */ public static void main(String[] args) { LockAndCondition tLockAndCondition = new LockAndCondition(); tLockAndCondition.init(); } /** * 定义初始化方法, 解决main(静态)方法不能创建内部类实例对象 */ public void init() { FirstThread firstThread = new FirstThread(); firstThread.start(); SecondThread secondThread = new SecondThread(); secondThread.start(); } /** * * Outputer.java * * @title 内部类实现输出,锁在资源类的方法中 * @description * @author SAM-SHO * @Date 2014-8-17 */ class Outputer { // 使用锁:LOCK解决 Lock lock = new ReentrantLock();//同一个锁对象 public void output3(String name) { int len = name.length(); try { lock.lock(); for (int i = 0; i < len; i++) { System.out.print(name.charAt(i)); } System.out.println(); } finally { lock.unlock(); } } /** * 传统互斥,使用synchronized 关键字 * @param name */ public synchronized void output(String name) { int len = name.length(); for (int i = 0; i < len; i++) { System.out.print(name.charAt(i)); } System.out.println(); } } /* * 线程一 */ class FirstThread extends Thread { Outputer out = new Outputer(); @Override public void run() { while (true) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } out.output("ShaoXiaoBao"); } } } /* * 线程二 */ class SecondThread extends Thread { Outputer out = new Outputer(); @Override public void run() { while (true) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } out.output("ZhaoXiaoNiu"); } } } }
二、读写锁:ReadWriteLock
1、互斥关系:读写锁,分为读锁与写锁。多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥。
2、控制:这种读写锁的互斥关系是由 JVM 自己控制的,程序员只要上好相应的锁即可。
package com.newThread; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * * ReadAndWriteLock.java * * @title 读写锁 * @description * @author SAM-SHO * @Date 2014-11-29 */ public class ReadAndWriteLock { /** * @param args */ public static void main(String[] args) { ReadAndWriteLock tReadAndWriteLock= new ReadAndWriteLock(); tReadAndWriteLock.init(); } private void init() { Queue tQueue = new Queue(); OneReadThread oneRThread = new OneReadThread(tQueue); new Thread(oneRThread).start(); OneWriteThread oneWThraed= new OneWriteThread(tQueue); new Thread(oneWThraed).start(); TwoReadThread twoRThread = new TwoReadThread(tQueue); new Thread(twoRThread).start(); TwoWriteThread twoWThread= new TwoWriteThread(tQueue); new Thread(twoWThread).start(); } /** * @title 读的线程 * @description * @author SAM-SHO * @Date 2014-8-23 */ class OneReadThread implements Runnable{ private Queue tQueue; public OneReadThread(Queue tQueue) { super(); this.tQueue = tQueue; } @Override public void run() { for (int i = 0; i < 5; i++) { tQueue.read(); } } } class TwoReadThread implements Runnable{ private Queue tQueue; public TwoReadThread(Queue tQueue) { super(); this.tQueue = tQueue; } @Override public void run() { for (int i = 0; i < 5; i++) { tQueue.read(); } } } /** * @title 写的线程 * @description * @author SAM-SHO * @Date 2014-8-23 */ class OneWriteThread implements Runnable{ private Queue tQueue; public OneWriteThread(Queue tQueue) { super(); this.tQueue = tQueue; } @Override public void run() { for (int i = 3; i < 7; i++) { tQueue.write("woshibei-- " + i); } } } class TwoWriteThread implements Runnable{ private Queue tQueue; public TwoWriteThread(Queue tQueue) { super(); this.tQueue = tQueue; } @Override public void run() { for (int i = 0; i < 2; i++) { tQueue.write("woshibei-- " + i); } } } /** * @title 具体业务类 * @description * @author SAM-SHO * @Date 2014-8-23 */ class Queue{ //共享数据,只能有一个线程能写该数据,但可以多个线程同时读数据 private Object data = null; //使用读写锁 ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); //读数据的方法 public void read() { readWriteLock.readLock().lock();//上锁 try { System.out.println("开始读数据....."); Thread.sleep((long) (Math.random()*1000)); System.out.println(Thread.currentThread().getName()+"已经读到数据: " + data); } catch (InterruptedException e) { e.printStackTrace(); }finally{ readWriteLock.readLock().unlock();//解锁 } } //写数据的方法 public void write(Object tData) { readWriteLock.writeLock().lock();//上锁 try { System.out.println("准备写数据....."); Thread.sleep((long) (Math.random()*1000)); this.data = tData; System.out.println(Thread.currentThread().getName()+"已经写完数据: " + data); } catch (InterruptedException e) { e.printStackTrace(); }finally { readWriteLock.writeLock().unlock();//解锁 } } } }
3、利用读写锁实现一个简单的缓存类。
package com.newThread; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * * CacheDemo.java * * @title 利用读写锁实现缓存 * @description * @author SAM-SHO * @Date 2014-11-30 */ public class CacheDemo { // 模拟缓存容器 private Map<String, Object> cache = new HashMap<String, Object>(); // 读写锁对象 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 = "aaaa";// 赋值,去数据库查询 } } finally { rwl.writeLock().unlock();//写锁解除 } rwl.readLock().lock(); } } finally { rwl.readLock().unlock(); } return value; } }
三、同步
(一)传统同步:wait、notify
1、wait、notify必须在 synchronized 代码块内部。不然会报状态不对的 Exception 。
/** * * Bussiness * * @title 业务实现类 * @description 用到的共同数据的若干方法应该设计在一个类身上 * * @author SAM-SHO * @Date 2014-8-17 */ class Bussiness { private boolean bShouldSub = true; /* * 循环10次的方法 * if 可以用 while代替,会多检查一次 * synchronized(对象) 与 对象.wait 必须为同一个 */ public synchronized void eachChild(int index) { while(!bShouldSub) {//一开始是true,那么就是执行循环 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } for (int i = 0; i <= 10; i++) { System.out.println("子 线程-- "+i+ ", 第"+index+"循环"); } bShouldSub = false; this.notify();//唤醒主线程 } /* * 循环50次的方法 * if 可以用 while代替 */ public synchronized void eachMain(int index) { while(bShouldSub) {//一开始是true,那么就会wait try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } for (int i = 0; i <= 30; i++) { System.out.println("主 线程---- "+i + ", 第"+index+"循环"); } bShouldSub = true; this.notify();//唤醒子线程 } }
(二)juc同步通信:Condition
1、Lock只能实现线程的互斥,不能实现通信。Condition的功能类似于传统线程技术中的wait、notify 的功能。
Lock lock = new ReentrantLock();//condition是基于锁之上的 Condition condition = lock.newCondition();
2、在等待 Condition 时,允许发生“虚假唤醒”,这通常作为对基础平台的让步。对于大多数应用程序,这带来的实际影响很小,因为 Condition 应该总是在一个循环中被等待,并测试正被等待的状态声明。某个实现可以随意移除可能的虚假唤醒,但建议应用程序程序员总是假定这些虚假唤醒可能发生,因此总是在一个循环中等待
3、Condition 的两个方法:condition.await();//等待,condition.signal();//唤醒
/** * * Bussiness * * @title 业务实现类 Condition 实现 * @description 用到的共同数据的若干方法应该设计在一个类身上 * * @author SAM-SHO * @Date 2014-8-17 */ class BussinessCondition { Lock lock = new ReentrantLock();//condition是基于锁之上的 Condition condition = lock.newCondition(); private boolean bShouldSub = true; /* * 循环10次的方法 * if 可以用 while代替 可以再次检查条件,防止虚假唤醒 */ public void eachChild(int index) { lock.lock();//锁掉 try{ while(!bShouldSub) { try { condition.await();//等待 } catch (Exception e) { e.printStackTrace(); } } for (int i = 0; i <= 10; i++) { System.out.println("子 线程-- "+i+ ", 第"+index+"循环"); } bShouldSub = false; condition.signal();//唤醒 }finally{ lock.unlock();//解锁 } } /* * 循环50次的方法 * if 可以用 while代替 */ public void eachMain(int index) { lock.lock();//锁掉 try{ while(bShouldSub) { try { condition.await();//等待 } catch (Exception e) { e.printStackTrace(); } } for (int i = 0; i <= 50; i++) { System.out.println("主 线程---- "+i + ", 第"+index+"循环"); } bShouldSub = true; condition.signal();//等待 }finally{ lock.unlock();//解锁 } } }
4、Condition 是基于Lock 的。一个 Lock 可以有多个 Condition 。
5、利用Lock 和 Condition 实现一个阻塞队列。(线程队列 ArrayBlockingQueue 类提供了这项功能)
1)如有两种功能的线程(如生产者、消费者)。 如果只用一个 condition ,取的condition只能唤醒取的,放的condition只能唤醒放的。
2)如果有5个放的,其中一个放的等待,然后去唤醒,只能去唤醒另外的放的,还是达不到唤醒取的效果。所以需要用两个condition,区分2种不同功能的线程。
package com.newThread; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * * BoundedBuffer.java * * @title 线程队列 ArrayBlockingQueue 类提供了这项功能,因此没有理由去实现这个示例类。) * @description * 如果只用一个condition(取的condition只能唤醒取的,放的condition只能唤醒放的),如果有5个放的, * 其中一个放的等待,然后去唤醒,只能去唤醒另外的放的,还是达不到唤醒取的作用。所以用两个condition,区分 * 2种不同功能的线程。 * @author SAM-SHO * @Date 2014-8-25 */ public class BoundedBuffer { final Lock lock = new ReentrantLock(); //两个Condition基于同一个Lock final Condition notFull = lock.newCondition(); //“放”功能的线程的 Condition final Condition notEmpty = lock.newCondition(); //“取”功能的线程的 Condition final Object[] items = new Object[100];//容器容量为100 int putptr, takeptr, count; /** * 往队列放数据 * @param x * @throws InterruptedException */ public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) {//放满了 notFull.await();//"放"的 Condition 等待 } items[putptr] = x; //把 x 放入队列 if (++putptr == items.length) {//如果位置放到100了,从新从 0 开始放 putptr = 0; } ++count;//放进去一个就 count++ notEmpty.signal();//唤醒"取"的 Condition } finally { lock.unlock(); } } /** * 从队列取数据 * @return * @throws InterruptedException */ public Object take() throws InterruptedException { lock.lock(); try { while (count == 0) {//队列中没有数据 notEmpty.await();//"取"的等待 } Object x = items[takeptr]; if (++takeptr == items.length) {//取值已到 100 从 0开始取 takeptr = 0; } --count;//取走一个 count-- notFull.signal();//唤醒"放"的 return x; } finally { lock.unlock(); } } }