《Java并发编程实战》读书笔记

java并发编程实战读书笔记之FutureTask 在《Android AsyncTask源代码浅析》一文中介绍了Android AsyncTask的原理,里面也初步介绍了FutureTask的使用方法。在创建Future的时候,根据其构造器来看需要传一个Callable对象,其构造器如下。 public FutureTask(Callable<V> callable) { this.callable = callable; this.state = NEW; // ensure visib 阅读详情

Subsections 
线程安全(Thread safety)
锁(lock)
共享对象
对象组合
基础构建模块
任务执行
取消和关闭
线程池的使用
性能与可伸缩性
并发程序的测试
显示锁
原子变量和非阻塞同步机制

一、线程安全(Thread safety)
无论何时,只要多于一个线程访问给定的状态变量。而且其中某个线程会写入该变量,此时必须使用同步来协助线程对该变量的访问。
线程安全是指多个线程在访问一个类时,如果不需要额外的同步,这个类的行为仍然是正确的。
线程安全的实例:
(1)、一个无状态的类是线程安全的。
     无状态类是指不包含任何域或也没有引用其它类的域。一次特定计算的瞬间状态,会唯一存在本地变量中。
(2)、原子操作是线程安全的。
     自增操作时一个离散操作的简写形式,获取当前值,加一,写回新值。这是一个读-改-写操作,不具备原子性。
(3)、竞争条件是不安全的。
     当计算的正确性依赖于运行时相关的时序或者多线程的交替时,会产生竞争条件。最常见的一种是检查再运行(check-then-act)。
eg:
  1. public class Instance() {  
  2.      private Instance in = null;  
  3.      public Instance getInstance() {  
  4.           if(in == null) {  
  5.               in= new Instance();  
  6.           }  
  7.           return in;  
  8.      }  
  9. }  
这个例子意图是想得到一个单例的对象,但是如果两个线程同时执行到getInstance()这个地方,此时此刻,in是否为null,这依赖于时序。这是无法预测到的。
解决检查再运行和读-改-写操作导致的线程不安全方法,就是必须保证操作的原子性。而java 内置的原子性机制-锁可以解决这些个问题。

二、锁(lock)
(1)、内部锁,java提供了强制原子性的内置锁机制:synchronized 块。
操作共享状态的复合操作必须是原子的,以避免竞争条件,比如读-改-写操作和检查再运行操作。复合操作会在完整的运行期占有锁,以确保其行为为原子的。

三、共享对象
要编写正确的程序,关键问题在于:在访问共享的可变状态时需要进行正确的管理。
(1)、内存可见性
我们不仅希望防止某个线程在访问某个状态,而另外一个线程同时在修改这个状态。同时希望确保一个线程修改了对象状态以后,其它线程能够看到发生的状态变化。
在没有同步的情况下,编译器,处理器对操作的执行顺序进行一些意想不到的调整。在缺少同步多线程程序中,要想对内存操作的执行顺序进行判断,几乎无法得到正确的结论。

(2)、失效数据
在缺少同步的程序中产生错误的结果的一种情况就是,失效数据。

(3)、加锁与可见性
锁可以确保某个线程以一种可以预测的方式来查看另一个线程的执行结果,比如A执行某个同步代码块时,线程B随后进入同一锁保护的代码块。在B执行锁保护的同步代码块时,可以看到线程A之前在同一代码块中所有的操作结果。
为什么在访问某个共享且可变的共享变量时,要求线程在同一个锁上同步,是为了确保某个线程写入该变量的值对于其它线程来说是可见的。否则,如果一个线程在未持有正确锁的情况下读取某个变量,那么读到的可能是一个失效值。

(4)、发布和逸出

(5)、线程封闭
当访问共享的可变数据时,通常需要同步。一种避免使用同步的方式就是不共享数据。如果仅在单线程内访问数据,就不需要同步。这种技术称为线程封闭。它是线程安全最简单方式之一。
eg:JDBC 的Connection对象,servlet请求大部分都是单线程同步方式处理,并且在Connection对象返回之前,连接池不会再将它分配给其它线程。
维持线程封闭更规范的方法是使用ThreadLocal
其设计的初衷是为了解决多线程编程中的资源共享问题。提起这个,大家一般会想到synchronized,synchronized采取的是“以时间换空间”的策略,本质上是对关键资源上锁,让大家排队操作。而ThreadLocal采取的是“以空间换时间”的思路,为每个使用该变量的线程提供独立的变量副本,在本线程内部,它相当于一个“全局变量”,可以保证本线程任何时间操纵的都是同一个对象。
原理:每个运行的线程都会有一个类型为ThreadLocal.ThreadLocalMap的map,这个map就是用来存储与这个线程绑定的变量,map的key就是ThreadLocal对象,value就是线程正在执行的任务中的某个变量的包装类Entry.
ThreadLocal有四个方法:
//返回此线程局部变量的当前线程副本中的值
  1. public T get() {  
  2.         Thread t = Thread.currentThread();  
  3.         ThreadLocalMap map = getMap(t);  
  4.         if (map != null) {  
  5.             ThreadLocalMap.Entry e = map.getEntry(this);  
  6.             if (e != null)  
  7.                 return (T)e.value;  
  8.         }  
  9.         return setInitialValue();  
  10.     }  

//如果是第一次调用,需要初始化。

  1. private T setInitialValue() {  
  2.         T value = initialValue();  
  3.         Thread t = Thread.currentThread();  
  4.         ThreadLocalMap map = getMap(t);  
  5.         if (map != null)  
  6.             map.set(this, value);  
  7.         else  
  8.             createMap(t, value);  
  9.         return value;  
  10.     }  
//将此线程局部变量的当前线程副本中的值设置为指定值

  1. public void set(T value) {  
  2.   
  3.         Thread t = Thread.currentThread();  
  4.         ThreadLocalMap map = getMap(t);  
  5.         if (map != null)  
  6.             map.set(this, value);  
  7.         else  
  8.             createMap(t, value);  
  9.     }  
//移除此线程局部变量的值。

  1. public void remove() {  
  2.          ThreadLocalMap m = getMap(Thread.currentThread());  
  3.          if (m != null)  
  4.              m.remove(this);  
  5.      }  
数据库连接管理类,转载:http://blog.youkuaiyun.com/ghsau/article/details/15732053

  1. public class ConnectionManager {  
  2.   
  3.      /** 线程内共享Connection,ThreadLocal通常是全局的,支持泛型 */  
  4.      private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();  
  5.        
  6.      public static Connection getCurrConnection() {  
  7.           // 获取当前线程内共享的Connection  
  8.           Connection conn = threadLocal.get();  
  9.           try {  
  10.                // 判断连接是否可用  
  11.                if(conn == null || conn.isClosed()) {  
  12.                     // 创建新的Connection赋值给conn(略)  
  13.                     // 保存Connection  
  14.                     threadLocal.set(conn);  
  15.                }  
  16.           } catch (SQLException e) {  
  17.                // 异常处理  
  18.           }  
  19.           return conn;  
  20.      }  
  21.        
  22.      /** 
  23.       * 关闭当前数据库连接 
  24.       */  
  25.      public static void close() {  
  26.           // 获取当前线程内共享的Connection  
  27.           Connection conn = threadLocal.get();  
  28.           try {  
  29.                // 判断是否已经关闭  
  30.                if(conn != null && !conn.isClosed()) {  
  31.                     // 关闭资源  
  32.                     conn.close();  
  33.                     // 移除Connection  
  34.                     threadLocal.remove();  
  35.                     conn = null;  
  36.                }  
  37.           } catch (SQLException e) {  
  38.                // 异常处理  
  39.           }  
  40.      }  
  41. }  
(6)、 不变性
满足同步需求的另外一种方法是使用不可变对象。不可变对象一定是线程安全的。
不可变对象的定义:对象创建出来后(通过构造方法)无论如何对此对象进行非暴力操作(不用反射),此对象的状态(实例域的值)都不会发生变化,那么此对象就是不可变的,相应类就是不可变类,跟是否用 final 修饰没关系。
在并发程序中使用和共享对象时,可以使用一下策略:
线程封闭。线程封闭的对象只能由一个线程拥有。
只读共享。在没有额外同步情况下,共享的只读对象可以由多个线程并发访问。
线程共享安全。线程安全的对象在其内部实现同步。因此多个线程可以通过对象的公有接口来访问而不需要进一步的同步。

四、对象组合
(1)、如何设计线程安全的类。
     设计安全类需要注意一下三要素:
     找出构造对象状态的所有变量。
     约束状态变量的不变性条件。
     建立对象状态的并发访问管理策略。
(2)、实例封闭
     如果某对象不是线程安全的,我们可以通过多种技术使其在多线程中能安全的使用。确保该对象只能由单个线程访问。
  1. public class PersonSet{  
  2.          private final Set<Person> mySet = new HashSet<Person>();  
  3.            
  4.          public sychronized void addPersion(Person p) {  
  5.               mySet.add(p)  
  6.          }  
  7.       
  8.          public sychronized boolean containsPerson(Person p) {  
  9.               return mySet.contains(p);  
  10.          }  
  11.     }  
虽然HashSet 并非线程安全的。但是mySet是私有的不会逸出。唯一能访问mySet的代码是addPerson(),和 containsPerson()。在执行上他们都要获的PersonSet 上的锁。PersonSet的状态完全又它的内置锁保护。所以
PersonSet是一个线程安全的类。
java 平台的类库有很多 实例封闭的例子。比如一些基本的容器并非线程安全的,如ArrayList,HashMap。类库提供的包装方法, Collections.synchronizedList( list)、 Collections.synchronizedMap( m)使得非线程安全的类可以在多线程中使用。
(3)、java 监视器模式
     把对象的所有可变状态都封装起来,并由对象自己的内部锁来保护。
  1. public class privateLock {  
  2.          private final Object myLock = new Object();  
  3.       
  4.          private int weight;  
  5.            
  6.          void someMethod() {  
  7.               synchronized(myLock) {  
  8.                    //访问weight  
  9.               }  
  10.          }  
  11.     }  
使用私有锁对象比使用对象的内置锁有许多优点。私有锁可以将锁封装起来,客户代码无法得到锁。但客户可以通过公有方法来访问锁。以便参与到同步策略中去。
监视器好比一做建筑,它有一个很特别的房间,房间里有一些数据,而且在同一时间只能被一个线程占据,进入这个建筑叫做"进入监视器",进入建筑中的那个特别的房间叫做"获得监视器",占据房间叫做"持有监视器",离开房间叫做"释放监视器",离开建筑叫做"退出监视器".

如上图所示,一个线程通过1号门进入Entry Set(入口区),如果在入口区没有线程等待,那么这个线程就会获取监视器成为监视器的owner,然后执行监视区域的代码。如果在入口区中有其它线程在 等待,那么新来的线程也会和这些线程一起等待。线程在持有监视器的过程中,有两个选择,一个是正常执行监视器区域的代码,释放监视器,通过5号门退出监视 器;还有可能等待某个条件的出现,于是它会通过3号门到Wait Set(等待区)休息,直到相应的条件满足后再通过4号门进入重新获取监视器再执行。

注意:当一个线程释放监视器时,在入口区和等待区的等待线程都会去竞争监视器,如果入口区的线程赢了,会从2号门进入;如果等待区的线程赢了会从4 号门进入。只有通过3号门才能进入等待区,在等待区中的线程只有通过4号门才能退出等待区,也就是说一个线程只有在持有监视器时才能执行wait操作,处于等待的线程只有再次获得监视器才能退出等待状态。

五、基础构建模块
(1)、同步容器类。包括Vector和Hashtable。同步的封装容器类由Collections.sychronizedXXX工厂方法创建。
     eg:synchronizedList,synchronizedMap(m)、synchronizedSet(s)
(2)、同步工具类。
     阻塞队列(BlockingQueue(LinkedBlockingQueue,ArrayBlockingQueue,PriorityBlockingQueue,SynchronousQueue))不仅可以保存对象的容器,而且还可以协调生产者和消费者之间的控制流。
     信号量(Semaphore):用来控制同时访问某个特定资源的操作数量。通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。Semaphore允许线程获取许可, 未获得许可的线程需要等待.这样防止了在同一时间有太多的线程执行。Semaphore实现的功能就类似厕所有5个坑,假如有10个人要上厕所,那么同时只能有多少个人去上厕所呢?同时只能有5个人能够占用,当5个人中 的任何一个人让开后,其中等待的另外5个人中又有一个人可以占用了。另外等待的5个人中可以是随机获得优先机会,也可以是按照先来后到的顺序获得机会,这取决于构造Semaphore对象时传入的参数选项。单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获得了“锁”,再由另一个线程释放“锁”,这可应用于死锁恢复的一些场合。
 eg:模拟30辆车去泊车,而车位有10个的场景. 当车位满时,出来一辆车,才能有一辆车进入停车. 转载http://mouselearnjava.iteye.com/blog/1921468
  1. package my.concurrent.semaphore;  
  2.   
  3. import java.util.concurrent.Semaphore;  
  4.   
  5. public class Car implements Runnable {  
  6.   
  7.      private final Semaphore parkingSlot;  
  8.   
  9.      private int carNo;  
  10.   
  11.      /** 
  12.      * @param parkingSlot 
  13.      * @param carName 
  14.      */  
  15.      public Car(Semaphore parkingSlot, int carNo) {  
  16.           this.parkingSlot = parkingSlot;  
  17.           this.carNo = carNo;  
  18.      }  
  19.   
  20.      public void run() {  
  21.   
  22.           try {  
  23.                parkingSlot.acquire();  
  24.                parking();  
  25.                sleep(300);  
  26.                parkingSlot.release();  
  27.                leaving();  
  28.   
  29.           } catch (InterruptedException e) {  
  30.                // TODO Auto-generated catch block  
  31.                e.printStackTrace();  
  32.           }  
  33.   
  34.      }  
  35.   
  36.      private void parking() {  
  37.           System.out.println(String.format("%d号车泊车", carNo));  
  38.      }  
  39.   
  40.      private void leaving() {  
  41.           System.out.println(String.format("%d号车离开车位", carNo));  
  42.      }  
  43.   
  44.      private static void sleep(long millis) {  
  45.           try {  
  46.                Thread.sleep(millis);  
  47.           } catch (InterruptedException e) {  
  48.                // TODO Auto-generated catch block  
  49.                e.printStackTrace();  
  50.   
  51.           }  
  52.      }  
  53.   
  54. }  
  55.   
  56. package my.concurrent.semaphore;  
  57.   
  58. import java.util.concurrent.ExecutorService;  
  59. import java.util.concurrent.Executors;  
  60. import java.util.concurrent.Semaphore;  
  61.   
  62. public class ParkingCars {  
  63.   
  64.      private static final int NUMBER_OF_CARS = 30;  
  65.   
  66.      private static final int NUMBER_OF_PARKING_SLOT = 10;  
  67.   
  68.      public static void main(String[] args) {  
  69.   
  70.           /* 
  71.           * 采用FIFO, 设置true 
  72.           */  
  73.           Semaphore parkingSlot = new Semaphore(NUMBER_OF_PARKING_SLOT, true);  
  74.   
  75.           ExecutorService service = Executors.newCachedThreadPool();  
  76.   
  77.           for (int carNo = 1; carNo <= NUMBER_OF_CARS; carNo++) {  
  78.                service.execute(new Car(parkingSlot, carNo));  
  79.           }  
  80.   
  81.           sleep(3000);  
  82.   
  83.           service.shutdown();  
  84.   
  85.           /* 
  86.           * 输出还有几个可以用的资源数 
  87.           */  
  88.           System.out.println(parkingSlot.availablePermits() + " 个停车位可以用!");  
  89.      }  
  90.   
  91.      private static void sleep(long millis) {  
  92.           try {  
  93.                Thread.sleep(millis);  
  94.           } catch (InterruptedException e) {  
  95.                // TODO Auto-generated catch block  
  96.                e.printStackTrace();  
  97.           }  
  98.      }  
  99.   
  100. }  

运行结果:
1号车泊车 
4号车泊车 
9号车泊车 
2号车泊车 
8号车泊车 
10号车泊车 
3号车泊车 
12号车泊车 
14号车泊车 
6号车泊车 
2号车离开车位 
4号车离开车位 
6号车离开车位 
1号车离开车位 
9号车离开车位 
3号车离开车位 
5号车泊车 
8号车离开车位 
10号车离开车位 
11号车泊车 
7号车泊车 
12号车离开车位 
13号车泊车 
14号车离开车位 
16号车泊车 
17号车泊车 
20号车泊车 
19号车泊车 
18号车泊车 
15号车泊车 
5号车离开车位 
20号车离开车位 
18号车离开车位 
22号车泊车 
11号车离开车位 
7号车离开车位 
13号车离开车位 
15号车离开车位 
21号车泊车 
26号车泊车 
23号车泊车 
28号车泊车 
25号车泊车 
16号车离开车位 
27号车泊车 
17号车离开车位 
30号车泊车 
24号车泊车 
29号车泊车 
19号车离开车位 
25号车离开车位 
24号车离开车位 
22号车离开车位 
26号车离开车位 
28号车离开车位 
30号车离开车位 
21号车离开车位 
23号车离开车位 
27号车离开车位 
29号车离开车位 
10 个停车位可以用!

六:任务执行
1、在线程中执行任务
首先需要找出清晰的任务边界,各个任务之间是相互独立的。任务并不依赖其它任务的状态、结果和边界。独立性有助于实现并发。在调度和负载均衡中实现更高的灵活性。在正常负载下,服务器应用程序应该表现良好的吞吐量和快速的响应性。
eg:主线程接受连接和处理请求之间不断交替运行。当服务器正在处理请求的时候,新到来的连接必须等待处理完以后才能在此调用 accept()。此种方式,服务器的资源利用率非常低。
  1. class singleThreadWebServer {  
  2.       ServerSocket socket = new ServerSocket(80) ;  
  3.       while (true) {  
  4.              Socket connection = socket.accept();  
  5.             handleRequest(connection);  
  6.       }  
  7. }  
改进:
  1. class MultiThreadWebServer {  
  2.       ServerSocket socket = new ServerSocket(80) ;  
  3.       while (true) {  
  4.              final Socket connection = socket.accept();  
  5.             Runnable task = new Runnable() {  
  6.                    public void run() {  
  7.                         handleRequest(connection);  
  8.                   }  
  9.              }  
  10.              new Thread(task).start();  
  11.       }  
  12. }  
结论:任务处理从主线程分离出来。使住线程能够在完全前面的请求之前可以接受新的请求,从而提高响应性。
      任务可以并行处理,从而同时服务多个请求,如果有多个处理器,或者某个任务某种原因被阻塞,程序的吞吐量提高。
2、Executor 框架
先给一张 java.util.concurrent 的结构

任务是一组逻辑工作单元,而线程则是使任务异步执行的逻辑单元。Executor为灵活且强大的异步任务执行框架提供了基础,还提供了对生命周期的支持,以及统计信息、应用管理机制和性能监视等机制。
Executor 基于生产者-消费者模式。提交任务相当是生产者,执行任务相当是消费者

a、执行策略:
任务在什么(What)线程中执行
任务以什么(What)顺序执行(FIFO/LIFO/优先级等)
同时有多少个(How Many)任务并发执行
允许有多少个(How Many)个任务进入执行队列
系统过载时选择放弃哪一个(Which)任务,如何(How)通知应用程序这个动作
任务执行的开始、结束应该做什么(What)处理

b、线程池:
线程池和工作者队列密切相关,工作者线程的任务:从工作队列中获取一个任务,执行任务,然后返回线程池并等待下一个任务。
Executors类里面提供了一些静态工厂,生成一些常用的线程池。
newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
newSingleThreadScheduledExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。

c、线程池Executor任务拒绝策略
java.util.concurrent.RejectedExecutionHandler描述的任务操作。
第一种方式直接丢弃(DiscardPolicy)
第二种丢弃最旧任务(DiscardOldestPolicy)
第三种直接抛出异常(AbortPolicy)
第四种任务将有调用者线程去执行(CallerRunsPolicy)

d、生命周期
java.util.concurrent.ExecutorService 接口对象来执行任务,该接口对象通过工具类java.util.concurrent.Executors的静态方法来创建。 Executors此包中所定义的 Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 类的工厂和实用方法。
ExecutorService扩展了Executor并添加了一些生命周期管理的方法。一个Executor的生命周期有三种状态,运行 ,关闭 ,终止。Executor创建时处于运行状态。当调用ExecutorService.shutdown()后,处于关闭状态,isShutdown()方法返回true。这时,不应该再想Executor中添加任务,所有已添加的任务执行完毕后,Executor处于终止状态,isTerminated()返回true。
shutdown():执行平缓的关闭过程,不再接受新的任务,同时等待已经提交的任务执行完成。
shutdownNow();执行粗暴的关闭过程,尝试取消所有运行中的任务,并且不再启动队列中尚未开始启动的任务。
awaitTermination: 这个方法有两个参数,一个是timeout即超时时间,另一个是unit即时间单位。这个方法会使线程等待timeout时长,当超过timeout时间后,会监测ExecutorService是否已经关闭,若关闭则返回true,否则返回false。一般情况下会和shutdown方法组合使用。
// 创建一个固定大小的线程池
  1. ExecutorService service = Executors. newFixedThreadPool(3);  
  2.          for ( int i = 0; i < 10; i++) {  
  3.              System. out.println( "创建线程" + i);  
  4.              Runnable run = new Runnable() {  
  5.                  @Override  
  6.                  public void run() {  
  7.                      System. out.println( "启动线程");  
  8.                  }  
  9.              };  
  10.              // 在未来某个时间执行给定的命令  
  11.              service.execute(run);  
  12.          }  
  13.          // 关闭启动线程  
  14.          service.shutdown();  
  15.          // 每隔1秒监测一次ExecutorService的关闭情况.  
  16.          service.awaitTermination(1, TimeUnit. SECONDS);  
  17.          System. out.println( "all thread complete");  
  18.          System. out.println(service.isTerminated());  
3、携带结果的任务callable 和Future

七:取消和关闭
要想使任务和线程安全、快速、可靠的停下来,并不是件容易的事情,java没有提供任何机制来安全终止线程。但他提供了中断(Interruption),一种协作机制,能使线程终止另外一个线程的当前工作。
任务取消
中断
中断策略
响应中断

八、线程池的使用

九、避免活跃性危险
我们使用加锁机制来确保线程安全,但如果过度使用,会导致顺序死锁(Lock-Ordering Deadlock)。我们使用线程池和信号量来限制对资源的限制。但这些被限制的行为可能导致资源死锁(Resource Deadlock)。
死锁:每个人拥有其他人需要的资源,同时等待其他人已经拥有的资源,并且每个人在获取所有需要的资源之前都不放弃已经拥有的资源。
饥饿:当线程无法访问到它所需要的资源而不能继续执行时,就会发生 饥饿。引发 饥饿的最常见资源就是CUP的时钟周期。
活锁:liveLock。改问题尽管不会阻塞线程,但也不能继续执行,因为线程将不断重复执行相同的操作,而且总会失败。

十、性能与可伸缩性
可伸缩性:当增加计算资源时,(CPU,内存,存储容量和I/O宽带),程序的吞吐量和处理能力响应的增加。
吞吐量:指一组并发任务中已完成任务所占的比例。
响应性:指请求从发出到完成所需要的时间。
引入线程存在的开销:
(1)、上下文切换。cpu在做线程切换的时候,需要保存当前线程执行的上下文,并且新调度进来的线程执行上下文设置为当前上下文。发生越多的上下文切换,增加了调度开销,并因此降低吞吐量。
(2)、内存同步。synchronized 发生锁竞争的地方带来的开销会影响其它线程的性能。
(3)、阻塞。当在锁上发生竞争时,竞争失败的线程会阻塞,JVM 通过循环,不断的尝试获取锁,直到成功。或者通过操作系统挂起阻塞的线程。如果时间短,采用等待方式,如果时间长才适合采用线程挂起的方式。
串行操作降低可伸缩性,并行切换上下文也会降低性能。在锁发生竞争时,会同时导致上面两种问题,因此,减少锁的竞争能够提高性能和收缩性。在并发程序中,对可伸缩性最主要的威胁就是独占方式的资源锁。
两个因素将影响锁上面发生竞争的可能性:锁的请求频率以及每次持有该锁的时间。如果两者的乘积很小,那么大多数获取锁操作都不会发生竞争。
三种方式可以降低锁的竞争程度:
(1)、降低锁的请求频率。
降低线程请求锁的频率,可以通过锁分解和锁分段等技术来实现。即减小锁的粒度。如果一个锁同时需要保护好几个状态变量,那么可以把这个锁分解成多个锁,并且每个锁只保护一个状态变量,从而提高可伸缩性,并最终降低每个锁的请求频率。但是使用的锁越多,发生死锁的风险也会越高。

(2)、减少锁的持有时间。
缩小锁的范围(快进快出),可以将一些与锁无关的代码移出同步代码块,尤其是开销较大的操作,以及可能被阻塞的操作,比如I/O 操作。

(3)、放弃使用独占锁,并发容器,读-写锁,不可变对象以及原子变量,

十一、并发程序的测试

十二、显示锁
访问共享对象可以使用的机制有synchronized,volatile,ReentrantLock。
有了synchronized 为啥JSR 166 小组花了这么多时间来开发 java.util.concurrent.lock 框架呢?答案很简单-同步是不错,但它并不完美。它有一些功能性的限制 —— 它无法中断一个正在等候获得锁的线程,也无法通过投票得到锁,如果不想等下去,也就没法得到锁。同步还要求锁的释放只能在与获得锁所在的堆栈帧相同的堆栈帧中进行,多数情况下,这没问题(而且与异常处理交互得很好),但是,确实存在一些非块结构的锁定更合适的情况。
Lock 和ReentrantLock
Lock接口中定义了一组抽象的加锁操作。Lock提供了一种无条件的、可轮询的、定时的以及可中断的锁后去操作。ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。在确实需要一些 synchronized 所没有的特性的时候,比如时间锁等候、可中断锁等候、无块结构锁、多个条件变量或者锁投票。
格式:
Lock lock = new ReentrantLock();
lock.lock();
try {
  // update object state
}
finally {
  lock.unlock();
}
必须在finally 中来释放Lock。

定时锁和轮询锁是为了避免死锁的发生。如果不能获取所需要的锁,可以使用定时的或者轮询的锁获取方式,从而使你重新获的控制权。
(1)、轮询锁( tryLock())
(2)、定时锁(tryLock(timeout, NANOSECONDS))。如果操作不能在指定的时间内给出结果,那么就会使程序提前结束
(3)、定时以及可中断的锁(lockInterruptibly)
(4)、读写锁(ReadWriteLock)。可以被多个读者访问或者被一个写者访问。
ReentrantLock 每次只能一个线程访问加锁的数据,从而达到维护数据完整性的目的。通过这种策略可以避免写/写和写/读冲突。但是也同时避免了读/读冲突。但是有些时候读操
作是可以被并发进行的。所以需要读写锁。
eg:实现一个简单的缓存。

十三、原子变量和非阻塞同步机制
(1)、 原子变量 
大多数现代处理器都包含对多处理的支持。当然这种支持包括多处理器可以共享外部设备和主内存,同时它通常还包括对指令系统的增加来支持多处理的特殊要求。特别是,几乎每个现代处理器都有通过可以检测或阻止其他处理器的并发访问的方式来更新共享变量的指令。
现在的处理器(包括 Intel 和 Sparc 处理器)使用的最通用的方法是实现名为“比较并交换(Compare And Swap)”或 CAS 的原语。
CAS 操作包含三个操作数—— 内存位置(V)、预期原值(A)和新值(B)。 通常将 CAS 用于同步的方式是从地址 V 读取值 A,执行多步计算来获得新值 B,然后使用 CAS 将 V 的值从 A 改为 B。如果 V 处的值尚未同时更改,则 CAS 操作成功。
类似于 CAS 的指令允许算法执行读-修改-写操作,而无需害怕其他线程同时修改变量,因为如果其他线程修改变量,那么 CAS 会检测它(并失败),算法可以对该操作重新计算。
eg:模拟CAS来实现一个计数器。
  1. public class CASCount implements Runnable {  
  2.       
  3.     private SimilatedCAS counter = new SimilatedCAS();    
  4.   
  5.     @Override  
  6.     public void run() {  
  7.          for (int i = 0; i < 10000; i++) {    
  8.                 System.out.println(this.increment());    
  9.          }    
  10.     }  
  11.       
  12.     public int increment() {  
  13.         int oldValue = counter.getValue();  
  14.         int newValue = oldValue + 1;  
  15.           
  16.         while (!counter.compareAndSwap(oldValue, newValue)) { //如果CAS失败,就去拿新值继续执行CAS  
  17.               oldValue = counter.getValue();    
  18.               newValue = oldValue + 1;    
  19.         }  
  20.           
  21.         return newValue;  
  22.     }  
  23.       
  24.     public static void main(String[] args) {    
  25.         Runnable run = new CASCount();    
  26.     
  27.         new Thread(run).start();    
  28.         new Thread(run).start();    
  29.         new Thread(run).start();    
  30.         new Thread(run).start();    
  31.         new Thread(run).start();    
  32.         new Thread(run).start();    
  33.         new Thread(run).start();    
  34.         new Thread(run).start();    
  35.         new Thread(run).start();    
  36.         new Thread(run).start();    
  37.     }    
  38.   
  39. }  
  40.   
  41. class SimilatedCAS {  
  42.     private int value;  
  43.       
  44.     public int getValue() {  
  45.         return value;  
  46.     }  
  47.       
  48.     // 这里只能用synchronized了,毕竟无法调用操作系统的CAS    
  49.     public synchronized boolean compareAndSwap(int expectedValue, int newValue) {    
  50.         if (value == expectedValue) {    
  51.             value = newValue;    
  52.             return true;    
  53.         }    
  54.             
  55.         return false;    
  56.     }    
  57. }  

JDK 5.0引入底层CAS支持,java.util.concurrenent.atomic.AtomicXXX,使用底层的JVM支持为数字和引用类型提供一种高效的CAS操作。
eg:AtomicInteger,AtomicIntegerArray,AtomicLong,AtomicLongArray
原子变量能够支持原子的有条件的,读-改-写操作。
eg:使用原子变量类 实现一个计数器。
  1. public class AtomicCounter implements Runnable{  
  2.       
  3.     //AtomicInteger采用了系统的CAS  
  4.     private AtomicInteger value = new AtomicInteger();    
  5.   
  6.     @Override  
  7.     public void run() {  
  8.         for (int i = 0; i < 10000; i++) {    
  9.             System.out.println(value.incrementAndGet());    
  10.         }    
  11.     }  
  12.       
  13.     public static void main(String[] args) {    
  14.         Runnable run = new AtomicCounter();    
  15.     
  16.         new Thread(run).start();    
  17.         new Thread(run).start();    
  18.         new Thread(run).start();    
  19.         new Thread(run).start();    
  20.         new Thread(run).start();    
  21.         new Thread(run).start();    
  22.         new Thread(run).start();    
  23.         new Thread(run).start();    
  24.         new Thread(run).start();    
  25.         new Thread(run).start();    
  26.     }    
  27.   
  28. }  

(1)、非阻塞算法提供比synchronized 机制更高的性能和可收缩性。可以使多个线程在竞争相同的数据时候不会发生阻塞。
基于锁的算法中可能会出现各种活跃性的障碍,比如I/O 阻塞,导致其它线程都无法进行下去,如果某种算法中,一个线程的失败或者挂起,不会影响其它线程的失败或者挂起,那么这种算法就是非阻塞算法。
(1)、非阻塞栈(链式存储结构)
push方法创建一个新的节点,改节点的next域指向当前栈顶。让后使用CAS把这个新节点放入栈顶。如果在开始插入的时候位于栈顶的节点没有变化则CAS成功,如果栈顶发生了变化(其它线程操作引起)那么CAS会失败。
eg:ConcurentStack.java
  1. public class ConcurrentStack {  
  2.       
  3.     class Node {  
  4.         public final int item;  
  5.         public Node next;  
  6.         public Node(int item) {  
  7.             this.item = item;  
  8.         }  
  9.     }  
  10.       
  11.     private AtomicReference<Node> top = new AtomicReference<ConcurrentStack.Node>();  
  12.       
  13.     //入栈  
  14.     public void push(int item) {  
  15.         Node newNode = new Node(item);  
  16.         Node oldHead;  
  17.         do {  
  18.             oldHead = top.get();  
  19.             newNode.next = oldHead;  
  20.         } while (!top.compareAndSet(oldHead, newNode));  
  21.     }  
  22.       
  23.     //出栈  
  24.     public int pop() {  
  25.         Node oldHead;  
  26.         Node newHead;  
  27.         do {  
  28.             oldHead = top.get();  
  29.             if(oldHead == null ) {  
  30.                 return -1;  
  31.             }  
  32.             newHead = oldHead.next;   
  33.             //Atomically sets the value to the given updated value if the current value {@code ==} the expected value.  
  34.         } while (!top.compareAndSet(oldHead, newHead));  
  35.         return oldHead.item;  
  36.     }  
  37.   
  38. } 
Java并发编程实战读书笔记5 ---Executor在android中的应用 最近业余时间在读《Java并发编程实战》这本书,现在已经读到了第二部分。该书上有这么一段话:如果你的项目中出现这样的代码 new Thread(new Runnable() { @Override public void run() { } }).start();的话,那么你可以考虑使用Executor了 这句话让我想到了我以前 在项目中对Executor做了一个小小的封装,可以当做一个小小的工具类使用。封装后可以将上面代码改成: //ExecutorType是一个枚举类,TASK_ON_PER 阅读详情

相关推荐

读书笔记-Java并发编程实战-基础篇

读书笔记-Java并发编程实战-基础篇

Java并发编程实战 读书笔记

线程的优点恰当使用线程,可以提升复杂程序的性能,降低开发和维护成本可以把一部分复杂代码转为直接、简洁易懂的代码更有效地利用空闲处理器资源,提高吞吐量用户界面有更好的响应性线程的缺点安全性(synchronized解决)活跃度:死锁、饥饿、活锁性能,上下文切换。

m0_49499183的博客 695

java并发编程实践笔记

java并发编程实践笔记java并发编程实践笔记java并发编程实践笔记java并发编程实践笔记

经典始终是经典,从理论到实践的Java并发编程实战笔记

并发编程式Java语言的重要特性之一,当然也是最难以掌握的内容。编写可靠的并发程序是一项不小的挑战。但是,作为程序员的我们,要变得更有价值,就需要啃一些硬骨头了。例如理解并发编程。

公众号:该用户快成仙了 301

海康视觉平台VisionMaster使用学习笔记(1)

双击图像源设置图像源的属性,像素格式分为Mono8和RDB24分别对应导入的图像是彩色还是黑白。打开相机驱动软件MVS,对连接的相机进行属性上的配置,先对相机的IP进行对应的设置。将自动触发的使能打开,保存配置好的相机属性,默认保存到用户集1中。点击MVS软件中的触发按钮,查看相机是否进行正常的触发拍照。打开VM软件添加一个图像源,选择图像源选项拖出图像源。回到VM软件中将选择相机选项选择到配置好参数的相机。取图方式也可以调整为相机取图,将图像源改为相机。双击相机进行连接后对相机的属性进行配置。

A567852的博客 1万+

MacOS上VScode配置PlatformIO Core的疑难杂症与提速实战

本文详细解析了在MacOS上使用VScode配置PlatformIO Core时遇到的安装卡死、新建项目缓慢等常见问题,并提供了官方脚本安装、网络优化、命令行预下载等实用解决方案。通过环境变量配置和镜像源加速,大幅提升开发效率,特别适合ESP32等嵌入式开发场景。

weixin_42567046的博客 220

Java并发编程实战读书笔记

第二章 线程安全性 并发特性 原子性 一个操作不会被线程调度机制打断,要么操作中的指令全部执行完毕,要么全部不执行,中间不会有任何的线程切换. 可见性: 一个线程对变量的值进行了修改,其他线程能够立即得知这个修改.(内存屏障) 有序性 有序性就是指程序按照代码的先后顺序执行.编译器为了优化性能,有时候会改变程序中语句的先后顺序.Java提供了volatile和synchronized两个关键字来保证线程之间的操作的有序性. 同步 指的就是线程间的协作,本质上和现实生活中的协作没区别,一个线程执行完一个任务后

Java坤坤的博客 276

有限时间 vs 固定时间 vs 预定时间滑模:稳定性分析与仿真验证方法对比(中)

有限时间滑模控制(FTSMC)和固定时间滑模控制(FixTSMC)是两种重要的控制策略,旨在实现系统状态的快速收敛。FTSMC通过设计滑模面和控制律,确保系统状态在有限时间内收敛到原点,但其收敛时间与初始状态相关。FixTSMC则进一步优化,使收敛时间与初始状态无关,适用于高动态环境,具有更强的鲁棒性和更平滑的控制信号。两种方法均通过李雅普诺夫函数证明其稳定性,并通过仿真验证其有效性。FixTSMC在工程应用中更为可靠,尤其适合安全关键系统。

Lyajpunov的博客 3223

Java并发编程实战笔记(一)

第一章 线程会共享进程范围内的资源,例如内存句柄和文件句柄,但每个线程都有各自的程序计数器(Program Counter)、栈以及局部变量。 通过使用线程,可以将复杂并异步的工作进一步分解为一组简单并且同步的工作流,每个工作流在一个单独的线程中运行,并在特定的同步位置进行交互。现有的一些框架可以实现上述目标,例如Servlet和RMI(Remote Mothod Invocation,远程方法调用

Ti2Yuan的博客 445

YOLOv8目标出入计数部署:讲解C#使用ONNX检测模型进行目标出入计数 可运行完整实例

代码、步骤、讲解完整,可一键运行,效果如下:车辆出入计数人员出入计数。

Limiiiing的博客 79

选型不再靠感觉:一文看懂 SAP Event Mesh 与 SAP Integration Suite, advanced event mesh 的能力边界

本文对比分析了SAP Event Mesh与SAP Integration Suite, advanced event mesh的差异。SAP Event Mesh适合轻量级场景,主要用于SAP系统间异步通信,支持AMQP、MQTT等协议,消息大小限制为1MB。而advanced event mesh面向企业级事件网络,提供跨云/本地部署、事件治理、监控等高级功能,支持30MB消息和更大存储容量。选择时需考虑业务规模、实时性要求、治理需求等因素:单一SAP生态内扩展可选Event Mesh;跨区域、多云环境

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 133

Java并发编程实战读书笔记——第二章线程安全性

645132

MADE_哒哒 487

【华为OD机试真题 新系统】959、 水库溃坝填补 | 机试真题+思路参考+代码解析(C++、Java、Py、C语言、JS)

【华为OD机试真题 2025C卷】959、 水库溃坝填补 | 机试真题+思路参考+代码解析(C++、Java、Py、C语言、JS) 华为od机试,华为od机试真题,华为od机试E卷,华为odE卷,华为od机试真题E卷,华为OD,华为OD机试,华为OD机试真题,华为od机考,华为od机试D卷,华为od机试真题D卷,华为od手撕代码,华为od,华为od机试C卷,华为od e卷,华为2025e卷,od机试,2025E卷,华为OD机试E卷,什么是华为od,华为od介绍,全面了解华为od,2025华为od,如何备战华

KJ.JK 2932

java并发编程实战读书笔记

1、day01 无状态对象一定是线程安全的; 竞态条件:在并发编程中,这种由于不恰当的执行时序而出现不正确的结果是一种非常重要的情况。 先检查后执行 读取-修改-写入 不可变对象,只能提供一种弱形式的原子性; 当有多个线程访问一个变量时,可以把变量设置为final,更新时,创建新的变量存储。 任何线程都可以在不需要额外同步的情况下安全地访问不可变对象,即使这些对象没有使用同步。 不可变对象:状态不可修改,所有域都是final,正确的构造过程。 //将可变对象Date转换为不可变对象 Coll

lovesman的博客 438

测绘程序设计——试题一:出租车轨迹数据计算

仅展示试题1的原文,如需了解其余内容,请自行购买图书李老师著作主页-http://ybli.users.sgg.whu.edu.cn/book/

chengzilhc的博客 890

【同等学力】2005-数学⭐✨

有r个正方形排成一行,今用红、黄、白、蓝四种颜色给这个r个正方形染色,每个正方形只能染一种颜色,如果要求染红、黄、白色的正方形分别至少出现一个,问有多少种不同的染法?3、(4分)设G是一个有n个结点m条边的连通简单平面图,若n≥3,则m≤3n-6.4、(4分)证明:任何连通简单平面图至少有一个结点的度数不超过5

super_mochi1 305

《Java 并发编程实战》--读书笔记

学习攻略 | 如何才能学好并发编程? 注: 极客时间《Java 并发编程实战》–读书笔记 GitHub:https://github.com/ByrsH/Reading-notes/blob/master/Concurrency/Java并发编程实战-极客时间.md 并发编程领域抽象成三个核心问题:分工,同步和互斥。 分工:把一个功能分给不同的线程共同实现 同步:当某个条件不满足时,线程需要等待,...

回归心灵的专栏 1945

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

java并发编程实践 读书笔记_《java并发编程实战》读书笔记——基础知识

JAVA并发编程学习笔记之CLH队列锁 NUMA与SMP SMP(Symmetric Multi-Processor),即对称多处理器结构,指服务器中多个CPU对称工作,每个CPU访问内存地址所需时间相同。其主要特征是共享,包含对CPU,内存,I/O等进行共享。SMP的优点是能够保证内存一致性,缺点1。Servlet是非线程安全的。避免在servlet类中定义未受锁保护的属性public clas...

weixin_42461035的博客 203

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

Java并发编程实战 -- 读书笔记

01 02

Syntactic Sugar 363

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

JAVA并发编程实战读书笔记

今天第一天读这个书吧,感觉翻译确实有些问题,有些地方读起来并不是很通顺,感觉这本书对于我这种并发编程菜鸡不是很实用,要更深一点。 原子性:如count++这种操作其实是多步的操作,当并发执行它时一定注意加锁的操作 原子变量类:当我们遇到这类操作时,java的并发包中提供了一个原子变量类,用于实现数值和对象引用的原子状态转换。如通过AutomicLon个替代long类型的计数器,能过确保所有对计...

Sunmeok的博客 415
上一篇: java面试题String,StringBuilder,StringBuffer
下一篇: 《纳什均衡与博弈论》
Footloose_
博客等级 码龄10年 254粉丝 11原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值