并发编程实战 - 基础部分[对象的组合、基础构建模块]

《Java并发编程实战》【第一部分 基础知识】 文章目录第1章 简介1.1 并发简史1.2 线程的优势1.2.1 发挥多处理器的强大能力1.2.2 建模的简单性1.2.3 异步事件的简化处理1.2.4 响应更灵敏的用户界面1.3 线程带来的风险1.3.1 安全性问题1.3.2 活跃性问题1.3.3 性能问题1.4 线程无处不在第一部分 基础知识第2章 线程安全性第3章 对象的共享第4章 对象的组合第5章 基础构建模块第二部分 结构化并发应用程序第6章 任务执行第7章 取消与关闭第8章 线程池的使用第9章 图形用户界面应用程序第三部分 活跃性、性能与测试第 阅读详情

我们并不希望每一次内存访问都进行分析以确保程序是线程安全的,而是希望将一些现有的线程安全组件组合为更大规模的组件或程序。

1、对象的组合

1、设计线程安全的类,包含三要素:

找出构成对象状态的所有变量;

找出约束状态变量的不变性条件;

建立对象状态的并发访问管理策略;

所谓的不可变条件可以理解为保持状态变量之间关系的一种约束,比如a<b,那么在并发中我们也必须保证该不变性条件。

要确保类的线程安全性,就需要确保它的不变性条件不会在并发访问中被破坏。

2、仅当一个变量参与到包含其他状态变量的不变性条件时,才可以声明为volatile类型。

3、如果某个类含有复合操作(比如if..),那么仅靠现有的线程安全组件并不足以实现线程安全性。在这种情况下,这个类必须提供自己的加锁机制以保证这些复合操作都是原子操作。

举例:

public class NumberRange{
    //不变性条件:lower<upper
    private final AtomicInteger lower = new AtomicInteger(0);
    private final AtomicInteger upper = new AtomicInteger(0);
    
    public void setLower(int i){
        //注意:不安全的“先检查后执行”
        if(i>upper.get())
            throw new IllegalArgumentException("can not set to"+i+"> upper");
        lower.set(i);
    }
    public void setUpper(int i){
        //注意:不安全的“先检查后执行”
        if(i<lower.get())
            throw new IllegalArgumentException("can not set to"+i+"<  lower");
        upper.set(i);
    }
}

如果一个线程调用setLower(5),而另一个线程调用setUpper(4),那么在一些错误的执行时序中,这两个调用都会被通过,并且都能设置成功,得到的结果将是一个无效的状态。

虽然AtomicInteger是线程安全的,但经过组合得到的类却不是。因此,可以通过加锁机制来维护不变性条件以确保线程安全性。

4、客户端加锁机制

public class ListHelper<E>{
        public List<E> list = Collections.synchronizedList(new ArrayList<E>());
        ......
        public synchronized boolean putIfAbsent(E x){
            boolean absent = !list.contains(x);
            if (absent){
                list.add(x);
            }
            return absent;
        }
    }
上述示例不是线程安全的,为什么?原因是在错误的锁上进行了同步。无论List使用哪一个锁来保护它的状态,可以确定的是,这个锁并不是ListHelper上的锁。ListHelper只是带来了同步的假象,尽管所有的链表都被声明为synchronized,但却使用了不同的锁,这意味着putIfAbsent相对于List的其他操作并不是原子的,因此也就无法确保当putIfAbsent执行时,另一个线程不会修改链表。

解决方案:客户端加锁,即对于使用某个对象X的客户端代码,使用X本身用于保护其状态的锁来保护这段客户端代码。

 public class ListHelper<E>{
        public List<E> list = Collections.synchronizedList(new ArrayList<E>());
        ......
        public  boolean putIfAbsent(E x){
            synchronized (list){
                boolean absent = !list.contains(x);
                if (absent){
                    list.add(x);
                }
                return absent;
            }
        }
    }

2、基础构建模块

2.1、同步容器类

同步容器类包括Vector和HashTable,二者是早期JDK的一部分,此外还包括在JDK1.2中增加的一些功能相似的类,这些同步的封装器类都是由Collections.SynchronizedXxxx等工厂方法创建的。这些类实现线程安全的方式是:将它们的状态封装起来,对每一个公有方法都进行同步,使得每次只有一个线程能访问容器的状态。

同步容器类都是线程安全的,但在某些情况下可能需要额外的客户端加锁来保护复合操作。容器上常见的复合操作包括:迭代(反复访问元素,直到遍历完容器中所有的元素)、跳转(根据指定顺序找到当前元素的下一个元素)以及条件运算(例如“若没有则添加”)。在同步容器类中,这些复合操作在没有客户端加锁的情况下仍然是线程安全的,但当其他线程并发地修改容器时,它们可能会出现意料之外的行为。

public static Object getLast(Vector vector){
		int lastIndex = vector.size()-1;
	return vector.get(lastIndex);
	}	
	
	public static Object deleteLast(Vector vector){
		int lastIndex = vector.size()-1;
	return vector.remove(lastIndex);
	}
虽然Vector是线程安全的,但是在多线程的环境中,上述操作并非线程安全的,相反会抛出异常:ArrayIndexOutOfBoundException异常。因此需要额外的客户端加锁来保护复合操作:

public static Object getLast(Vector vector){
		synchronized (vector) {
			int lastIndex = vector.size()-1;
			return vector.get(lastIndex);
		}
	}	
	
	public static Object deleteLast(Vector vector){
		synchronized (vector) {
			int lastIndex = vector.size()-1;
			return vector.remove(lastIndex);
		}
	}

2.2、迭代器与ConcurrentModificationException

许多“现代”的容器类也并没有消除复合操作中的问题。无论是直接迭代还是在Java5中引入的for-each循环语法中,对容器类进行迭代的标准方式都是使用Iterator。然而如果有其他线程并发的修改容器,那么即使是使用迭代器也无法避免在迭代期间对容器加锁。在设计同步容器的迭代类的时候并没有考虑到并发修改的问题,并且他们表现的行为是“及时失败”的。这意味着,当它们发现容器在迭代过程中被修改时,就会抛出一个ConcurrentModificationException异常。

这种“及时失败”的迭代器并不是一种完备的处理机制,而只是“善意地”提示并发错误,因此只能作为并发问题的预警提示器。与Vector一样,要想避免出现ConcurrentModificationException,就必须在迭代过程中持有容器的锁。

List<Widget> widgetList = Collections.synchronizedList(new ArrayList<>());
	......
	for(Widget w : widgetList){
		doSomething(w);
	}
有的开发人员可能并不想在容器上进行加锁,那么一种替代方法就是“克隆”容器,并在副本上迭代,由于副本被封闭在线程内,因此其他线程不会对其进行修改操作,这样就 避免了抛出ConcurrentModificationException异常。注意:在克隆容器时,有可能存在显著的性能开销。这取决于容器的大小,容器元素上的执行工作等。另外一个需要了解的是在单线程环境仍然可能抛出ConcurrentModificationException异常,当对象直接从容器中删除,而不是通过Iterator.remove删除时,就会抛出这个异常。

2.3、并发容器

Java5.0提供了多种并发容器类来改进同步容器类的性能。同步容器类将所有对容器状态的访问串行化,以实现它们的线程安全性,这种方法的代价是严重降低并发性,当多个线程竞争容器的锁时,吞吐量将严重减少。

另一方面,并发容器是针对多个线程并发访问设计的,在Java5.0中增加了ConcurrentHashMap,用来替换同步且基于散列的Map,以及CopyOnWriteArrayList,用于在遍历操作为主要操作情况下代替同步的List。在新的Concurrent接口中,新增加了对一些复合操作的支持,例如“若没有则添加”、替换以及有条件删除等【通过并发容器来代替同步容器,可以极大的提高伸缩性并降低风险】

Java5.0还提供了另外两个新的容器:Queue和BlockingQueue。Queue用于临时保存一组待处理的元素。它提供了几种实现,包括ConcurrentLinkedQueue,这是一个传统的先进先出队列,以及PriorityQueue,这是一个(非并发的)优先队列。Queue上的操作不会阻塞,如果队列为空,那么获取元素的操作将返回空值。虽然可以用List模拟Queue的行为 -- 事实上,正是通过LinkedList来实现Queue的,但还需要一个Queue的类,因为它能去掉List的随机访问,从而实现高并发。

BlockingQueue扩展了Queue,增加了可阻塞的插入和获取等操作。如果队列为空,获取元素的操作将一直阻塞,直到队列中出现一个可用的元素。如果队列已满(对于有界队列来说),那么插入元素的操作将一直阻塞,直到队列中出现可用的空间。在“生产者-消费者”这种设计模式中,阻塞队列是非常有用的。

正如ConcurrentHashMap中用来代替基于散列的同步Map,Java6也引入了ConcurrentSkipLstMap和ConcurrentSkipListSet,分别作为同步的SortedMap和SortedSet的并发替代品(例如用synchronizedMap包装的TreeMap或TreeSet)。

2.3.1、ConcurrentHashMap

同步容器类在执行每个操作期间都持有一个锁。在一些操作中,比如HashMap,在某些情况下,某些糟糕的散列函数可能会把散列表变为线性表,当遍历很长的链表并且在某些或者全部元素上调用equals方法时,会花费很长的时间,而其他线程在这段时间内都不能访问这个容器。

与HashMap一样,ConcurrentHashMap也是一个基于散列的Map,但它使用了一种完全不同的加锁策略来提供更高的并发性和可伸缩性。ConcurrentHashMap并不是将每一个方法都在同一个锁上同步并使得每次只能有一个线程访问容器,而是使用了一种粒度更细的加锁机制来实现更大程度的共享,这种机制被称为分段锁。在这种机制中,任意数量的读取线程可以并发访问Map,执行读取操作的线程和执行写入操作的线程可以并发地访问Map,并且一定数量的写入线程可以并发的修改Map。ConcurrentHashMap带来的结果是,在并发访问环境下将实现更高的吞吐量,而在单线程环境中只损失非常小的性能。

ConcurrentHashMap和其他并发容器类一起增强了同步容器类:它们提供的迭代器不会抛出ConcurrentModificationException,因此不需要再迭代过程中对容器加锁。。ConcurrentHashMap返回的迭代器具有弱一致性,而并非“及时失败”。弱一致性的迭代器可以容忍并发的修改,当创建迭代器时会遍历已有的元素,并可以(但不保证)在迭代器被构造后将修改操作反馈给容器。

在ConcurrentHashMap中没有实现对Map加锁以提供独占式访问。在HashTable和SynchronizedMap中,获取Map的锁能防止其他线程访问这个Map。与HashTable和SynchronizedMap相比,ConcurrentHashMap有着更多的优势和更少的劣势。因此,在大多数情况下,应当用ConcurrentHashMap来替代同步Map能进一步提高代码的可伸缩性。只有当应用程序需要加锁Map以进行独占式访问时,才应该放弃使用COncurrentHashMap。

public interface ConcurrentMap<K, V> extends Map<K, V> {
        /**
         * 仅当K没有相应的映射值时才插入(如果有则添加)
         */
        V putIfAbsent(K key, V value);

        /**
         * 仅当key被映射到value时才移除(若相等则移除)
         */
        boolean remove(Object key, Object value);

        /**
         * 仅当k被映射到oldValue时才替换为newValue(若相等则替换)
         */
        boolean replace(K key, V oldValue, V newValue);

        /**
         * 仅当k被映射到某个值时才替换为value(若相等则替换)
         */
        V replace(K key, V value);
    }
对于一些复合式操作,例如“若相等则移除”、“若没有则添加”、“若相等则替换”等,ConcurrentHashMap都已经实现了原子操作。如果你需要在现有的同步Map中添加这样的功能,那么很可能意味着你要考虑使用ConcurrentHashMap了。

2.3.2、CopyOnWriteArrayList

CopyOnWriteArrayList用于替代同步List,在某些情况下它能提供更好的并发性能,并且在迭代期间不需要对容器进行加锁或复制(类似的,CopyOnWriteArraySet的作用是替换同步Set)。

“写入时复制”容器的线程安全性在于,只要正确的发布一个事实不可变的对象,那么在访问该对象时就不再需要进一步的同步。在每次修改时,都会创建并重新发布一个新的容器副本,从而实现可变性。“写入时复制”容器的迭代器保留一个指向底层基础数组的引用,这个数组当前位于迭代器的起始位置,由于它不会被修改,因此在对其进行同步时只需保留确保数组内容的可见性。因此,多个线程可以同时对这个容器进行迭代,而不会彼此干扰或者与修改容器的线程相互干扰。“写入时复制”容器返回的迭代器不会抛出ConcurrentModificationException,并且返回的元素与迭代器创建时的元素完全一致,而不必考虑之后修改操作所带来的影响。

显然,每当修改容器的时候都会复制底层数组,这需要一定的开销。特别是当容器的规模较大时,仅当迭代器操作远远多于修改操作时,才应该使用“写入时复制”容器。

2.4、阻塞队列

阻塞队列提供了可阻塞的take和put方法,以及支持定时的offer和poll方法。BlockingQueue简化了生产者-消费者设计的实现过程,它支持任意数量的生产者和消费者。一种常见的生产者-消费者模式就是线程池与工作队列的组合,在Executor任务执行框架中就体现了这种模式。

BlockingQueue子类:LinkedBlockingQueue、ArrayBlockingQueue、SynchronousQueue。前两者不用说了,很简单。说下第三个SynchronousQueue实际上它不是一个真正的队列,因为它不会为元素维护存储空间。与其他队列不同的是,它维护一组线程,这些线程在等待着把元素移入或移除队列。比如以洗盘子的比喻为例,那么就相当于没有盘架,而是将洗好的盘子直接放入到下一个空闲的烘干机中。这种队列的实现方式看似奇怪,但由于可以直接交付工作,从而降低了将数据从生产者移动到消费者的延迟。(在传统的队列中,在一个工作单元交付之前,必须通过串行方式首先完成入列或者出列等操作)。因为SynchronousQueue没有存储功能,因此put和take会一直阻塞,直到有一个线程已经准备好参与到交付过程中。

在java.uil.concurrent中实现的各种阻塞队列都包含足够的内部同步机制,从而安全地将对象从生产者线程发布到消费者线程。

2.4.1、双端队列与工作密取

Java6增加了两种容器类型:Deque和BlockingDeque,它们分别对Queue和BlockingQueue进行了扩展。Deque是一个双端队列,实现了在队列头和队列尾的高效插入和删除操作。具体实现包括ArrayDeque和LinkedBlockingDeque。

正如阻塞队列适用于生产者-消费者模式,双端队列同样适用于另外一种模式,即工作密取模式。在生产者-消费者设计中,所有消费者有一个共享的工作队列,而在工作密取设计中,每个消费者都有各自的双端队列。如果一个消费者完成了自己双端队列中的全部工作,那么它可以从其他消费者双端队列的尾部秘密获取工作。相对于生产者-消费者模式,工作密取具有更高的可伸缩性,这是因为工作者线程不会在单个共享的任务队列中发生竞争。当双端队列为空时,它会在另一个线程的队列尾部查找新的任务,从而确保每个线程都保持忙碌状态。

2.5、阻塞方法与中断方法

线程可能发生阻塞或暂停执行,原因有多种:等待I/O操作结束,等待获取一个锁等。BlockingQueue的put和take等方法会抛出受检查异常,这与类库中其他一些方法的做法相同,例如Thread.sleep。当某方法抛出Interrupted-Exception时,表示该方法是一个阻塞方法。如果这个方法被中断。那么它将努力提前结束阻塞状态。

Thread提供了intercept方法,用于中断线程或者查询线程是否已经被中断。每个线程都有一个布尔类型的属性,表示线程的中断状态,当中断线程时将设置这个状态。中断是一种协作机制。一个线程不能强制其他线程停止正在执行的操作而去执行其他的操作。当线程A中断线程B,A仅仅是要求B在执行到某一个可以暂停的地方停止正在执行的操作-前提是如果线程B愿意停止下来。最常用的中断的情况就是取消某个操作。方法对中断请求点的响应度越高,就越容易及时取消那些执行时间很长的操作。

2.6、同步工具类

在容器类中,阻塞队列是一种独特的类:它们不仅能作为保存对象的容器,还能协调生产者和消费者等线程之间的控制流,因为take和put等方法将阻塞,直到队列达到期望的状态(队列既非满,也非空)。

同步工具类除了阻塞队列,还包括信号量(Semaphore)、栅栏(Barrier)以及闭锁(Latch,比如CountDownLatch、FutureTask)。在平台类库中还包含其他一些同步工具的类,如果这些类还无法满足需要,那么就需要创建自己的同步工具类。

2.6.1、闭锁

闭锁是一种同步工具类,可以延迟线程的进度直到其到达终止状态。闭锁的作用相当于一扇门:在闭锁到达结束状态前,这扇门一直是关闭的,并且没有任何线程能通过,当到达结束状态时,这扇门会打开并允许所有的线程通过。当闭锁到达结束状态之后,将不会再改变状态,因此这扇门将永远保持打开状态。

闭锁可以用来确定某些活动直到其他活动都完成才继续执行。

CountDownLatch是一种灵活的闭锁实现,闭锁状态包括一个计数器,该计数器被初始化为一个正数,表示需要等待的事件数量。countDown方法递减计数器,表示一个事件已经发生了,而await方法等待计数器达到零,这表示所有需要等待的事件都已经发生。如果计数器的值非零,那么await会一直阻塞到计数器为零,或者等待中的线程中断,或者等待超时。

public void timeTask(int nThreads,final Runnable task){
		final CountDownLatch start = new CountDownLatch(1);
		final CountDownLatch end = new CountDownLatch(nThreads);
		for(int i=0;i<nThreads;i++){
			Thread t = new Thread(new Runnable() {
				
				@Override
				public void run() {
					try{
						try{
							start.await();
		                    task.run();
	                  }finally{
		                end.countDown();
	                     }
					}catch(Exception e){
						e.printStackTrace();
					}
				}
			});
			t.start();
		}
		start.countDown();//启动门同时释放所有工作线程
		try {
			end.await();//结束门,等待最后一个线程执行完成
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
FutureTask(FutureTask实现了Future语义,表示一种抽象的可生成结果的计算)也可以作为闭锁。
FutureTask表示的计算是通过Callable实现的,相当于一种可生成结果的Runnable,并且可处于以下三种状态:等待运行、正在运行、运行完成。“执行完成”表示计算的所有可能结束方式,包括正常结束、由于取消或者异常而结束。当FutureTask进入完成状态之后,将永远的停止在这个状态。

Future.get()的行为取决于任务的状态。如果任务已经完成,那么get将立即返回,否则get将阻塞直到任务进入完成状态,然后返回结果或抛出异常。FutureTask将计算结果从执行计算的线程传递到获取这个结果的线程,而FutureTask的规范确保了这种传递过程能实现结果的安全发布。

FutureTask在Executor框架中表示异步任务,此外还可以用来表示一些时间较长的计算,这些计算可以在使用计算结果之前启动。

 private final FutureTask<ProductInfo> future = new FutureTask<ProductInfo>(new Callable<ProductInfo>() {
        @Override
        public ProductInfo call() throws Exception {
            return null;
        }
    });
    
    private final Thread thread = new Thread(future);
    
    public void start(){
        thread.start();
    }
    
    public ProductInfo get(){
        try{
            return future.get();
        }catch (ExecutionException e){
            e.printStackTrace();
        }
    }

2.6.2、信号量

计数信号量(Counting Semaphore)用来控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量。计数信号量还可以用来实现某种资源池,或者对容器施加边界。

SemapPhore中管理了一组虚拟的许可,许可的初始数量可以通过构造函数来指定。在执行操作时可以首先获取许可(只要还有剩余的许可),并在使用以后释放许可。如果没有许可,那么acquire将阻塞直到有许可(或者被中断或者操作超时)。release方法将返回一个许可给信号量。计算信号量的一种简化形式是二值信号量,即初始值为1的Semaphore。二值信号量可以用作互斥体,并具备不可重入的加锁语义:谁拥有了这个唯一的许可,谁就拥有了互斥锁。

Semaphore可以用于实现资源池,例如数据库连接池。同样,你也可以使用Semaphore将任何一种容器变成有界阻塞容器。

public  class BoundedHashSet<T>{
        private final Set<T> set;
        private final Semaphore sem;
        
        public BoundedHashSet(int bound){
            this.set = Collections.synchronizedSet(new HashSet<T>());
            sem = new Semaphore(bound);
        }
        public boolean add(T o) throws InterruptedException{
            sem.acquire();
            boolean wasAdded = false;
            try{
                wasAdded = set.add(o);
                return wasAdded;
            }finally {
                if (!wasAdded){
                    sem.release();
                }
            }
        }
        
        public boolean remove(Object o){
            boolean wasRemoved = set.remove(o);
            if (wasRemoved)
                sem.release();
            return wasRemoved;
        }
        
    }

2.6.3、栅栏

闭锁是一次性对象,一旦进入终止状态,就不能被重置。

栅栏类似于闭锁,它能阻塞一组线程直到某个事件发生。栅栏和闭锁的关键区别在于,所有线程必须同时到达栅栏,才能继续执行。闭锁用于等待事件,而栅栏用于等待其他线程。

CyclicBarrier可以使一定数量的参与方反复地在栅栏位置汇集,它在并行迭代算法中非常有用:这种算法通常将一个问题拆分成一系列相互独立的子问题。当线程到达栅栏位置时,将调用await方法,这个方法将阻塞直到所有的线程都到达栅栏位置。如果所有的线程都到达栅栏位置,那么栅栏将打开,此时将释放所有的线程,而栅栏将被重置以便再次使用。如果对await的调用超时,或者await阻塞的线程被中断,那么栅栏就被认为是打破的。所有阻塞的await调用都将终止并抛出BrokenBarrierException。如果成功地通过栅栏,那么await将为每个线程返回一个唯一的达到索引号,我们可以利用这些索引来“选举”产生一个领导线程,并在下一次迭代中有该领导线程执行一些特殊的操作。

CyclicBarrier还可以使你将一个栅栏操作传递给构造函数,这是一个Runnable,当成功通过栅栏时会执行它,但在阻塞线程被释放前是不能执行的。

public class Beer {  
  
    public static void main(String[] args) {  
        final int count = 5;  
        final CyclicBarrier barrier = new CyclicBarrier(count, new Runnable() {  
            @Override  
            public void run() {  
                System.out.println("drink beer!");  
            }  
        });  
  
        // they do not have to start at the same time...  
        for (int i = 0; i < count; i++) {  
            new Thread(new Worker(i, barrier)).start();  
        }  
    }  
  
}  
  
class Worker implements Runnable {  
    final int id;  
    final CyclicBarrier barrier;  
  
    public Worker(final int id, final CyclicBarrier barrier) {  
        this.id = id;  
        this.barrier = barrier;  
    }  
  
    @Override  
    public void run() {  
        try {  
            this.barrier.await();  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        } catch (BrokenBarrierException e) {  
            e.printStackTrace();  
        }  
    }  
}  
再次说明CountDownLatch和CyclicBarrier区别:CountDownLatch是等待事件的发生(countDown->0),而CyclicBarrier则是等待所有的线程到达(即count=5,5个线程都到达了[调用了barrier.await],则执行我们传递给CyclicBarrier的Runnable)。

另一种形式的栅栏时ExChanger,它是一种两方的栅栏,在栅栏位置上交换数据。当两方执行不对称的操作时,ExChanger非常有用。例如当一个线程向缓冲区写数据,而另一个线程则向缓冲区读数据。这些线程可以使用ExChanger来汇合,并将满的缓冲区与空的缓冲区交换。当两个线程通过Exchanger交换对象时,这种交换就把这两个对象安全发布给另一方。

Java并发编程实战_盖兹 文章目录第一部分 基础知识第1章 简介1.1 并发简史1.2 线程的优势1.3 线程带来的风险1.4 线程无处不在(框架线程或类线程并发注意点)第2章 线程安全性2.1 什么是线程安全性2.2 原子性2.3 加锁机制内置锁:Synchronized关键字可重入锁:获取锁的操作粒度是“线程”而不是调用用锁来保护状态活跃性与性能性第3章 对象的共享3.1 可见性3.2 发布与逸出3.3 线程封闭3.4 对象的不变性3.5 安全发布第4章 对象的组合4.1 设计线程安全的类4.2 实例封闭4.3 线程安全性委托4 阅读详情

相关推荐

掌握Scala并发编程的艺术:从基础到高级框架

本书详细介绍了Scala中的并发编程,涵盖从传统并发工具到现代并发框架的各个方面。作者不仅解释了并发编程的必要性和基础理论,还深入讲解了如何在Scala中使用高级并发框架,例如Futures和Promises,数据并行集合,Reactive Extensions,软件事务内存,以及Akka actor模型。本书旨在帮助读者理解并发编程的基础和高级概念,并通过实际代码展示这些概念的应用,最终成为现代并发编程的专家。

weixin_28850145的博客 973

Java并发编程实战总结

每一个想学习Java多线程的人,手里至少有这本书或者至少要看这本书,2012年在看这本书的时候,当时正开发支付平台的后台应用,正好给了我大量的实践机会。强烈建议大家多看几遍。 代码中比较容易出现bug的场景: 不一致的同步,直接调用Thread.run,未被释放的锁,空的同步块,双重检查加锁,在构造函数中启动一个线程,notify或notifyAll通知错误,Object.wait和Condition.await未在同步方法或块中调用,把Lock当锁用,调用Condition.wait方法,在休眠.

mrdragon的博客 353

有源滤波电路设计

目录一、低通滤波器1、同相输入低通滤波器(1)一阶低通滤波电路(2)二阶低通滤波电路(压控电压源;Sallen-Key)2、反相输入低通滤波器(1)一阶低通滤波电路(2)二阶低通滤波电路(无限增益多路反馈)二、高通滤波器1、同相输入高通滤波器(1)一阶高通滤波电路(2)二阶高通滤波电路(压控电压源;Sallen-Key)2、反相输入高通滤波电路(1)二阶高通滤波电路(无限增益多路反馈) 一、低通滤波器 1、同相输入低通滤波器 (1)一阶低通滤波电路 即在一阶无源低通滤波器的基础上,加了一个集成运放。 它的

qq_41873236的博客 3万+

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

3.1可见性      “...因此,从内存可见性的角度来看,写入volatile变量相当于退出同步代码块,而读取volatile变量相当于进入同步代码块。”        是否理解为,写入是没有锁的(所以并发写会产生问题),读取的时候有锁,所以读到的不是缓存的数据,同时不会被写入操作更改。        volatile变量适用于一写多读。

yibei8811的专栏 1125

Android Audio - 支持多应用同时录音_Android8.1修改方法

支持多应用同时录音_Android8.1修改方法 源码路径: sdk\frameworks\av\services\audiopolicy\managerdefault\AudioPolicyManager.cpp 需要修改两个位置: audio_io_handle_t AudioPolicyManager::getInputForDevice() 在mpClientInterface-&gt...

iFinelio Tower 的博客 5758

ECharts 实现世界地图

效果图 源代码(仅供参考) <template> <div class="echarts"> <div class="className" id="id" style="width: 100%; height: 450px" ref="myEchart" ></div> </div> </template> <script lang="ts"> imp

EsquireY的博客 4873

《Java并发编程实战》-3

对象的组合 4.1 设计线程安全的类 在设计线程安全类的过程中,需要包含一下三个基本要素: 找出构成对象状态的所有变量。 找出约束状态变量的不变性条件。 建立对象状态的并发访问管理策略。 4.1.1 收集同步需求 如果不了解对象的不变性与厚颜条件,那么就不能确保线程安全性。要满足在状态变量的有效值或状态转换上的各种约束条件,就需要结组于原子性和封装性。 4.1....

gonefuture的学习博客 309

K型热电偶分度表读取方法

K型热电偶分度表读取方法

guaizaiguaizai的博客 5891

面向对象随想 -- 多用组合来代替继承

用组合来代替继承

kbasm的专栏 1425

Jetson Nano 【1】入手开箱、烧系统

类似于半开箱哈哈 怀着小期待的心情,屁颠屁颠跑到村口领快递,拿到了这个香喷喷的Jetson Nano 开发板 开箱: 由于太激动了忘记拍点啥,于是在最后留了张全家福 板子的结构: 我觉得这个有必要记录一下,于是去官方文档拿了张图来:官方文档传送 1=SD card 的卡槽,位于第二层板子的背部,也就是大风扇屁股位置 2=40针扩展接头(还没开用) 3=USB的供电口,根据大佬测试(某哩...

椰子奶糖的博客 4194

银行监管报送系统介绍(十五):金融审计平台

金审平台主要包括基础、财务、结算、信贷和中间业务五大板块,主要服务于审计署对中国银行的各项审计任务,同时也可为行内审计、稽核检查、数据分析、总分行提供基础数据服务,为实现基础数据共享提供了有力的数据保障。2019年1月某全国性股份制商业银行收到审计署2019-23号文,文中指出为进一步提高审计数据质量,审计署审计组派员前往9家重点商业银行,就完善标准化数据的相关情况与各银行有关部门进行讨论协商。关于数据报送频率及报送内容:数据从2019年4月开始,按月报送,即于每月25日之前报送上月数据;

银行信息系统应用架构详解 2053

如何将多个C语言模块组合成一个程序

一般来说,我们编制的C语言源程序都是放在一个扩展名为.C的文件中,这对一个较简单的程序是合适的,但对于大型项目就不合适了,其一,大项目往往由多人合作完成,大家都来 操作同一个文件极易出错;其二,大文件的编译、调试都极为复杂;其三,如果两个程序中都要用到同一个自定义的函数,此函数须从一文件移动到另一文件中,这种移动容易出错。为了解决这一矛盾,一个大的程序往往被分成几个部分,存放在不同的DOS文件各。

小小阎罗的专栏 7257

基于CNN的三相故障识别算法及实现(Matlab)

通过预处理数据、构建CNN模型、训练与评估模型等步骤,我们可以准确快速地识别三相故障。现代电力系统中,三相故障是一种常见的故障情况,可以导致电网中断、设备损坏甚至火灾等严重后果。本文将介绍基于卷积神经网络(CNN)的三相故障识别算法,并提供Matlab源代码实现。首先,将原始信号分为训练集和测试集。然后,对输入信号进行归一化处理,确保数据在合适的范围内。CNN适用于处理二维数据,所以我们可以将输入信号看作是时间序列数据和特征(如电压、电流等)的二维矩阵。在开始之前,我们需要一个包含正常和故障样本的数据集。

学习使你进步。 384

Java并发编程实战 -- 基础知识

文章目录第一章第二章第三章第四章第五章         平台提供的各种并发功能与开发人员在程序中需要的并发语义并不匹配! 第一章 第二章 第三章 第四章 第五章

Syntactic Sugar 320

基于STM32L4XX、HAL库的AP6275S WIFI 模块驱动程序设计

支持802.11a/b/g/n/ac/ax (Wi-Fi 6)标准。// 这部分通常依赖于Broadcom提供的固件和驱动。// 这里需要添加Wi-Fi固件加载和初始化的代码。// 通常需要通过SDIO发送命令和接收响应。// 这里需要添加蓝牙HCI初始化代码。// 处理Wi-Fi和蓝牙数据。// 这里需要实现Wi-Fi连接逻辑。// 初始化SDIO和UART。// 确保蓝牙使能引脚已设置。// 关闭Wi-Fi和蓝牙。// 关闭Wi-Fi和蓝牙。// 打开Wi-Fi和蓝牙。// 初始化AP6275S。

zhusl6688的博客 275

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

JAVA 并发编程—基础2

对象的组合 ###1、设计线程安全的类   在设计线程安全类的过程中,需要包含以下三个基本要素 - 找出构成对象状态的所有变量。 - 找出约束状态变量的不变性条件。 - 建立对象状态的并发访问策略。   要分析对象的状态,首先从对象的域开始。如果对象中所有的域都是基本类型的变量,那么这些类将构成对象的全部状态。   同步策略定义了如何在不违背对象不变条件或后验条件的情况下对其状态的访问操作进...

Q12271235的博客 266

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

《java并发编程实战》

(第5章的ConcurrentHashMap部分未完成) 第2章 线程安全性 无状态对象一定是线程安全的,无状态:既不包含任何域,也不包含对其他类中域的引用。 竞态条件(Race Condition):因不恰当的执行时序而出现不正确的结果(要 想获得正确的结果,取决于事件发生的时序) 原子性:i++不是原子的,包括read-update-write。 对于无状态的类: 增加单个域,可以...

六种鱼的博客 5451

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

《Java并发编程实战》第4、5章学习笔记

本周进行了《Java并发编程实战》第4、5章的学习,这部分的知识更加侧重于并发编程的思想。 对象的组合 对象的状态:由对象中的所有所有域以及域中所有被引用对象的域组成。 状态空间:即对象中所有状态的可能的取值。状态空间越小,就越容易判断线程的状态。其中final类型的域的状态只有唯一的一个,所以尽量使用final类型的变量。 不可变条件:用来判断状态的变化是否是有效的,也就是在多线程环境中对变量的各种条件约束。例如:在多线程环境中,变量的变化不能小于0且不能大于100,修改变量前需要参考之前的值

weixin_45207474的博客 199

java并发编程 目录_Java并发编程实战的作品目录

展开全部对本书的赞誉译者序前 言第1章 简介1.1 并发简史1.2 线程的优势1.2.1 发挥多处理器的强大能力e5a48de588b662616964757a686964616f313333613031321.2.2 建模的简单性1.2.3 异步事件的简化处理1.2.4 响应更灵敏的用户界面1.3 线程带来的风险1.3.1 安全性问题1.3.2 活跃性问题1.3.3 性能问题1.4 线程无处不在...

weixin_39564036的博客 443

《Java并发编程实战》笔记

这个假期读了《Java并发编程实战》,因为暂时没有相关项目的实践所以读来的作用也只是了解居多。这里记录一下阅读过程中的笔记,到后面要用到的时候可以回来看看,再结合这里的总结去翻翻书里相关部分的原文。 文章目录第一部分 基础知识第二章 线程安全性竞态条件Java内置锁重入其他第三章 对象的共享可见性volatile多线程的long和double发布与逸出线程封闭栈封闭ThreadLocal类不变性第四章 对象的组合Java监视器模式为现有类添加原子操作修改这个类扩展这个类客户端加锁机制组合第五章 基础构建模块

F_Arisa的博客 1489

java并发编程 pdf_Java并发编程实战 PDF

目录对本书的赞誉译者序前 言第1章 简介1.1 并发简史1.2 线程的优势1.2.1 发挥多处理器的强大能力1.2.2 建模的简单性1.2.3 异步事件的简化处理1.2.4 响应更灵敏的用户界面1.3 线程带来的风险1.3.1 安全性问题1.3.2 活跃性问题1.3.3 性能问题1.4 线程无处不在第一部分 基础知识第2章 线程安全性2.1 什么是线程安全性2.2 原子性2.2.1 竞态条件2.2...

weixin_36077932的博客 1301

《JAVA并发编程实战》笔记

第1章 简介1.1 并发简史 1.2 线程的优势1.2.1 发挥多处理器的强大能力1.2.2 建模的简单性1.2.3 异步事件的简化处理1.2.4 响应更灵敏的用户界面 1.3 线程带来的风险1.3.1 安全性问题1.3.2 活跃性问题1.3.3 性能问题 1.4 线程无处不在第一部分 基础知识第2章 线程安全性2.1 什么是线程安全性 2.2 原子性 2...

weixin_34318326的博客 121

java并发编程源码_Java并发编程实战 PDF+源码

《Java并发编程实战》深入浅出地介绍了Java线程和并发,是一本完美的Java并发参考手册。书中从并发性和线程安全性的基本概念出发,介绍了如何使用类库提供的基本并发构建块,用于避免并发危险、构造线程安全的类及验证线程安全的规则,如何将小的线程安全类组合成更大的线程安全类,如何利用线程来提高并发应用程序的吞吐量,如何识别可并行执行的任务,如何提高单线程子系统的响应性,如何确保并发程序执行预期任务,...

weixin_36418584的博客 1136

《Java并发编程实战》读后感

文章目录第一部分 基础知识第一章 简介二、使用步骤1.引入库2.读入数据总结 第一部分 基础知识 第一章 简介 1.1线程的作用? 线程会共享进程范围内资源,如:内存句柄和文件句柄,但每个线程都有各自的程序计数器、栈以及局部变量等。 示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。 二、使用步骤 1.引入库 代码如下(示例): import numpy as np import pandas as pd import matplotlib.pyplot as pl

qq_42514129的博客 1393
上一篇: 深入理解JVM - 类文件结构
下一篇: 并发编程实战 - [任务执行、取消与关闭]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值