并发队列、线程池、锁

1、CountDownLatch(计数器)
     CountDownLatch 类位于java.util.concurrent包下,利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在await()锁上等待的线程就可以恢复执行任务。

package com.zhang;import java.util.concurrent.CountDownLatch;public class TestCountDownLatch {public static void main(String[] args) throws InterruptedException {final CountDownLatch countDownLatch = new CountDownLatch(2);new Thread(new Runnable() {public void run() {System.out.println(Thread.currentThread().getName() + ",子线程开始执行...");countDownLatch.countDown();System.out.println(Thread.currentThread().getName() + ",子线程结束执行...");}}).start();new Thread(new Runnable() {public void run() {System.out.println(Thread.currentThread().getName() + ",子线程开始执行...");countDownLatch.countDown();//计数器值每次减去1System.out.println(Thread.currentThread().getName() + ",子线程结束执行...");}}).start();countDownLatch.await();// 減去为0,恢复任务继续执行System.out.println("两个子线程执行完毕....");System.out.println("主线程继续执行.....");for (int i = 0; i < 10; i++) {System.out.println("main,i:" + i);}}}

2、CyclicBarrier(屏障)

      CyclicBarrier初始化时规定一个数目,然后计算调用了CyclicBarrier.await()进入等待的线程数。当线程数达到了这个数目时,所有进入等待状态的线程被唤醒并继续CyclicBarrier就象它名字的意思一样,可看成是个障碍,所有的线程必须到齐后才能一起通过这个障碍。

      CyclicBarrier初始时还可带一个Runnable的参数,此Runnable任务在CyclicBarrier的数目达到后,所有其它线程被唤醒前被执行。

import java.util.concurrent.CyclicBarrier;class Writer extends Thread {private CyclicBarrier cyclicBarrier;public Writer(CyclicBarrier cyclicBarrier) {this.cyclicBarrier = cyclicBarrier;}@Overridepublic void run() {try {System.out.println("线程" + Thread.currentThread().getName() + ",正在写入数据");Thread.sleep(3000);System.out.println("线程" + Thread.currentThread().getName() + ",写入数据成功.....");cyclicBarrier.await();System.out.println("所有线程执行完毕..........");} catch (Exception e) {}}
}public class Test001 {public static void main(String[] args) {CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new Runnable() {public void run() {System.out.println("全部线程唤醒前被执行");}});for (int i = 0; i < 5; i++) {Writer writer = new Writer(cyclicBarrier);writer.start();}}
}

3、Semaphore(计数信号量)

     Semaphore是一种基于计数的信号量。它可以设定一个阈值,多个线程竞争获取许可信号,做自己的申请后归还,超过阈值后线程申请许可信号将会被阻塞。

需求: 一个厕所只有3个坑位,但是有10个人来上厕所,那怎么办?假设10的人的编号分别为1-10,并且1号先到厕所,10号最后到厕所。那么1-3号来的时候必然有可用坑位,顺利如厕,4号来的时候需要看看前面3人是否有人出来了,如果有人出来就进去,否则等待。同样的道理,4-10号也需要等待正在上厕所的人出来后才能进去,并且谁先进去这得看等待的人是否有素质,是否能遵守先来先上的规则。(公平锁与非公平锁:排队和竞争)

package com.zhang.test;import java.util.concurrent.Semaphore;class ThradDemo001 extends Thread {private String name;private Semaphore wc;public ThradDemo001(String name, Semaphore wc) {this.name = name;this.wc = wc;}@Overridepublic void run() {try {// 剩下的资源int availablePermits = wc.availablePermits();if (availablePermits > 0) {System.out.println(name + "天助我也,终于有茅坑了.....");} else {System.out.println(name + "怎么没有茅坑了...");}// 申请资源
            wc.acquire();System.out.println(name + "终于上厕所啦.爽啊" + ",剩下厕所:" + wc.availablePermits());Thread.sleep(1000);System.out.println(name + "厕所上完啦!");// 释放资源
            wc.release();} catch (Exception e) {}}
}public class TestSemaphore {public static void main(String[] args) {Semaphore semaphore = new Semaphore(3);for (int i = 1; i <= 10; i++) {ThradDemo001 thradDemo001 = new ThradDemo001("第" + i + "个人", semaphore);thradDemo001.start();}}} 

         在很多情况下,可能有多个线程需要访问数目很少的资源。假想在服务器上运行着若干个回答客户端请求的线程。这些线程需要连接到同一数据库,但任一时刻只能获得一定数目的数据库连接。你要怎样才能够有效地将这些固定数目的数据库连接分配给大量的线程?     

       答:1.给方法加同步锁,保证同一时刻只能有一个人去调用此方法,其他所有线程排队等待,但是此种情况下即使你的数据库链接有10个,也始终只有一个处于使用状态。这样将会大大的浪费系统资源,而且系统的运行效率非常的低下。

            2.另外一种方法当然是使用信号量,通过信号量许可与数据库可用连接数相同的数目,将大大的提高效率和性能。

3、并发队列

     在并发队列上JDK提供了两套实现,一个是以ConcurrentLinkedQueue为代表的高性能非阻塞队列,一个是以BlockingQueue接口为代表的阻塞队列,无论哪种都继承自Queue。

4、阻塞队列与非阻塞队

      阻塞队列与普通队列的区别在于,当队列是空的时,从队列中获取元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞。试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素。试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他的线程使队列重新变得不满

1.ArrayDeque, (数组双端队列) 
2.PriorityQueue, (优先级队列) 
3.ConcurrentLinkedQueue, (基于链表的并发队列) 
4.DelayQueue, (延期阻塞队列)(阻塞队列实现了BlockingQueue接口) 
5.ArrayBlockingQueue, (基于数组的并发阻塞队列) 
6.LinkedBlockingQueue, (基于链表的FIFO阻塞队列) 
7.LinkedBlockingDeque, (基于链表的FIFO双端阻塞队列) 
8.PriorityBlockingQueue, (带优先级的无界阻塞队列) 
9.SynchronousQueue (并发同步阻塞队列)

5、ConcurrentLinkedQueue(非阻塞)

     ConcurrentLinkedQueue : 是一个适用于高并发场景下的队列,通过无锁的方式,实现了高并发状态下的高性能,通常ConcurrentLinkedQueue性能好于BlockingQueue.它是一个基于链接节点的无界线程安全队列。该队列的元素遵循先进先出的原则。该队列不允许null元素。

     ConcurrentLinkedQueue重要方法:
       add 和offer() 都是加入元素的方法(在ConcurrentLinkedQueue中这俩个方法没有任何区别)
       poll() 和peek() 都是取头元素节点,区别在于前者会删除元素,后者不会。

package com.zhang.test;import java.util.concurrent.ConcurrentLinkedQueue;public class TestConcurrentLinkedQueue {public static void main(String[] args) {ConcurrentLinkedQueue q = new ConcurrentLinkedQueue();q.offer("张三");q.add("add");q.offer("李四");q.offer("哈哈哈");System.out.println(q.poll());//从头获取元素,删除该元素System.out.println(q.peek()); //从头获取元素,不刪除该元素System.out.println(q.size()); //获取总长度
    }
}

6、BlockingQueue

     阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。因此当一个线程试图对一个已经满了的队列进行入队列操作时,它将会被阻塞,除非有另一个线程做了出队列操作;同样,当一个线程试图对一个空队列进行出队列操作时,它将会被阻塞,除非有另一个线程进行了入队列操作。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。阻塞队列是线程安全的。

7、ArrayBlockingQueue

     ArrayBlockingQueue是一个有边界的阻塞队列,它的内部实现是一个数组。有边界的意思是它的容量是有限的,我们必须在其初始化的时候指定它的容量大小,容量大小一旦指定就不可改变。ArrayBlockingQueue是以先进先出的方式存储数据,最新插入的对象是尾部,最新移出的对象是头部。

package com.zhang.test;import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;public class TestArrayBlockingQueue {public static void main(String[] args) throws  Exception{ArrayBlockingQueue<String> arrays = new ArrayBlockingQueue<String>(3);arrays.add("李四");arrays.add("张军");arrays.add("张军");// 添加阻塞队列arrays.offer("张三", 1, TimeUnit.SECONDS);System.out.println(arrays.poll());System.out.println(arrays.poll());System.out.println(arrays.poll());System.out.println(arrays.poll());//null
    }
}

8、LinkedBlockingQueue

     LinkedBlockingQueue阻塞队列大小的配置是可选的,如果我们初始化时指定一个大小,它就是有边界的,如果不指定,它就是无边界的。说是无边界,其实是采用了默认大小为Integer.MAX_VALUE的容量 。它的内部实现是一个链表。和ArrayBlockingQueue一样,LinkedBlockingQueue 也是以先进先出的方式存储数据,最新插入的对象是尾部,最新移出的对象是头部。

package com.zhang.test;import java.util.concurrent.LinkedBlockingQueue;public class TestLinkedBlockingQueue {public static void main(String[] args) {LinkedBlockingQueue linkedBlockingQueue = new LinkedBlockingQueue(3);linkedBlockingQueue.add("张三");linkedBlockingQueue.add("李四");linkedBlockingQueue.add("李四");//  linkedBlockingQueue.add("王五"); //抛异常
        System.out.println(linkedBlockingQueue.size());}
}

9、使用BlockingQueue模拟生产者与消费者

package com.zhang.test;import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;class ProducerThread implements Runnable {private BlockingQueue<String> blockingQueue;private AtomicInteger count = new AtomicInteger();private volatile boolean FLAG = true;public ProducerThread(BlockingQueue<String> blockingQueue) {this.blockingQueue = blockingQueue;}public void run() {System.out.println(Thread.currentThread().getName() + "生产者开始启动....");while (FLAG) {String data = count.incrementAndGet() + "";try {boolean offer = blockingQueue.offer(data, 2, TimeUnit.SECONDS);if (offer) {System.out.println(Thread.currentThread().getName() + ",生产队列" + data + "成功..");} else {System.out.println(Thread.currentThread().getName() + ",生产队列" + data + "失败..");}Thread.sleep(1000);} catch (Exception e) {}}System.out.println(Thread.currentThread().getName() + ",生产者线程停止...");}public void stop() {this.FLAG = false;}}class ConsumerThread implements Runnable {private volatile boolean FLAG = true;private BlockingQueue<String> blockingQueue;public ConsumerThread(BlockingQueue<String> blockingQueue) {this.blockingQueue = blockingQueue;}public void run() {System.out.println(Thread.currentThread().getName() + "消费者开始启动....");while (FLAG) {try {String data = blockingQueue.poll(2, TimeUnit.SECONDS);if (data == null) {FLAG = false;System.out.println("消费者超过2秒时间未获取到消息.");return;}System.out.println("消费者获取到队列信息成功,data:" + data);} catch (Exception e) {// TODO: handle exception
            }}}}public class Test0008 {public static void main(String[] args) {LinkedBlockingQueue<String> blockingQueue = new LinkedBlockingQueue<String>(3);ProducerThread producerThread = new ProducerThread(blockingQueue);ConsumerThread consumerThread = new ConsumerThread(blockingQueue);Thread t1 = new Thread(producerThread);Thread t2 = new Thread(consumerThread);t1.start();t2.start();//10秒后 停止线程..try {Thread.sleep(10*1000);producerThread.stop();} catch (Exception e) {// TODO: handle exception
        }}}

10、线程池的作用

合理地使用线程池能够带来3个好处:

  1. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  2. 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  3. 提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控

11、ThreadPoolExecutor机制 

corePoolSize

核心线程池大小

maximumPoolSize

最大线程池大小

keepAliveTime

线程池中超过corePoolSize数目的空闲线程最大存活时间;可以allowCoreThreadTimeOut(true)使得核心线程有效时间

TimeUnit

keepAliveTime时间单位

workQueue

阻塞任务队列

threadFactory

新建线程工厂

RejectedExecutionHandler

当提交任务数超过maxmumPoolSize+workQueue之和时,任务会交给RejectedExecutionHandler来处理

  1. 当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。 
  2. 当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行 
  3. 当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务 
  4. 当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理 
  5. 当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程 
  6. 当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭 

12、线程池原理剖析

       提交一个任务到线程池中,线程池的处理流程如下:

  1. 判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。
  2. 线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。
  3. 判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。

13、线程池四种创建方式

       Java通过Executors(jdk1.5并发包)提供四种线程池:
newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

 
// 无限大小线程池 jvm自动回收ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();for (int i = 0; i < 10; i++) {final int temp = i;newCachedThreadPool.execute(new Runnable() {public void run() {System.out.println(Thread.currentThread().getName() + ",i:" + temp);}});}

newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

 ExecutorService newFixedThreadPool=   Executors.newFixedThreadPool(10);for (int i=0;i<100;i++) {final int temp = i;newFixedThreadPool.execute(new Runnable() {public void run() {System.out.println(Thread.currentThread().getName()+",i"+temp);}});}

 

newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(5);for (int i = 0; i < 10; i++) {final int temp = i;newScheduledThreadPool.schedule(new Runnable() {public void run() {System.out.println(Thread.currentThread().getName()+",i:" + temp);}}, 3, TimeUnit.SECONDS);//表示延迟3秒执行}

 

newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

//结果依次输出,相当于顺序执行各个任务。  
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();for (int i = 0; i < 10; i++) {final int index = i;newSingleThreadExecutor.execute(new Runnable() {public void run() {System.out.println(Thread.currentThread().getName()+",index:" + index);try {Thread.sleep(200);} catch (Exception e) {}}});}

14、自定义线程线程池

package com.zhang.test;import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;/***         //提交一个任务到线程池中,线程池的处理流程如下:*         //1、判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。*         //2、线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。*         //3、判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。*/
public class Test0007 {public static void main(String[] args) {/*** int corePoolSize 核心线程数* int maximumPoolSize 最大能创建多少个线程* long keepAliveTime 存活时间* TimeUnit unit,* BlockingQueue<Runnable> workQueue 线程队列*/ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 2, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3));for (int i = 1; i <= 5; i++) {final int temp=i;threadPoolExecutor.execute(new Runnable() {public void run() {System.out.println(Thread.currentThread().getName()+"任务"+temp);}});}threadPoolExecutor.shutdown();}
}

15、合理配置线程池

       CPU密集:是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。CPU密集型时,任务可以少配置线程数,大概和机器的cpu核数相当,这样可以使得每个线程都在执行任务

       IO密集:即该任务需要大量的IO,即大量的阻塞。在单线程上运行IO密集型的任务会导致浪费大量的CPU运算能力浪费在等待。IO密集型时,大部分线程都阻塞,故需要多配置线程数,2*cpu核数

16、Callable与Future

        在Java中创建线程一般有两种方式,一种是继承Thread类,一种是实现Runnable接口。这两种方式的缺点是在线程任务执行结束后,无法获取执行结果。我们一般只能采用共享变量或共享存储区以及线程通信的方式实现获得任务结果的目的。Java中也提供了使用Callable和Future来实现获取任务结果的操作。Callable用来执行任务产生结果,而Future用来获得结果。Future常用方法:

  1. V get() :获取异步执行的结果,如果没有结果可用,此方法会阻塞直到异步计算完成。
  2. V get(Long timeout , TimeUnit unit) :获取异步执行结果,如果没有结果可用,此方法会阻塞,但是会有时间限制,如果阻塞时间超过设定的timeout时间,该方法将抛出异常。
  3. boolean isDone() :如果任务执行结束,无论是正常结束或是中途取消还是发生异常,都返回true。
  4. boolean isCanceller() :如果任务完成前被取消,则返回true。
  5. boolean cancel(boolean mayInterruptRunning) :如果任务还没开始,执行cancel(...)方法将返回false;如果任务已经启动,执行cancel(true)方法将以中断执行此任务线程的方式来试图停止任务,如果停止成功,返回true;当任务已经启动,执行cancel(false)方法将不会对正在执行的任务线程产生影响(让线程正常执行到完成),此时返回false;当任务已经完成,执行cancel(...)方法将返回false。mayInterruptRunning参数表示是否中断执行中的线程。

       通过方法分析我们也知道实际上Future提供了3种功能:

  1. 能够中断执行中的任务
  2. 判断任务是否执行完成
  3. 获取任务执行完成后的结果
package com.zhang.test;import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;public class TestMain {public static void main(String[] args) throws Exception {ExecutorService executor = Executors.newCachedThreadPool();Future<Integer> future = executor.submit(new Callable<Integer>() {public Integer call() throws Exception {System.out.println("子线程执行call()");Thread.sleep(5000);return 200;}});System.out.println("主线程执行其他任务");Integer integer = future.get();//等待返回结果,这里会阻塞
        System.out.println(integer);// 关闭线程池if (executor != null)executor.shutdown();}
}

17、Futrure模式

    在多线程中经常举的一个例子就是:网络图片的下载,刚开始是通过模糊的图片来代替最后的图片,等下载图片的线程下载完图片后在替换。而在这个过程中可以做一些其他的事情。首先客户端向服务器请求RealSubject,但是这个资源的创建是非常耗时的。这种情况下,首先返回Client一个FutureSubject,以满足客户端的需求,于此同时,Future会通过另外一个Thread 去构造一个真正的资源,资源准备完毕之后,在给future一个通知。如果客户端急于获取这个真正的资源,那么就会阻塞客户端的其他所有线程,等待资源准备完毕

package com.zhang.future;/*** 公共数据接口,FutureData和RealData都要实现*/
public interface Data {String getRequest();
}
package com.zhang.future;public class RealData implements Data {private String result;public RealData(String data) {System.out.println("正在使用data:" + data + "网络请求数据,耗时操作需要等待.");try {Thread.sleep(3000);} catch (Exception e) {}System.out.println("操作完毕,获取结果...");result = "哈哈哈";}public String getRequest() {return result;}
}
package com.zhang.future;/*** FutureData,当有线程想要获取RealData的时候,程序会被阻塞。等到RealData被注入才会使用getReal()方法*/
public class FurureData implements Data {private volatile static boolean ISFLAG = false;private RealData realData;public synchronized void setRealData(RealData realData) {// 如果已经获取到结果,直接返回if (ISFLAG) {return;}// 如果没有获取到数据,传递真是对象this.realData = realData;ISFLAG = true;// 进行通知
        notify();}public synchronized String getRequest() {while (!ISFLAG) {try {wait();} catch (Exception e) {}}// 获取到数据,直接返回return realData.getRequest();}}
package com.zhang.future;public class FutureClient {public Data request( final String queryStr) {final     FurureData furureData = new FurureData();new Thread(new Runnable() {public void run() {RealData realData = new RealData(queryStr);furureData.setRealData(realData);}}).start();return furureData;}}
package com.zhang.future;public class Main {public static void main(String[] args) {FutureClient futureClient = new FutureClient();Data request = futureClient.request("请求参数.");System.out.println("请求发送成功!");System.out.println("执行其他任务...");String result = request.getRequest();System.out.println("获取到结果..." + result);}}

18、悲观锁与乐观锁

        悲观锁:悲观锁悲观的认为每一次操作都会造成更新丢失问题,在每次查询时加上排他锁。每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。synchronized的思想也是悲观锁。

Select * from xxx for update;

       乐观锁:乐观锁会乐观的认为每次查询都不会造成更新丢失,利用版本字段控制。一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。

update table set x=x+1, version=version+1 where id=#{id} and version=#{version}; 

19、重入锁

       重入锁,也叫做递归锁,指的是同一线程外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。ReentrantLock和synchronized 都是可重入锁

package com.zhang.cache;public class Test implements Runnable {private   synchronized void get() {System.out.println("name:" + Thread.currentThread().getName() + " get();");set();}private synchronized  void set() {System.out.println("name:" + Thread.currentThread().getName() + " set();");}public void run() {get();}public static void main(String[] args) {Test ss = new Test();new Thread(ss).start();new Thread(ss).start();new Thread(ss).start();new Thread(ss).start();}
}
package com.zhang.cache;import java.util.concurrent.locks.ReentrantLock;public class Test02 extends Thread {ReentrantLock lock = new ReentrantLock();private void get() {lock.lock();System.out.println(Thread.currentThread().getId()+",get()");set();lock.unlock();}private void set() {lock.lock();System.out.println(Thread.currentThread().getId()+",set()");lock.unlock();}public void run() {get();}public static void main(String[] args) {Test02 ss = new Test02();new Thread(ss).start();new Thread(ss).start();new Thread(ss).start();}}

20、读写锁

假设你的程序中涉及到对一些共享资源的读和写操作,且写操作没有读操作那么频繁。在没有写操作的时候,两个线程同时读一个资源没有任何问题,所以应该允许多个线程能在同时读取共享资源。但是如果有一个线程想去写这些共享资源,就不应该再有其它线程对该资源进行读或写。这就需要一个读/写锁来解决这个问题。Java5在java.util.concurrent包中已经包含了读写锁。

package com.zhang.cache;import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;public class Cache {static Map<String, Object> map = new HashMap<String, Object>();static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();static Lock r = rwl.readLock();static Lock w = rwl.writeLock();// 获取一个key对应的valueprivate static  Object get(String key) {r.lock();try {System.out.println("正在做读的操作,key:" + key + " 开始");Thread.sleep(100);Object object = map.get(key);System.out.println("正在做读的操作,key:" + key + ",value:" + object + "结束.");return object;} catch (InterruptedException e) {} finally {r.unlock();}return key;}// 设置key对应的valueprivate static  Object put(String key, Object value) {w.lock();try {System.out.println("正在做写的操作,key:" + key + ",value:" + value + "开始.");Thread.sleep(100);Object object = map.put(key, value);System.out.println("正在做写的操作,key:" + key + ",value:" + value + "结束.");return object;} catch (InterruptedException e) {} finally {w.unlock();}return value;}public static void main(String[] args) {new Thread(new Runnable() {public void run() {for (int i = 0; i < 10; i++) {Cache.put(i + "", i + "");}}}).start();new Thread(new Runnable() {public void run() {for (int i = 0; i < 10; i++) {Cache.get(i + "");}}}).start();}
}

21、CAS无锁机制

无锁的好处:

  1. 由于其非阻塞性,它对死锁问题天生免疫,并且线程间的相互影响也远远比基于锁的方式要小。
  2. 使用无锁的方式完全没有锁竞争带来的系统开销,也没有线程间频繁调度带来的开销,因此它要比基于锁的方式拥有更优越的性能

CAS算法理解:

  1. 它包含三个参数CAS(V,E,N): V表示要更新的变量,E表示预期值,N表示新值。仅当V值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。最后,CAS返回当前V的真实值。
  2. CAS操作是抱着乐观的态度进行的,它总是认为自己可以成功完成操作。当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。失败的线程不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。基于这样的原理,CAS操作即使没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理。
  3. 简单地说,CAS需要你额外给出一个期望值,也就是你认为这个变量现在应该是什么样子的。如果变量不是你想象的那样,那说明它已经被别人修改过了。你就重新读取,再次尝试修改就好了。

CAS缺点:

  1. CAS存在一个很明显的问题,即ABA问题:如果变量V初次读取的时候是A,并且在准备赋值的时候检查到它仍然是A,如果在这段期间曾经被改成B,然后又改回A,那CAS操作就会误认为它从来没有被修改过。针对这种情况,java并发包中提供了一个带有标记的原子引用类AtomicStampedReference,它可以通过控制变量值的版本来保证CAS的正确性
  2. CPU开销较大:在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力
  3. 不能保证代码块的原子性:CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了。

22、自旋锁

       自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋:死循环),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。但是线程自旋是需要消耗cup的,说白了就是让cup在做无用功,线程不能一直占用cup自旋做无用功,所以需要设定一个自旋等待的最大时间。如果持有锁的线程执行的时间超过自旋等待的最大时间扔没有释放锁,就会导致其它争用锁的线程在最大等待时间内还是获取不到锁,这时争用线程会停止自旋进入阻塞状态。

23、常用原子类

        Java中的原子操作类大致可以分为4类:原子更新基本类型、原子更新数组类型、原子更新引用类型、原子更新属性类型。这些原子类中都是用了无锁的概念,有的地方直接使用CAS操作的线程安全的类型。

AtomicBoolean

AtomicInteger

AtomicLong

AtomicReference

 

package com.zhang.future;import java.util.concurrent.atomic.AtomicInteger;public class Test0001 implements Runnable {private static AtomicInteger atomic = new AtomicInteger();public void run() {while (true) {int count = getCountAtomic();System.out.println(count);if (count >= 150) {break;}}}private Integer getCountAtomic() {try {Thread.sleep(50);} catch (Exception e) {}return atomic.incrementAndGet();}public static void main(String[] args) {Test0001 test0001 = new Test0001();Thread t1 = new Thread(test0001);Thread t2 = new Thread(test0001);t1.start();t2.start();}}

 

24、并发框架Disruptor

转载于:https://www.cnblogs.com/zhangjinru123/p/10429472.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/569698.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

POJ 1088----滑雪(DP)

原题连接&#xff1a;http://poj.org/problem?id1088 Description Michael喜欢滑雪百这并不奇怪&#xff0c; 因为滑雪的确很刺激。可是为了获得速度&#xff0c;滑的区域必须向下倾斜&#xff0c;而且当你滑到坡底&#xff0c;你不得不再次走上坡或者等待升降机来载你。Mic…

Vijos 1603 ----迷宫(矩阵乘法,矩阵快速幂)

描述 在某个神秘的星球上有一个游乐园 游乐园里有一个奇怪的迷宫&#xff0c;迷宫内有n个点&#xff0c;每个点之间都可能会有一条有向边&#xff08;可能会有自环&#xff09; 现在游乐园主有个问题想请你帮忙&#xff1a; 问&#xff1a;从s点走到f点&#xff0c;恰好走过…

蓝桥杯第六届省赛JAVA真题----循环节长度

循环节长度 两个整数做除法&#xff0c;有时会产生循环小数&#xff0c;其循环部分称为&#xff1a;循环节。 比如&#xff0c;11/136>0.846153846153….. 其循环节为[846153] 共有6位。 下面的方法&#xff0c;可以求出循环节的长度。 请仔细阅读代码&#xff0c;并填写…

蓝桥杯第六届省赛JAVA真题----打印菱形

打印菱形 给出菱形的边长&#xff0c;在控制台上打印出一个菱形来。 为了便于比对空格&#xff0c;我们把空格用句点代替。 当边长为8时&#xff0c;菱形为&#xff1a; .......* ......*.* .....*...* ....*.....* ...*.......* ..*.........* .*...........* *..........…

蓝桥杯第六届省赛JAVA真题----生命之树

生命之树 在X森林里&#xff0c;上帝创建了生命之树。 他给每棵树的每个节点&#xff08;叶子也称为一个节点&#xff09;上&#xff0c;都标了一个整数&#xff0c;代表这个点的和谐值。 上帝要在这棵树内选出一个非空节点集S&#xff0c;使得对于S中的任意两个点a,b&#…

蓝桥杯第七届省赛JAVA真题----剪邮票

剪邮票 如【图1.jpg】, 有12张连在一起的12生肖的邮票。 现在你要从中剪下5张来&#xff0c;要求必须是连着的。 &#xff08;仅仅连接一个角不算相连&#xff09; 比如&#xff0c;【图2.jpg】&#xff0c;【图3.jpg】中&#xff0c;粉红色所示部分就是合格的剪取。 请你…

PAT甲级1080 Graduate Admission【模拟】

题目&#xff1a;https://pintia.cn/problem-sets/994805342720868352/problems/994805387268571136 题意&#xff1a; 模拟高考志愿录取。 考生根据总成绩和高考成绩排名。根据排名往下录取&#xff0c;每个人有k个志愿。 如果他填的学校名额没有满&#xff0c;那么就可以被录…

蓝桥杯第三届省赛JAVA真题----取球博弈

题目描述 今盒子里有n个小球&#xff0c;A、B两人轮流从盒中取球&#xff0c;每个人都可以看到另一个人取了多少个&#xff0c;也可以看到盒中还剩下多少个&#xff0c;并且两人都很聪明&#xff0c;不会做出错误的判断。 我们约定&#xff1a; 每个人从盒子中取出的球的数目…

目标检测之YOLO V2 V3

YOLO V2 YOLO V2是在YOLO的基础上&#xff0c;融合了其他一些网络结构的特性&#xff08;比如&#xff1a;Faster R-CNN的Anchor,GooLeNet的\(1\times1\)卷积核等&#xff09;&#xff0c;进行的升级。其目的是弥补YOLO的两个缺陷&#xff1a; YOLO中的大量的定位错误和基于区域…

蓝桥杯第五届省赛JAVA真题----n级台阶

有n级台阶。从地面&#xff08;第0级&#xff09;出发&#xff0c;首先连续的上台阶&#xff0c;上到不超过第n级的某一个位置后再连续的下台阶&#xff0c;直到回到地面。若每次上下台阶只允许走1级或2级&#xff0c;请问可能的上下台阶的方案数是多少&#xff1f; 特别地&am…

ubuntu下安装oracle

开源的世界挺有意思&#xff0c;安装oracle如此复杂。 主要分为四个大步骤&#xff1a; Java的安装Oracle安装前的准备Oracle的安装环境配置安装Oracle1、Java安装&#xff08;略&#xff09; 这一步网上有好多教程&#xff0c;大家可以自行安装。 验证Java是否安装成功&#x…

蓝桥杯第五届省赛JAVA真题----七对数字

今有7对数字&#xff1a;两个1&#xff0c;两个2&#xff0c;两个3&#xff0c;…两个7&#xff0c;把它们排成一行。 要求&#xff0c;两个1间有1个其它数字&#xff0c;两个2间有2个其它数字&#xff0c;以此类推&#xff0c;两个7之间有7个其它数字。如下就是一个符合要求的…

liunx-mysql-password重置(初始化)

1 #!/bin/bash #开头必写2 3 #数据库密码初始化4 pwd"admin"5 mysql -uroot <<EOF6 use mysql;7 UPDATE user SET passwordpassword(${pwd}) WHERE userroot;8 flush privileges;9 exit 10 EOF 转载于:https://www.cnblogs.com/CWQPHP/p/10484101.html

NCRE四级网络工程师考题详解----三级索引结构

在一个采用三级索引结构的UNIX 文件系统中&#xff0c;假设物理块大小为1KB&#xff0c;用64位表示一个物理块号。主索引表含有13 个块地址指针&#xff0c;其中前10 个直接指向盘块号&#xff0c;第11 个指向一级索引表&#xff0c;第12 个指向二级索引表&#xff0c;第13 个指…

laravel5单元测试

https://www.cnblogs.com/love-snow/articles/7641198.html转载于:https://www.cnblogs.com/anqiphp/p/10490767.html

NCRE四级网络工程师考题详解----目录分解法

在实现文件系统时&#xff0c;可采用"目录项分解法"加快文件目录检索速度。假设目录文件存放在磁盘上&#xff0c;每个盘块 512 字节。文件控制块有 64 字节&#xff0c;其中文件名占 8 字节&#xff0c;文件控制块分解后&#xff0c;第一部分占有 10 字节&#xff0…

自关联

设计省信息的表结构provinces idptitle 设计市信息的表结构citys idctitleproid citys表的proid表示城市所属的省&#xff0c;对应着provinces表的id值 问题&#xff1a; 能不能将两个表合成一张表呢&#xff1f; 思考&#xff1a; 观察两张表发现&#xff0c;citys表比provinc…

NCRE四级网络工程师考题详解----对等计算模型(P2P)

对等计算模型&#xff08;P2P&#xff09;: 在这种模型中&#xff0c;所有的计算机都可以当服务器。用中心服务器存放各个资源的目录。持续运转。以Napster为代表。 分布式非结构化P2P网络&#xff0c;没有中心服务器&#xff0c;采用泛洪方式&#xff0c;负载很重&#xff0c…

PyTorch 1.0 中文文档:常见问题解答

译者&#xff1a;冯宝宝 我的模型报告“cuda runtime error(2): out of memory” 正如错误消息所示&#xff0c;您的GPU显存已耗尽。由于经常在PyTorch中处理大量数据&#xff0c;因此小错误会迅速导致程序耗尽所有GPU资源; 幸运的是&#xff0c;这些情况下的修复通常很简单。这…

蓝桥杯第八届省赛JAVA真题----迷宫

标题&#xff1a;迷宫 X星球的一处迷宫游乐场建在某个小山坡上。 它是由10x10相互连通的小房间组成的。 房间的地板上写着一个很大的字母。 我们假设玩家是面朝上坡的方向站立&#xff0c;则&#xff1a; L表示走到左边的房间&#xff0c; R表示走到右边的房间&#xff0…