并发编程之共享资源访问
1.解决共享资源竞争问题
设想一种极端状况,A、B两人都想拿篮子中的苹果,但是现在篮子中只剩一个苹果,如果我们不加管理让A、B自己去争,可能会导致A、B为此掐架。所以我们应该有个规定,比如:当A、B都想拿苹果时,谁先碰到篮子谁先拿苹果(同时碰到不做考虑),并且当其中一个人拿完之后归还篮子,另个一人才能去取。这个篮子中的苹果就相当于共享资源,篮子就相当锁。谁先碰到篮子就意味着谁先拿到锁,只有持有锁的人才能对资源进行访问,归还篮子就相当于释放锁,这样就又进行下一轮的资源竞争。
- java中提供了synchronized关键字来对共享资源(即所谓的域)进行控制访问 关于该关键字的使用方法详见该博文 http://www.cnblogs.com/GnagWang/archive/2011/02/27/1966606.html
- synchronized是可重入锁。同一线程在调用自己类中其他synchronized方法/块或调用父类的synchronized方法/块都不会阻碍该线程的执行,就是说同一线程对同一个对象锁是可重入的,而且同一个线程可以获取同一把锁多次,也就是可以多次重入。关于synchronized的具体实现可以参考这篇文章 http://www.cnblogs.com/xidongyu/p/6531285.html
- 使用显示的Lock对象,但是该对象必须被显示的创建、锁定和释放 使用方法 http://www.cnblogs.com/dolphin0520/p/3923167.html
2.原子类
2.1 原子性操作
原子性操作可用于long和double之外的所有基本类型上的“简单操作”(即不可分割操作)。但由于对long或double操作时,并不是原子的。因为JVM虚拟机一次最多能操作32位,所以需要两步才能完成操作。在这两步中间可能会发生上下文的切换。如果使用volatile关键字来修饰的话,就可以让获得原子性。当然这只是让long和double获得了原子性,关于volatile的其它作用可见 http://www.cnblogs.com/xidongyu/p/6531285.html
我们希望下面代码一直是死循环状态,但是在实际运行中并不是这样,如何更改呢?
public class Test implements Runnable { private volatile int i = 0; public static void main(String[] args) { Test test = new Test(); Thread t = new Thread(test); t.start(); while (true) { int val = test.getValue(); if (val % 2 != 0) { System.out.println(val); System.exit(0); } } } @Override public void run() { while (true) { incr(); } } public int getValue() { return i; } public synchronized void incr() { i++; i++; } }
2.2 原子类
java中引入了如AtomicInteger、AtomicLong、AtomicReference等特殊原子性变量类,并提供了相应的更新操作,对于常规编程来说,它们很少会派上用场,但是涉及性能调优时,就会有用武之地。下面只是一个简单的事例程序。
1 import java.util.Timer; 2 import java.util.TimerTask; 3 import java.util.concurrent.atomic.AtomicInteger; 4 5 public class Test { 6 public static void main(String[] args) { 7 new Timer().schedule(new TimerTask() { 8 public void run() { 9 System.out.println("Aborting"); 10 System.exit(0); 11 } 12 }, 10000); 13 AtomicIntegerTest a = new AtomicIntegerTest(); 14 Thread t = new Thread(a); 15 t.start(); 16 while (true) { 17 int val = a.getValue(); 18 if (val % 2 != 0) { 19 System.out.println(val); 20 System.exit(0); 21 } 22 } 23 } 24 } 25 26 class AtomicIntegerTest implements Runnable { 27 private AtomicInteger i = new AtomicInteger(0); 28 public int getValue() { 29 return i.get(); 30 } 31 private void evenIncrement() { 32 i.addAndGet(2); 33 } 34 35 @Override 36 public void run() { 37 while (true) 38 evenIncrement(); 39 } 40 41 }
3.线程本地存储
线程本地存储可以为使用同一种数据类型的不同线程创建不同的存储,可以简单的理解为为每个线程增加了一个额外的属性,我们可以随时随地的进行访问,并且不用担心线程冲突的问题。关于本地存储的详细说明可以参考以下链接 链接1 链接2
每个线程中有个ThreadLocalMap域,这个域在JAVA源码中已经帮我们实现好了。这个域的作用就是用来存储对象的。而TreadLocal类是JAVA中对外提供的一个操作该域的接口。ThreadLocal对象一般被定义为类成员变量。我们可以通过initialValue()方法将对象填充到Map中(也可以将填充推迟到set()方法中),通过get()方法获取填充的对象,通过set()方法重新设置该对象。注意:一个ThreadLocal对象只能填充一个本地对象。如下代码所示,填充了一个Integer对象到ThreadLocalMap中,如果还想填充一个Integer对象则需要再声明一个ThreadLocal。
import java.util.Random; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; public class Test { private static ThreadLocal<Integer> value = new ThreadLocal<Integer>() { private Random rand = new Random(47); protected synchronized Integer initialValue() { return rand.nextInt(1000); } }; public static void increment() { value.set(value.get() + 1); } public static int get() { return value.get(); } public static void main(String[] args) throws InterruptedException { ExecutorService exe = Executors.newCachedThreadPool(); for (int i = 0; i < 5; i++) { exe.execute(new Accessor(i)); } TimeUnit.SECONDS.sleep(3); exe.shutdown(); } } class Accessor implements Runnable { private final int id; public Accessor(int idn) { id = idn; } @Override public void run() { while (!Thread.currentThread().isInterrupted()) { Test.increment(); System.out.println(this); Thread.yield(); } } public String toString() { return "#" + id + ": " + Test.get(); } }
4.阻塞
线程可以通过一下进入阻塞态
- 调用sleep函数 Thread.sleep(1000)或TimeUnit.SECONDS.sleep(1)睡眠1S
- 调用wait()挂起 wait()函数是每个线程都已经实现的函数,并且只能通过notify()或notifyAll()来唤醒
- I/O操作
- 等待锁
5.中断
每个线程有个中断标志位,当调用该线程的Interrupt()方法时,会把该中断标志位置为1,可以在该线程内部通过isInterrupted()方法查看该位是否被置为1,但是标志位不会被清零。使用Thread.interrupted方法也可查看该位是否为1,并具有清零效果。当然在有些情况下,触发中断会直接抛出异常。下面的事例代码是在睡眠阻塞、I/O阻塞和锁阻塞的情况下使用中断。
1 import java.io.IOException; 2 import java.io.InputStream; 3 import java.util.concurrent.ExecutorService; 4 import java.util.concurrent.Executors; 5 import java.util.concurrent.Future; 6 import java.util.concurrent.TimeUnit; 7 8 class SleepBlocked implements Runnable { 9 10 @Override 11 public void run() { 12 try { 13 TimeUnit.SECONDS.sleep(100); 14 } catch (InterruptedException e) { 15 System.out.println("InterruptionExecption"); 16 17 } 18 System.out.println("Exiting SleepBlocked.run()"); 19 } 20 } 21 22 class IOBlocked implements Runnable { 23 private InputStream in; 24 public IOBlocked(InputStream is) { 25 in = is; 26 } 27 @Override 28 public void run() { 29 try { 30 System.out.println("Waiting for read():"); 31 in.read(); //等待输入而阻塞 32 } catch (IOException e) { 33 if (Thread.currentThread().isInterrupted()) 34 System.out.println("Interrupted from blocked I/O"); 35 else 36 throw new RuntimeException(); 37 } 38 System.out.println("Exiting IOBlocked.run()"); 39 } 40 } 41 42 class SynchronizedBlocked implements Runnable { 43 public synchronized void f() { 44 while (true) //永久性的获取锁,并不释放 45 Thread.yield(); 46 } 47 48 public SynchronizedBlocked() { 49 new Thread() { 50 public void run() { 51 f(); 52 } 53 }.start(); 54 } 55 56 @Override 57 public void run() { 58 System.out.println("Trying to call f()"); 59 f(); //因等待锁而阻塞 60 System.out.println("Exiting SychronizedBlocked.run()"); 61 } 62 } 63 64 class NormalRun implements Runnable { 65 66 @Override 67 public void run() { 68 while (true) { 69 if (Thread.interrupted()) { 70 System.out.println("fdfdf"); 71 break; 72 } 73 74 } 75 } 76 77 } 78 public class Interrupting { 79 private static ExecutorService exec = Executors.newCachedThreadPool(); 80 81 static void test(Runnable r) throws InterruptedException { 82 Future<?> f = exec.submit(r); 83 TimeUnit.MILLISECONDS.sleep(100); 84 System.out.println("Interrupting " + r.getClass().getName()); 85 f.cancel(true); 86 System.out.println("Interrupting sent to " + r.getClass().getName()); 87 } 88 89 public static void main(String[] args) throws Exception { 90 test(new SleepBlocked()); 91 test(new IOBlocked(System.in)); 92 test(new SynchronizedBlocked()); 93 test(new NormalRun()); 94 TimeUnit.MILLISECONDS.sleep(3); 95 System.out.println("Aborting with System.exit(0)"); 96 System.exit(0); 97 } 98 }
运行结果分析:SleepBlocked线程是可中断的,并会捕获中断异常。而IOBlocked和SynchronizedBlocked是不可中断的阻塞示例,即使给他们发中断信信号,它们也置之不顾。代码中也表明在一个非阻塞的线程中也可以通过查看中断标志位来查看是否被中断(没有实际用途)。
6.中断因I/O而发生阻塞的任务
如果我们的确需要中断阻塞的IO操作可以通过关闭底层资源的方式来实现。
1 package com.dy.xidian; 2 3 import java.io.IOException; 4 import java.io.InputStream; 5 import java.net.Socket; 6 import java.util.concurrent.ExecutorService; 7 import java.util.concurrent.Executors; 8 import java.util.concurrent.TimeUnit; 9 10 class IOBlocked implements Runnable { 11 private InputStream in; 12 public IOBlocked(InputStream is) { 13 in = is; 14 } 15 @Override 16 public void run() { 17 System.out.println("Waiting for read()"); 18 try { 19 in.read(); 20 } catch (IOException e) { 21 if (Thread.currentThread().isInterrupted()) 22 System.out.println("Interrupted from blocked I/O"); 23 else 24 throw new RuntimeException(); 25 } 26 System.out.println("Exiting IOBlock.run()"); 27 } 28 29 } 30 public class CloseResource { 31 public static void main(String[] args) throws Exception { 32 ExecutorService exec = Executors.newCachedThreadPool(); 33 @SuppressWarnings("resource") 34 InputStream socketInput = new Socket("localhost", 80).getInputStream(); 35 exec.execute(new IOBlocked(socketInput)); 36 exec.execute(new IOBlocked(System.in)); 37 TimeUnit.MILLISECONDS.sleep(111); 38 System.out.println("Shutting down all threads"); 39 exec.shutdownNow(); 40 TimeUnit.SECONDS.sleep(1); 41 System.out.println("Closing " + socketInput.getClass().getName()); 42 socketInput.close(); 43 TimeUnit.SECONDS.sleep(1); 44 System.out.println("Closing " + System.in.getClass().getName()); 45 System.in.close(); 46 } 47 }
7.中断因锁阻塞的任务
java中提供了ReentranLock类,在该类上的阻塞任务具备可以被中断的能力。
1 package com.dy.xidian; 2 3 import java.io.IOException; 4 import java.io.InputStream; 5 import java.util.concurrent.TimeUnit; 6 import java.util.concurrent.locks.Lock; 7 import java.util.concurrent.locks.ReentrantLock; 8 9 class BlockedMutex { 10 private Lock lock = new ReentrantLock(); 11 public BlockedMutex() { 12 lock.lock(); //获取锁 13 } 14 15 public void f() { 16 InputStream is = System.in; 17 try { 18 lock.lockInterruptibly(); //如果该线程未被中断,则获取锁;若未拿到锁,则睡眠,直到锁被释放或中断发生 19 System.out.println("lock acquired in f()"); 20 } catch (InterruptedException e) { 21 System.out.println("Interrupted from lock acquisition in f()"); 22 try { 23 is.close(); 24 } catch (IOException e1) { 25 e1.printStackTrace(); 26 } 27 } 28 } 29 } 30 31 class Blocked2 implements Runnable { 32 BlockedMutex blokced = new BlockedMutex(); 33 @Override 34 public void run() { 35 System.out.println("Waiting for f() in BlockedMutex"); 36 blokced.f(); 37 System.out.println("Broken out of blocked call"); 38 } 39 } 40 41 public class Test { 42 public static void main(String[] args) throws InterruptedException { 43 Thread t = new Thread(new Blocked2()); // 在创建该对象时主线程已经持有BlockedMutex对象锁 44 t.start(); 45 TimeUnit.SECONDS.sleep(2); 46 System.out.println("Issuing t.intrrupt()"); 47 t.interrupt(); 48 } 49 }