1.ReentrantLock理解我们之前讨论的可重入锁,翻译成英文就是ReentrantLock,大多数情况下这个英文单词应该理解为这个锁特性,但是在少数情况,应该理解为类类似于synchronized定位,用来实现互斥,保证线程安全。同时,这个锁是可重入的。公共类测试{静态类计数器{publicintcount=0;publicvoidincrease(){count++;}}publicstaticvoidmain(String[]args){Countercounter=newCounter();Threadt1=newThread(){@Overridepublicvoidrun(){for(inti=0;i<50000;i++){counter.increase();}}}};Threadt2=newThread(){@Overridepublicvoidrun(){for(inti=0;i<50000;i++){counter.increase();}}}};t1.开始();t2.开始();尝试{t1.join();t2.join();}赶上(InterruptedExceptione){e.printStackTrace();}System.out.println(counter.count);}}用法下面看一段代码实现两个线程分别累加一个变量count:经过前面的学习,我们认为这个方法打印count是线程不安全的,不会每次都准确打印10000:我们的解决方案之前学过的是使用synchronized来保证线程安全,代码如下:staticclassCounter{publicintcount=0;synchronizedpublicvoidincrease(){count++;但是此时我们可以通过创建对象ReentrantLock来锁定它,完整代码如下:importjava.util.concurrent.locks.ReentrantLock;公共类测试{静态类计数器{公共整数计数;publicReentrantLocklocker=newReentrantLock();publicvoidincrease(){locker.lock();计数++;储物柜.解锁();}}publicstaticvoidmain(String[]args){Countercounter=newCounter();Threadt1=newThread(){@Overridepublicvoidrun(){for(inti=0;i<50000;i++){counter.increase();}}}};Threadt2=newThread(){@Overridepublicvoidrun(){for(inti=0;i<50000;i++){counter.increase();}}}};t1.开始();t2。开始();尝试{t1.join();t2.join();}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(counter.count);}}与synchronized的区别在于,它还可以用synchronized实现加锁功能。两者有什么区别?ReentrantLock将锁定和解锁分为两种方法。确实存在忘记解锁的风险,但是可以让代码更加灵活。您可以将锁定和解锁代码分别放入两个方法中。synchronized申请锁失败这时候代码会等死,ReentrantLock可以通过trylock方法等待一段时间后放弃,不会浪费时间。Synchronized是非公平锁,ReentrantLock默认是非公平锁。但是可以通过构造方法传入一个真值来开启公平锁模式。ReentrantLock有更强大的唤醒机制。Synchronized通过Object的wait/notify方法实现等待和唤醒过程。每次唤醒都是一个随机等待的线程。而ReentrantLock配合Condition类实现wait-wake,可以更精确的控制和唤醒指定的线程。综上所述,大多数情况下,使用synchronized就够了。当锁竞争激烈时,使用ReentrantLock并使用trylock方法可以更灵活地控制加锁行为,而不是死等。如果需要使用公平锁,使用ReentrantLock2。原子类的理解并不一定需要加锁来保证线程安全。当然也可以使用原子类。从java1.5开始,jdk提供了java.util.concurrent.atomic包,其中包含了一系列原子操作类,提供了一种易用、高性能、线程安全的更新变量的方式。内部通常以CAS方式实现,所以性能通常比锁i++高很多。具体使用方法如下(上例)publicAtomicIntegercount=newAtomicInteger(0);publicvoidincrease(){count.getAndIncrement();}常用原子类AtomicBooleanAtomicIntegerAtomicIntegerArrayAtomicLongAtomicReferenceAtomicStampedReference常用方法以AtomicInteger为例,常用方法有addAndGet(intdelta);相当于i+=delta;decrementAndGet();相当于–i;getAndDecrement();相当于i–;incrementAndGet();等价于++i;getAndIncrement();相当于i++;3、为什么要引入线程池来解决并发编程解决方案一般依赖多进程,但是进程开销的资源非常大,所以我们进一步引入了Multithreading。虽然创建销毁线程看起来比创建销毁进程轻,但是在频繁创建销毁线程的情况下还是比较低效的。线程池就是为了解决这个问题。如果一个线程不再被使用,这个线程实际上并没有被释放,而是被放入一个“池”中。当我们需要使用多线程时,只需从之前创建的池中取出一个线程即可。当我们不用的时候,只要把线程放回池中即可。引入线程池的好处当我们不使用线程池时,频繁创建或销毁线程涉及到在用户态和内核态之间来回切换。从用户态切换到内核态会创建相应的PCB(进程控制块,英文是ProcessingControlBlock),会消耗大量的系统资源,效率会比较低。当我们引入线程池时,相当于只在用户态完成各种操作,这样代码执行效率和系统开销都会得到极大的优化。创建线程池的方法(1)ThreadPoolExecutor是使用Java标准库中的ThreadPoolExecutor方法创建的,但是要注意各个参数所代表的含义使用起来比较复杂。构造方法2)使用Executors类创建Executors,相当于一个工厂类。通过这个工厂类中的一些方法,可以创建不同风格的线程池实例。一些方法Executors.newFixedThreadPool:创建一个固定大小的线程池Executors.newCachedThreadPool:创建一个可缓存的线程池。如果线程数超过处理要求,缓存会在一段时间后回收。如果线程数不够,就会创建一个新的线程。Executors.newSingleThreadExecutor:创建一个只包含一个线程号的线程池,可以保证先进先出的执行顺序。Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池(放置的任务可以稍后执行)Executors.newSingleThreadScheduledExecutor:创建一个单线程的线程池,可以执行延迟任务使用示例:importjava.util.concurrent。ExecutorService;导入java.util.concurrent.Executors;公共类测试{publicstaticvoidmain(String[]args){ExecutorServiceservice=Executors.newFixedThreadPool(10);for(inti=0;i<20;i++){service.submit(newRunnable(){@Overridepublicvoidrun(){System.out.println("hello");}});}}}四。信号量信号量定义信号量一般用来表示可用资源的数量,相当于一个计数器,可以比作生活中停车场标志牌上显示的剩余车位数量。有车开进来,相当于申请了一个可用资源,可用车位为-1(这就叫信号量的P操作)。当一辆车出来,相当于释放了一个可用资源,可用车位为-1+1(这个V操作叫信号量)如果计数器的值已经是0了,你再去申请资源,它会阻塞并等待直到其他线程释放资源(计数器的值大于或等于0)来创建使用信号量时,可以给出一个初始值(可用资源的数量)。当可用资源数量用完后,会阻塞等待,保证线程安全。如果将信号量的初始值设置为1,则计数器的值只能为0或1。此时,这种信号量称为二进制信号量。它类似于锁的功能。它有一个锁状态(不能申请资源)和一个解锁状态(可以申请资源)。示例用法下面我们创建15个线程,假设初始资源量为3,然后尝试申请资源(acquire),申请资源后休眠1秒,然后释放资源(release):mportjava.util.concurrent。信号;公共类测试{publicstaticvoidmain(String[]args){信号量semaphore=newSemaphore(3);Runnablerunnable=newRunnable(){@Overridepublicvoidrun(){try{System.out.println("准备申请资源");信号量.acquire();System.out.println("资源申请成功");//申请资源后休眠1秒Thread.sleep(1000);信号量.release();//释放资源System.out.println("资源已释放");}catch(InterruptedExceptione){e.printStackTrace();}}};//创建15个线程,让这15个线程分别尝试申请资源for(inti=0;i<15;i++){Threadt=newThread(runnable);t.开始();}}}可以看出,由于资源个数为3,所以前3个线程申请资源后很容易成功,后面的线程就没有资源可以申请了。只能等前3个线程都释放完资源再去申请信号量,相当于锁的升级版。锁只能控制一种资源的有无,而信号量可以控制多种资源的有无。5.CountDownLatch用来理解等待N个任务同时结束就好比是百米赛跑。只有当所有跑者就位,哨声响起后,他们才能同时开始跑,并在所有跑者通过终点线时公布成绩。用法我们同时创建10个线程开始执行一个任务,每个任务执行完后记录,调用latch.countDown()方法。CountDownLatch内部的计数器同时递减。创建另一个主线程,它使用latch.await();阻塞等待所有任务执行完毕(此时计数器为0)用法示例importjava.util.concurrent.CountDownLatch;公共类测试{publicstaticvoidmain(String[]args)throwsInterruptedException{CountDownLatchlatch=newCountDownLatch(10);Runnablerunnable=newRunnable(){@Overridepublicvoidrun(){System.out.println("任务开始");try{Thread.sleep((long)(Math.random()*10000));//生成随机数}catch(InterruptedExceptione){e.printStackTrace();}latch.countDown();System.out.println("任务完成!");}};for(inti=0;i<10;i++){Threadt=newThread(runnable);t.开始();}latch.await();System.out.println("所有任务结束");}}本文转载自博主“春风~十一年”的原创文章GoodMaihttps://www.goodmai.com/
