《Java编程思想第四版》笔记---21章(2) 并发

1.java中的原子操作类(P684 21.3.4)

原子操作是指程序编译后对应于一条CPU操作指令,即原子操作是最小的不可再分指令集,编程中的原子操作是线程安全的,不需要使用进行线程同步和加锁机制来确保原子操作的线程同步。

JDK5中引入了java.util.concurrent.atomic原子操作类包,提供了常用的原子类型操作数据类型,如:AtomicInteger,AtomicLong, AtomicReference等等,原子操作类例子如下:

  
  
  1. import java.util.concurrent.*;
  2. import java.util.concurrent.atomic.*;
  3. import java.util.*;
  4. public class AtomicIntegerTest implement Runnable{
  5. //创建一个值为0的Integer类型原子类
  6. private AtomicInteger i = new AtomicInteger(0);
  7. public int getValue(){
  8. //获取Integer类型原子类的值
  9. return i.get();
  10. }
  11. private void evenIncrement(){
  12. //值增加2
  13. i.addAndGet(2);
  14. }
  15. public void run(){
  16. while(true){
  17. evenIncrement();
  18. }
  19. }
  20. public static void main(String[] args){
  21. //创建一个定时任务,5秒钟终止运行
  22. new Timer().schedule(new TimerTask(){
  23. public void run(){
  24. System.err.println(“Aborting”);
  25. System.exit(0);
  26. }
  27. }, 5000);
  28. ExecutorService exec = Executors.newCachedThreadPool();
  29. AtomicIntegerTest at = new AtomicIntegerTest();
  30. Exec.execute(at);
  31. while(true){
  32. int val = at.getValue();
  33. //奇数
  34. if(val % 2 != 0){
  35. System.out.println(val);
  36. System.exit(0);
  37. }
  38. }
  39. }
  40. }

Java中加减运算等不是原子操作,为了确保线程安全必须确保线程同步安全,使用整数类型的原子类之后,整数原子类的加减运算是原子操作,因此evenIncrement()方法不再需要synchronized关键字或者线程锁机制来确保线程安全。

注意:原子类不是Integer等通用类的通用替换方法,原子类不顶用诸如hashCode和compareTo之类的方法,因为源自变量是可变的,所以不是hash表键的好的选择。

2.线程本地存储(P690 21.3.7)

防止多个线程对同一个共享的资源操作碰撞的另一种方法是使用线程局部变量,线程局部变量的机制是为共享的变量在每个线程中创建一个存储区存储变量副本。线程局部变量ThreadLocal实例通常是类中的private static字段,它们希望将状态与某一个线程相关联。例子如下:

  
  
  1. import java.util.concurrent.atomic.AtomicInteger;
  2. public class UniqueThreadGenerator{
  3. private static final AtomicInteger uniqueId = new AtomicInteger(0);
  4. //创建一个线程局部变量
  5. private static final ThreadLocal<Integer> uniqueNum = new ThreadLocal<Integer>(){
  6. //覆盖线程局部变量的initialValue方法,为线程局部变量初始化赋值
  7. protected Integer initialValue(){
  8. return uniqueId.getAndIncrement();
  9. }
  10. }
  11. public static int getCurrentThreadId(){
  12. //获取线程局部变量的当前线程副本中的值
  13. return uniqueId.get();
  14. }
  15. }

线程局部变量java.lang.ThreadLocal类有下面四个方法:

(1) T get():返回此线程局部变量的当前线程副本中的值。

(2) protected T initialValue():返回此线程局部变量的当前线程的初始值。

(3) void remove():移除此线程局部变量当前线程的值。

(4) void set(T value):将此线程局部变量的当前线程副本中的值设置为指定值。

只要线程是活动的并且线程局部变量实例是可访问的,每个线程都保持对其线程局部变量副本的隐式引用。在线程消失之后,其线程局部变量实例的所有副本都会被垃圾回收。

ThreadLocal通常作为静态存储区域,在创建ThreadLocal时,只能使用get()和set()方法访问该对象的内容。

3.线程的yield,sleep和wait的区别

(1).yield:

正在运行的线程让出CPU时间片,让线程调度器运行其他优先级相同的线程。使用yield()处于可运行状态的线程有可能立刻又进入执行状态。

yield不释放对象锁,即yield线程对象中其他synchronized的数据不能被别的线程使用。

(2).sleep:

当前正在运行的线程进入阻塞状态,sleep是线程Thread类的方法,在sleep的时间内线程肯定不会再运行,sleep可使优先级低得线程得到执行的机会,也可以让同优先级和高优先级的线程有执行机会。

sleep的线程如果持有对象的线程锁,则sleep期间也不会释放线程锁,即sleep线程对象中其他synchronized的数据不能被别的线程使用。

sleep方法可以在非synchronized线程同步的方法中调用,因为sleep()不释放锁。

(3).wait:

正在运行的线程进入等待队列中,wait是Object的方法,线程调用wait方法后会释放掉所持有的对象锁,即wait线程对象中其他synchronized的数据可以被别的线程使用。

wait(), notify()和notifyAll()方法必须只能在synchronized线程同步方法中调用,因为在调用这些方法之前必须首先获得线程锁,如果在非线程同步的方法中调用时,编译时没有问题,但在运行时会抛IllegalMonitorStateException异常,异常信息是当前线程不是方法的监视器对象所有者。

4.线程间的通信(P702)

Java中通过wait(),notify()和notifyAll()方法进行线程间的通信,在JDK5之后,又引入了signal()和signalAll()进行线程通信。

(1).wait()和notify(),notifyAll():

wait使得一个正在执行的线程进入阻塞状态,wait也可以象sleep一样指定时间,但是常用的是无参数的wait()方法。notify和notifyAll唤醒处于阻塞态的线程进入可运行状态,通知他们当前的CPU可用。这三个方法都是Object中的方法,不是线程Thread的特有方法。例子如下:

  
  
  1. import java.util.concurent.*;
  2. class Car{
  3. private Boolean waxOn = false;
  4. public synchronized void waxed(){
  5. waxOn = true;
  6. notifyAll();
  7. }
  8. public synchronized void buffed(){
  9. waxOn = false;
  10. notifyAll();
  11. }
  12. public synchronized void waitForWaxing()throws InterruptedException{
  13. while(waxOn == false){
  14. wait();
  15. }
  16. }
  17. public synchronized void waitForBuffing()throws InterruptedException{
  18. while(waxOn == true){
  19. wait();
  20. }
  21. }
  22. }
  23. class WaxOn implements Runnable{
  24. private Car car;
  25. public WaxOn(Car c){
  26. car = c;
  27. }
  28. public void run(){
  29. try{
  30. while(!Thread.interrupted()){
  31. System.out.println(“Wax On!”);
  32. TimeUnit.SECONDS.sleep(1);
  33. car.waxed();
  34. car.waitForBuffing();
  35. }
  36. }catch(InterruptedException e){
  37. System.out.println(“Exiting via interrupt”);
  38. }
  39. System.out.println(“Ending Wax On task”);
  40. }
  41. }
  42. class WaxOff implements Runnable{
  43. private Car car;
  44. public WaxOff(Car c){
  45. car = c;
  46. }
  47. public void run(){
  48. try{
  49. while(!Thread.interrupted()){
  50. car.waitForWaxing();
  51. System.out.println(“Wax Off!”);
  52. TimeUnit.SECONDS.sleep(1);
  53. car.buffed();
  54. }
  55. }catch(InterruptedException e){
  56. System.out.println(“Exiting via interrupt”);
  57. }
  58. System.out.println(“Ending Wax Off task”);
  59. }
  60. }
  61. public class WaxOMatic{
  62. public static void main(String[] args)throws Exception{
  63. Car car = new Car();
  64. ExecutorService exec = Executors.newCachedThreadPool();
  65. exec.execute(new WaxOff(car));
  66. exec.execute(new WaxOn(car));
  67. TimeUnit.SECONDS.sleep(5);
  68. exec.shutdownNow();
  69. }
  70. }

输出结果:

Wax On! Wax Off! Wax On! Wax Off! Wax On!

Exiting via interrupt

Ending Wax On task

Exiting via interrupt

Ending Wax Off task

注意:waite (),notify()和notifyAll()因为会对对象的“锁标志”进行操作,所以它们必须在synchronized函数或synchronized block中进行调用。如果在non-synchronized函数或non-synchronized block中进行调用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。

(2).await(), signal()和signalAll()进行线程通信:

JDK5中引入了线程锁Lock来实现线程同步,使用Condition将对象的监视器分解成截然不同的对象,以便通过这些对象与任意线程锁Lock组合使用,使用signal和signalAll唤醒处于阻塞状态的线程,例子如下:

     
     
  1. import java.util.concurrent.*;
  2. import java.util.concurrent.lock.*;
  3. class Car{
  4. private Lock lock = new ReentrantLock();
  5. //获取线程锁的条件实例
  6. private Condition condition = lock.newCondition();
  7. private Boolean waxOn = false;
  8. public void waxed(){
  9. lock.lock();
  10. try{
  11. waxOn = true;
  12. //唤醒等待的线程
  13. condition.signalAll();
  14. }finally{
  15. lock.unlock();
  16. }
  17. }
  18. public void buffed(){
  19. lock.lock();
  20. try{
  21. waxOn = false;
  22. //唤醒等待的线程
  23. condition.signalAll();
  24. }finally{
  25. lock.unlock();
  26. }
  27. }
  28. public void waitForWaxing()throws InterruptedException{
  29. lock.lock();
  30. try{
  31. while(waxOn == false){
  32. //使当前线程处于等待状态
  33. condition.await();
  34. }finally{
  35. lock.unlock();
  36. }
  37. }
  38. }
  39. public void waitForBuffing()throws InterruptedException{
  40. lock.lock();
  41. try{
  42. while(waxOn == true){
  43. //使当前线程处于等待状态
  44. condition.await();
  45. }finally{
  46. lock.unlock();
  47. }
  48. }
  49. }
  50. }
  51. class WaxOn implements Runnable{
  52. private Car car;
  53. public WaxOn(Car c){
  54. car = c;
  55. }
  56. public void run(){
  57. try{
  58. while(!Thread.interrupted()){
  59. System.out.println(“Wax On!”);
  60. TimeUnit.SECONDS.sleep(1);
  61. car.waxed();
  62. car.waitForBuffing();
  63. }
  64. }catch(InterruptedException e){
  65. System.out.println(“Exiting via interrupt”);
  66. }
  67. System.out.println(“Ending Wax On task”);
  68. }
  69. }
  70. class WaxOff implements Runnable{
  71. private Car car;
  72. public WaxOff(Car c){
  73. car = c;
  74. }
  75. public void run(){
  76. try{
  77. while(!Thread.interrupted()){
  78. car.waitForWaxing();
  79. System.out.println(“Wax Off!”);
  80. TimeUnit.SECONDS.sleep(1);
  81. car.buffed();
  82. }
  83. }catch(InterruptedException e){
  84. System.out.println(“Exiting via interrupt”);
  85. }
  86. System.out.println(“Ending Wax Off task”);
  87. }
  88. }
  89. public class WaxOMatic{
  90. public static void main(String[] args)throws Exception{
  91. Car car = new Car();
  92. ExecutorService exec = Executors.newCachedThreadPool();
  93. exec.execute(new WaxOff(car));
  94. exec.execute(new WaxOn(car));
  95. TimeUnit.SECONDS.sleep(5);
  96. exec.shutdownNow();
  97. }
  98. }

输出结果:

Wax On! Wax Off! Wax On! Wax Off! Wax On!

Exiting via interrupt

Ending Wax On task

Exiting via interrupt

Ending Wax Off task

5.使用ScheduledThreadPoolExecutor实现定时任务(P722)

JDK1.5之前,使用Timer实现定时任务,JDK1.5之后引入了ScheduleThreadPoolExecutor实现多线程的定时任务。例子如下:

  
  
  1. import java.util.concurrent.*;
  2. import java.util.*;
  3. public class DemoSchedule{
  4. private final ScheduledExecutorService scheduler =
  5. Executors.newScheduledThreadPool(1);
  6. //创建并在给定延迟后时间启动的一次性操作
  7. public void schedule(Runnable event, long delay){
  8. scheduler.schedule(event, delay, TimeUnit.SECOND);
  9. }
  10. //创建并在给定延迟时间之后,每个给定时间周期性执行的操作
  11. public void repeat(Runnable event, long initialDelay, long period){
  12. scheduler.scheduleAtFixedRate(event, initialDelay, period, TimeUnit.SECOND);
  13. }
  14. }
  15. class OnetimeSchedule implement Runnable{
  16. public void run(){
  17. System.out.println(“One time schedule task”);
  18. }
  19. }
  20. class RepeatSchedule implement Runnable{
  21. public void run(){
  22. System.out.println(“Repeat schedule task”);
  23. }
  24. }
  25. class TestSchedule {
  26. public static void main(String[] args){
  27. DemoSchedule scheduler = new DemoSchedule();
  28. scheduler.schedule(new OnetimeSchedule(), 5);
  29. scheduler.schedule(new RepeatSchedule (), 10, 10);
  30. }
  31. }

输出结果:

5秒钟之后打印输出:One timeschedule task

10秒钟之后打印输出:Repeatschedule task

之后每隔10秒钟打印输出:Repeat schedule task

6.信号量Semaphore(P733)

一个正常的线程同步锁concurrent.locks或者内置的synchronized只允许同一时间一个任务访问一个资源,而计数信号量Semaphore运行多个任务在同一时间访问一个资源。Semaphore是一个计数信号量,维护了一个许可集,通常用于限制可以访问某些资源的线程数目。例子如下:

  
  
  1. import java.util.concurrent.*;
  2. import java.util.*;
  3. public class Pool<T> {
  4. //限制的线程数目
  5. private int size;
  6. //存放资源集合
  7. private List<T> items = new ArrayList<T>();
  8. //标记资源是否被使用
  9. private volatile boolean[] checkedOut;
  10. private Semaphore available;
  11. public Pool(Class<T> classObject, int size){
  12. this.size = size;
  13. checkedOut = new boolean[size];
  14. //创建信号量对象
  15. available = new Semaphore(size, true);
  16. for(int i = 0; i < size; ++i){
  17. try{
  18. //加载资源对象并存放到集合中
  19. items.add(classObject.newInstance());
  20. }catch(Exception e){
  21. throw new RuntimeException(e);
  22. }
  23. }
  24. }
  25. //访问资源
  26. public T checkout() throws InterruptedException{
  27. //从信号量中获取一个资源许可
  28. available.acquired();
  29. return getItem();
  30. }
  31. //释放资源
  32. public void checkIn(T x){
  33. if(releaseItem(x))
  34. //释放一个资源许可,将其返回给信号量
  35. available.release();
  36. }
  37. //获取资源
  38. private synchronized T getItem(){
  39. for(int i = 0; i < size; ++i){
  40. //资源没有被使用
  41. if(!checkedOut[i]){
  42. //标记资源被使用
  43. checkedOut[i] = true;
  44. return items.get(i);
  45. }
  46. }
  47. return null;
  48. }
  49. private synchronized boolean releaseItem(T item){
  50. int index = items.indexOf(item);
  51. //资源不在资源集合中
  52. if(index == -1) return false;
  53. //资源正在被使用
  54. if(checkedOut[index]){
  55. //将资源标记为不再使用
  56. checkedOut[index] = false;
  57. return true;
  58. }
  59. return false;
  60. }
  61. }

在访问资源前,每个线程必须首先从信号量获取许可,从而保证可以使用该资源。该线程结束后,将资源释放回资源池中并将许可返回给信号量,从而允许其他线程获取和使用该资源。

如果当前信号量中没有资源访问许可,则信号量会阻塞调用的线程直到获取一个资源许可,否则,线程将被中断。

注意:调用信号量的acquire时无法保持同步锁,因为这会阻止将该资源返回到资源池中,信号量封装所需要的同步,以限制对资源池的访问。

7.Exchanger线程同步交换器(P735)

Exchanger类,可以用来完成线程间的数据交换。 类java.util.concurrent.Exchanger提供了一个同步点,在这个同步点,一对线程可以交换数据。每个线程通过exchange()方法的入口提供数据给他的伙伴线程,并接收他的伙伴线程提供的数据,并返回。当两个线程通过Exchanger交换了对象,这个交换对于两个线程来说都是安全的。例子如下:

  
  
  1. //杯子类
  2. class Cup{
  3. private int capacity = 0;
  4. public Cup(int capacity){
  5. this.capacity = capacity;
  6. }
  7. public int getCapacity(){
  8. return capacity;
  9. }
  10. public void addWaterToCup(int i){
  11. capacity += I;
  12. capacity = capacity > 100 ? 100 : capacity;
  13. }
  14. public void drinkWaterFromCup(int i){
  15. capacity += I;
  16. capacity = capacity < 0 ? 0 : capacity;
  17. }
  18. public void isFull(){
  19. return capacity == 100 ? true : false;
  20. }
  21. public void isEmpty(){
  22. return capacity == 0 ? true : false;
  23. }
  24. }
  25. import java.util.concurrent.Exchanger;
  26. import java.util.*;
  27. public class TestExchanger{
  28. Cup emptyCup = new Cup(0);
  29. Cup fullCup = new Cup(100);
  30. Exchanger<Cup> exchanger = new Exchanger<Cup>();
  31. //服务员类
  32. class Waiter implements Runnable{
  33. private int addSpeed = 1;
  34. public Waiter(int addSpeed){
  35. this.addSpeed = addSpeed;
  36. }
  37. public void run(){
  38. while(emptyCup != null){
  39. try{
  40. //如果杯子已满,则与顾客交换,服务员有获得空杯子
  41. if(emptyCup.isFull()){
  42. emptyCup = exchanger.exchange(emptyCup);
  43. System.out.println(“Waiter: + emptyCup. getCapacity());
  44. }else{
  45. emptyCup.addWaterToCup(addSpeed);
  46. System.out.println(“Waiter add + addSpeed +
  47. and current capacity is:” + emptyCup. getCapacity());
  48. TimeUnit.SECONDS.sleep(new Random().nextInt(10));
  49. }
  50. }catch(InterruptedException e){
  51. e.printStackTrack();
  52. }
  53. }
  54. }
  55. }
  56. //顾客类
  57. class Customer implements Runnable{
  58. int drinkSpeed = 1;
  59. public Customer(int drinkSpeed){
  60. this.drinkSpeed = drinkSpeed;
  61. }
  62. public void run(){
  63. while(fullCup != null){
  64. try{
  65. //如果杯子已空,则与服务员进行交换,顾客获得装满水的杯子
  66. if(fullCup.isEmpty()){
  67. fullCup = exchanger.exchanger(fullCup);
  68. System.out.println(“Customer: + fullCup. getCapacity());
  69. }else{
  70. fullCup.drinkWaterFromCup(drinkSpeed);
  71. System.out.println(“Customer drink + drinkSpeed +
  72. and current capacity is:” + fullCup.getCapacity());
  73. TimeUnit.SECONDS.sleep(new Random().nextInt(10));
  74. }
  75. }catch(InterruptedException e){
  76. e.printStackTrack();
  77. }
  78. }
  79. }
  80. }
  81. public static void main(String[] args){
  82. new Thread(new Waiter(3)).start();
  83. new Thread(new Customer(6)).start();
  84. }
  85. }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值