lock 接口在多线程和并发编程中最大的优势是它们为读和写分别提供了锁,它能满足你写像 ConcurrentHashMap 这样的高性能数据结构和有条件的阻塞。
1 import java.text.SimpleDateFormat; 2 import java.util.Date; 3 import java.util.Random; 4 import java.util.concurrent.locks.ReadWriteLock; 5 import java.util.concurrent.locks.ReentrantReadWriteLock; 6 7 8 9 10 public class JoinTest2 { 11 12 public static void main(String[] args) { 13 final TheData theData = new TheData(); 14 for(int i=0;i<4;i++){ 15 new Thread(new Runnable() { 16 @Override 17 public void run() { 18 theData.get(); 19 } 20 }).start(); 21 } 22 for(int i=0;i<4;i++){ 23 new Thread(new Runnable() { 24 @Override 25 public void run() { 26 theData.put(new Random().nextInt(1000)); 27 } 28 }).start(); 29 } 30 } 31 32 33 } 34 35 class TheData{ 36 private Integer data = 0; 37 private ReadWriteLock rwLock = new ReentrantReadWriteLock(); 38 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); 39 public void get(){ 40 rwLock.readLock().lock();//读锁开启,读进程均可进入 41 try{//用try finally来防止因异常而造成的死锁 42 System.out.println(Thread.currentThread().getName()+"read lock is ready.."+sdf.format(new Date())); 43 Thread.sleep(1000); 44 System.out.println(Thread.currentThread().getName()+"read data is"+data); 45 }catch (InterruptedException e) { 46 e.printStackTrace(); 47 }finally{ 48 rwLock.readLock().unlock();//读锁解锁 49 } 50 } 51 52 public void put(Integer data){ 53 rwLock.writeLock().lock();//写锁开启,这时只有一个写线程进入 54 try{//用try finally来防止因异常而造成的死锁 55 System.out.println(Thread.currentThread().getName()+"write lock is ready.."+sdf.format(new Date())); 56 Thread.sleep(1000); 57 this.data = data; 58 System.out.println(Thread.currentThread().getName()+"write data is"+data); 59 60 }catch (InterruptedException e) { 61 e.printStackTrace(); 62 }finally{ 63 rwLock.writeLock().unlock();//写锁解锁 64 } 65 } 66 }这是一个相对艰难的多线程面试问题,它能达到很多的目的。第一,它可以检测侯选者是否能实际的用 Java 线程写程序;第二,可以检测侯选者对并发场景的理解,并且你可以根据这个问很多问题。如果他用wait()和 notify()方法来实现阻塞队列,你可以要求他用最新的Java 5 中的并发类来再写一次。
import java.util.LinkedList; import java.util.concurrent.atomic.AtomicInteger; /** * 使用wait和notify实现Queue * BlockingQueue: 顾名思义,首先它是一个队列,并且支持阻塞机制,阻塞的放入和阻塞的得到数据, * 我们要实现LinkedBlockingQueue下面下面两个简单的方法put和take * put(anObject):把把对象加到BlockingQueue里面,如果BlockQueue没有空间,则调用此方法的线程被阻断 * 直到BlockingQueue里面没有空间在继续。 *take():取走blockingQueue里面该在首位的对象,若blockingQueue为空,阻塞进入等待状态,直到BlockingQueue有新的数据被加入 * * wait 和notify 结合synchronized使用 * * */ public class MyQueue { //1.需要装元素的集合 private final LinkedList<Object> list = new LinkedList<Object>(); /** * 2.需要一个计数器,统计加入list几个的个数 *AtomicInteger,一个提供原子操作的Integer的类。在Java语言中 *,++i和i++操作并不是线程安全的,在使用的时候,不可避免的会用到 *synchronized关键字。而AtomicInteger则通过一种线程安全的加减操作接口。 * */ private AtomicInteger count = new AtomicInteger(0); //指定上限上限和下限 private final int minSize = 0; private final int maxSize; //构造方法,构造容器最大长度 public MyQueue(int size) { this.maxSize = size; } public MyQueue() { //最大长度默认为10 this(10); } /** * 初始化一个对象用于加锁 * */ private final Object lock = new Object(); //返回长度 public int size() { return count.get(); } /** * 添加一个对象,如果队列满了,则阻塞 * */ public void put(Object obj) { synchronized (lock) { //如果容器大小刚好等于最大长度,则阻塞 while(size() == maxSize) { try { lock.wait();//阻塞 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } list.add(obj); count.incrementAndGet();//相当于i++ System.out.println("新加入的元素为:"+obj); lock.notify();//通知另外一个线程去取元素 } } /** * 取出一个元素,如果队列为空,则阻塞 * */ public Object take() { synchronized (lock) { //如果容器的大小刚好等于队列最小长度,则阻塞 while(minSize == size()) { try { lock.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //取出第一个元素 Object obj = list.removeFirst(); //计数器递减 count.decrementAndGet(); lock.notify();//通知另外一个线程进行添加元素 return obj;//返回结果 } } public static void main(String[] args) { final MyQueue m = new MyQueue(5); Thread t1 = new Thread(new Runnable() { @Override public void run() { m.put("a"); m.put("b"); m.put("c"); m.put("d"); m.put("e"); m.put("f"); } }); t1.start(); try { Thread.sleep(1000); System.out.println("当前容器的大小:"+m.size()); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } Thread t2 = new Thread(new Runnable() { @Override public void run() { Object o = m.take(); System.out.println("取出的元素为:"+o); } }); t2.start(); try { Thread.sleep(10); System.out.println(m.size()); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
一个很经典的例子就是银行账户转账问题:
比如从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。
试想一下,如果这2个操作不具备原子性,会造成什么样的后果。假如从账户A减去1000元之后,操作突然中止。然后又从B取出了500元,取出500元之后,再执行 往账户B加上1000元 的操作。这样就会导致账户A虽然减去了1000元,但是账户B没有收到这个转过来的1000元
。
所以这2个操作必须要具备原子性才能保证不出现一些意外的问题。
区别: 一、volatile是变量修饰符,而synchronized则作用于一段代码或方法。
二、volatile只是在线程内存和“主”内存间同步某个变量的值;而synchronized通过锁定和解锁某个监视器同步所有变量的值。显然synchronized要比volatile消耗更多资源。
加锁解决
互斥锁:每个对象都对应于一个可称为’互斥锁‘的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。 同一进程中的多线程之间是共享系统资源的,多个线程同时对一个对象进行操作,一个线程操作尚未结束,另一个线程已经对其进行操作,导致最终结果出现错误,此时需要对被操作对象添加互斥锁,保证每个线程对该对象的操作都得到正确的结果。
这是个关于线程和阻塞的棘手的问题,它有很多解决方法。如果线程遇到了 IO 阻塞,我并 且不认为有一种方法可以中止线程。如果线程因为调用 wait()、sleep()、或者 join()方法而导致的阻塞,你可以中断线程,并且通过抛出 InterruptedException 来唤醒它。
不可变对象(Immutable Objects)即对象一旦被创建它的状态(对象的数据,也即 对象属性值)就不能改变,反之即为可变对象(Mutable Objects)。不可变对象的类即为不可变类(Immutable Class)。Java 平台类库中包含许多不可 变类,如 String、基本类型的包装类、BigInteger 和 BigDecimal 等。不可变对象天生是线程安全的。它们的常量(域)是在构造函数中创建的。既然 它们的状态无法修改,这些常量永远不会变。不可变对象永远是线程安全的。只有满足如下状态,一个对象才是不可变的;
它的状态不能在创建后再被修改; 所有域都是 final 类型;并且, 它被正确创建(创建期间没有发生 this 引用的逸出)。