并发队列、线程池、锁

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,一经查实,立即删除!

相关文章

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

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

目标检测之YOLO V2 V3

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

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

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

掌握MySQL数据库这些优化技巧,事半功倍!

一个成熟的数据库架构并不是一开始设计就具备高可用、高伸缩等特性的&#xff0c;它是随着用户量的增加&#xff0c;基础架构才逐渐完善。这篇文章主要谈谈MySQL数据库在发展周期中所面临的问题及优化方案&#xff0c;暂且抛开前端应用不说&#xff0c;大致分为以下五个阶段&am…

蓝桥杯第八届省赛JAVA真题----方格分割

标题&#xff1a;方格分割 6x6的方格&#xff0c;沿着格子的边线剪开成两部分。 要求这两部分的形状完全相同。 如图&#xff1a;p1.png, p2.png, p3.png 就是可行的分割法。 试计算&#xff1a; 包括这3种分法在内&#xff0c;一共有多少种不同的分割方法。 注意&#xff…

Python猫荐书系列:文也深度学习,理也深度学习

最近出了两件大新闻&#xff0c;相信大家可能有所耳闻。 我来当个播报员&#xff0c;给大家转述一下&#xff1a; 1、中国队在第 11 界罗马尼亚数学大师赛&#xff08;RMM&#xff09;中无缘金牌。该项赛事是三大国际赛事之一&#xff0c;被誉为中学奥数的最高难度。其中一道题…

NCRE四级网络工程师考题详解----LRU与LFU的区别

最近最少使用页面置换算法&#xff08;LRU&#xff09;淘汰的是最长时间不使用的 最近最不常用页面置换算法&#xff08;LFU&#xff09;淘汰的是一定时间内未被使用的 我们假设有主存块为3&#xff0c;所需页面的走向为2 1 2 1 2 3 4 注意,当调页面4时会发生缺页中断 若按L…

移动端与PC端页面布局区别

视口 视口是移动设备上用来显示网页的区域&#xff0c;一般会比移动设备可视区域大&#xff0c;宽度可能是980px或者1024px&#xff0c;目的是为了显示下整个为PC端设计的网页&#xff0c;这样带来的后果是移动端会出现横向滚动条&#xff0c;为了避免这种情况&#xff0c;移动…

蓝桥杯历届试题----斐波那契(矩阵快速幂)

问题描述 斐波那契数列大家都非常熟悉。它的定义是&#xff1a; f(x) 1 …. (x1,2) f(x) f(x-1) f(x-2) …. (x>2) 对于给定的整数 n 和 m&#xff0c;我们希望求出&#xff1a; f(1) f(2) … f(n) 的值。但这个值可能非常大&#xff0c;所以我们把它对 f(m) 取模…

蓝桥杯第七届国赛JAVA真题----七星填数

七星填数 如图【图1.png】所示。 在七角星的14个节点上填入1~14 的数字&#xff0c;不重复&#xff0c;不遗漏。 要求每条直线上的四个数字之和必须相等。 图中已经给出了3个数字。 请计算其它位置要填充的数字&#xff0c;答案唯一。 填好后&#xff0c;请提交绿色节点的4个…

洛谷 P1219 ---- 八皇后

题目描述 检查一个如下的6 x 6的跳棋棋盘&#xff0c;有六个棋子被放置在棋盘上&#xff0c;使得每行、每列有且只有一个&#xff0c;每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子。 上面的布局可以用序列2 4 6 1 3 5来描述&#xff0c;第i个数字表示在第i行…

PXE(preboot execution environment):【网络】预启动执行环节:引导 live光盘 ubuntu livecd 16.4:成功...

default menu.c32label ubuntu menu label ubuntu (version 16.04) kernel ub1604/casper/vmlinuz append root/dev/nfs bootcasper netbootnfs nfsroot192.168.56.1:/e/_temp/ub1604 initrdub1604/casper/initrd quiet splash --# 关键是搭建nfs相应的服务器器并export解压后的…

Mysql 外键创建失败原因

最近在学习PHP&#xff0c;在用mysql创建表的时候总是创建不出来&#xff0c;我用的是Navicat做的&#xff0c;虽然建不出来外键&#xff0c;但是会创建出来一个索引&#xff0c;后来才明白&#xff0c;一定要有了对应的索引才能创建外键。

PHP实现简单注册登录系统

目录结构如下&#xff0c;其中function文件夹下包含两个函数文件&#xff0c;uploads文件夹用于存放上传的文件。 注&#xff1a;博主使用的是php5&#xff0c;使用php7的小伙伴运行报错的话有一部分原因是新的语法造成的&#xff0c;修改成新语法就可以了 html页面 登录页面…

蓝桥杯第九届省赛JAVA真题----螺旋折线

标题&#xff1a;螺旋折线 如图p1.pgn所示的螺旋折线经过平面上所有整点恰好一次。 对于整点(X, Y)&#xff0c;我们定义它到原点的距离dis(X, Y)是从原点到(X, Y)的螺旋折线段的长度。 例如dis(0, 1)3, dis(-2, -1)9 给出整点坐标(X, Y)&#xff0c;你能计算出dis(X, Y)…

JAVA-WEB开发环境和搭建

JAVA Web开发环境与搭建 一、下载安装JDK 1.配置jdk开发环境 JAVA_HOME 2.path 二、下载安装eclipse javaEE版本 三、安装部署tomcat 3.1、安装&#xff1a; 直接解压到指定目录即可。&#xff08;注&#xff1a;目录不要太深&#xff1b;目录不要有中文或空格&#xff09; 3.2…

Vue入门 ---- 简易留言板

##简述 初学vue&#xff0c;比Angular要简单易学一点&#xff0c;基本就是html代码json。这是第一个小的例子&#xff0c;用到了vue的几个常用方法&#xff0c;其中v-for的$index稍微有点迷惑&#xff0c;也影响了完成的速度&#xff0c;网上说是vue2.0已经取消了这种用法&…

VS Code编译Python

一、想要编译Python我们首先要安装python&#xff0c;进入官网下载python3&#xff08;不要下载python2.7&#xff0c;不就之后就不会再使用低版本的python了&#xff09; 二、配置环境变量 三、在VS Code中添加插件&#xff0c;记得添加完点击重新加载插件&#xff0c;或…

JAVA学习笔记_五JAVA开发中的WEB前端技术

css 字体属性: font-size font-style font-family font-weight font 设置字体font-family时&#xff0c;中文、英文字体设置时的顺序&#xff0c;英文在前中文在后&#xff1b; font-size&#xff1a;常用单位px&#xff0c;也用in、cm、mm、pt、pc&#xff0c;这几个简…

VS Code编译C/C++

C/C环境的配置要比python的复杂许多&#xff0c;好几个配置文件要写。 一、编译C/C的环境一般都是集成在我们的编辑器中的&#xff0c;如果电脑上有codeblock和dev c的读者可以去安装路径下找找MinGW文件夹&#xff0c;可以不用重复下载。而没有的读者则需要下载MinGW 二、配置…