1.线程和进程
- 进程:一个程序,QQ.exe Music.exe 程序的集合;
- 一个进程往往可以包含多个线程,至少包含一个!
- Java默认有几个线程? 2 个 mian、GC
- 线程:开了一个进程 Typora,写字,自动保存(线程负责的)
- 对于Java而言:Thread、Runnable、Callable
1.1 Java可以开启线程么?不可以
Java把线程映射到内核上去,让操作系统自己实现
是通过调用本地方法实现的
//进入new Thread().start();里面查看源码 public synchronized void start() { /** * This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state "NEW". */ if (threadStatus != 0) throw new IllegalThreadStateException(); /* Notify the group that this thread is about to be started * so that it can be added to the group's list of threads * and the group's unstarted count can be decremented. */ group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } } //本地方法底层的C++,Java无法直接操作硬件 private native void start0();
查看CPU的核数
public class TestThread { public static void main(String[] args) { //获取CPU的核数 //CPU 密集型,IO 密集型 System.out.println(Runtime.getRuntime().availableProcessors()); } }
并发编程的本质:充分利用CPU的资源
1.2 线程的状态
public enum State { // 新生 NEW,wait/sleep 区别 1、来自不同的类 wait => Object sleep => Thread 2、关于锁的释放 wait 会释放锁,sleep 睡觉了,抱着锁睡觉,不会释放! 3、使用的范围是不同的 wait . sleep 可以再任何地方睡 4、是否需要捕获异常 wait 不需要捕获异常 sleep 必须要捕获异常 3、Lock锁(重点) 传统 Synchronized // 运行 RUNNABLE, // 阻塞 BLOCKED, // 等待,死死地等 WAITING, // 超时等待 TIMED_WAITING, // 终止 TERMINATED; }
2 Lock锁
2.1 使用传统的synchronized
package com.snowdong; /** * 真正的多线程开发,降低耦合性 * 线程就是一个单独的资源类,没有任何附属的操作 * 1、属性,方法 */ public class SaleTicketDemo01 { public static void main(String[] args) { //并发:多线程操作同一个资源类,把资源类丢入线程 final Ticket ticket=new Ticket(); //@functionalInterface 函数式接口,jdk1.8 lambda表达式 (参数)->{代码} new Thread(()->{ for (int i=1;i<40;i++){ ticket.sale(); } },"Thread A").start(); new Thread(()->{ for (int i=1;i<40;i++){ ticket.sale(); } },"Thread B").start(); new Thread(()->{ for (int i=1;i<40;i++){ ticket.sale(); } },"Thread C").start(); } } //资源类,面向对象的编程OOP class Ticket{ //属性、方法 private int ticketNum=30; //卖票的方式 //synchronized 本质:队列,锁 public synchronized void sale(){ if(ticketNum>0){ System.out.println(Thread.currentThread().getName()+"买了第"+ticketNum+"票,剩余:"+(--ticketNum)); } } }
2.2 使用Lock接口
- 公平锁:十分公平:可以先来后到
- 非公平锁:十分不公平:可以插队 (默认)
还是买票的代码,使用Lock锁
package com.snowdong; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 真正的多线程开发,降低耦合性 * 线程就是一个单独的资源类,没有任何附属的操作 * 1、属性,方法 */ public class SaleTicketDemo02 { public static void main(String[] args) { //并发:多线程操作同一个资源类,把资源类丢入线程 Ticket2 ticket=new Ticket2(); //@functionalInterface 函数式接口,jdk1.8 lambda表达式 (参数)->{代码} new Thread(()->{ for (int i=1;i<40;i++) ticket.sale(); },"Thread A").start(); new Thread(()->{ for (int i=1;i<40;i++) ticket.sale(); },"Thread B").start(); new Thread(()->{ for (int i=1;i<40;i++) ticket.sale(); },"Thread C").start(); } } //Lock三部曲 //1. new ReentrantLock() //2. lock.lock();//加锁 //3. finally=>lock.unlock();//解锁 class Ticket2{ //属性、方法 private int ticketNum=30; Lock lock = new ReentrantLock(); public void sale(){ lock.lock();//加锁 try{ //业务代码 if(ticketNum>0){ System.out.println(Thread.currentThread().getName()+"买了第"+ticketNum+"票,剩余:"+(--ticketNum)); } }catch(Exception e){ e.printStackTrace(); }finally{ lock.unlock(); } } }
2.3 synchronized 和 Lock 的区别
- Synchronized 内置的Java关键字,Lock是一个Java类
- Synchronized 无法判断获取锁的状态,Lock可以判断是否获取到了锁
- synchronized无法得知是否获取锁成功,Lock则可以通过tryLock得知加锁是否成功
- Synchronized 会自动释放锁,Lock必须手动释放锁。如果不释放,死锁
- Synchronized 线程1(获得锁,阻塞),线程2(等待,傻傻的等);Lock锁就不一定会等待下去
- Lock可以通过tryLock得知加锁是否成功,然后判断是否等待
- Synchronized 可重入锁,不可以中断的,非公平;Lock锁,可重入锁,可以判断锁,公平和非公平自己设置
- Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码。
可重入锁:可以多次获取同一个锁,释放也要多次释放
锁是同步监视器,保证线程安全,是实现同步互斥的一种机制你
2.4 生产者和消费者问题
2.4.1 生产者和消费者问题的Synchronized版
package com.snowdong; /** * 线程之间的通讯问题:生产者和消费者,等待唤醒和通知唤醒 * 线程之间交替执行:线程A 线程B操作同一个变量,num=0 * A执行num+1 * B执行num-1 */ public class ProducerAndConsumer { public static void main(String[] args) { Data data=new Data(); new Thread(()->{ for(int i=0;i<20;i++){ try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"Thread A").start(); new Thread(()->{ for(int i=1;i<20;i++){ try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"Thread B").start(); new Thread(()->{ for(int i=1;i<20;i++){ try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"Thread C").start(); new Thread(()->{ for(int i=1;i<20;i++){ try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"Thread D").start(); } } //1. 判断等待,2.进行业务,3.通知唤醒 class Data{ private int number=0;//数字,资源类 //+1 public synchronized void increment() throws InterruptedException { if(number !=0){ //等待 this.wait(); } number++; System.out.println(Thread.currentThread().getName()+"===>"+number); //通知其他线程我+1完毕了 this.notifyAll(); } //-1 public synchronized void decrement() throws InterruptedException { if(number ==0){ //等待 this.wait(); } number--; System.out.println(Thread.currentThread().getName()+"===>"+number); //通知其他线程我-1完毕了 this.notifyAll(); } }
问题存在,A B C D四个线程的时候会出现错误,出现了num=2,3...的情况!虚假唤醒
解释:
- 拿两个加法线程A、B来说,比如A先执行(num=1),执行时调用了wait方法,那它会等待,此时会释放锁,那么线程B获得锁并且也会执行wait方法,两个加线程一起等待被唤醒。此时减线程中的某一个线程执行完毕(num=0)并且唤醒了这俩加线程,那么这俩加线程不会一起执行,其中A获取了锁并且加1,执行完毕之后B再执行(num=2)。
- 如果是if的话,那么A修改完num后,B不会再去判断num的值,直接会给num+1。如果是while的话,A执行完之后,B还会去判断num的值,因此就不会执行
2.4.2 虚假唤醒
- 一般而言线程调用wait()方法后,需要其他线程调用notify,notifyAll方法后,线程才会从wait方法中返回, 而虚假唤醒(spurious wakeup)是指线程通过其他方式,从wait方法中返回
- 当一个条件满足时,很多线程都被唤醒了,但是只有其中部分是有用的唤醒,其它的唤醒都是无用功
- 1.比如说买货,如果商品本来没有货物,突然进了一件商品,这是所有的线程都被唤醒了,但是只能一个人买,所以其他人都是假唤醒,获取不到对象的锁
* A thread can also wake up without being notified, interrupted, or * timing out, a so-called <i>spurious wakeup</i>. While this will rarely * occur in practice, applications must guard against it by testing for * the condition that should have caused the thread to be awakened, and * continuing to wait if the condition is not satisfied. In other words, * waits should always occur in loops, like this one: * <pre> * synchronized (obj) { * while (<condition does not hold>) * obj.wait(timeout); * ... // Perform action appropriate to condition * } * </pre> * (For more information on this topic, see Section 3.2.3 in Doug Lea's * "Concurrent Programming in Java (Second Edition)" (Addison-Wesley, * 2000), or Item 50 in Joshua Bloch's "Effective Java Programming * Language Guide" (Addison-Wesley, 2001). * * <p>If the current thread is {@linkplain java.lang.Thread#interrupt() * interrupted} by any thread before or while it is waiting, then an * {@code InterruptedException} is thrown. This exception is not * thrown until the lock status of this object has been restored as * described above.
if 改为 while 判断,就可以避免
wait方法可以分为三个操作:
(1)释放锁并阻塞
(2)等待条件cond发生
(3)获取通知后,竞争获取锁
wait方法一定是要获取到锁后,才会返回
2.4.3 JUC版的生产者和消费者问题
官方实现代码:
package com.snowdong; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 线程之间的通讯问题:生产者和消费者,等待唤醒和通知唤醒 * 线程之间交替执行:线程A 线程B操作同一个变量,num=0 * A执行num+1 * B执行num-1 */ public class ProducerAndConsumer { public static void main(String[] args) { Data data=new Data(); new Thread(()->{ for(int i=0;i<200;i++){ try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"Thread A").start(); new Thread(()->{ for(int i=1;i<200;i++){ try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"Thread B").start(); new Thread(()->{ for(int i=1;i<200;i++){ try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"Thread C").start(); new Thread(()->{ for(int i=1;i<200;i++){ try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"Thread D").start(); } } //1. 判断等待,2.进行业务,3.通知唤醒 class Data{//数字,资源类 private int number=0; Lock lock=new ReentrantLock(); Condition condition=lock.newCondition(); //condition.await();//等待 //condition.signalAll();//唤醒全部 public void increment() throws InterruptedException { lock.lock();//加锁 try{ //业务代码 while(number !=0){ condition.await();//等待 } number++; System.out.println(Thread.currentThread().getName()+"===>"+number); //通知其他线程我+1完毕了 condition.signalAll(); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } //-1 public void decrement() throws InterruptedException { lock.lock();//加锁 try{ //业务 while(number ==0){ condition.await();//等待 } number--; System.out.println(Thread.currentThread().getName()+"===>"+number); //通知其他线程我-1完毕了 condition.signalAll(); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } }
得到的运行结果是线程随机的同步执行,即随机的状态。
Condition:精准的通知和唤醒线程
package com.snowdong; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class TestLockCondition { public static void main(String[] args) { Data03 data03=new Data03(); new Thread(()-> { for(int i=1;i<10;i++) data03.printA(); },"Thread A").start(); new Thread(()-> { for(int i=1;i<10;i++) data03.printB(); },"Thread B").start(); new Thread(()-> { for(int i=1;i<10;i++) data03.printC(); },"Thread B").start(); } } class Data03{//资源类 private Lock lock=new ReentrantLock(); private Condition condition1=lock.newCondition(); private Condition condition2=lock.newCondition(); private Condition condition3=lock.newCondition(); private int number=1;//1A 2B 3C public void printA(){ lock.lock(); try{ //业务代码:先判断-》执行-》通知 while(number!=1) condition1.await();//等待条件condition1这个条件被唤醒 System.out.println(Thread.currentThread().getName()+"=>AAAAAAAA"); //唤醒,唤醒指定的人,B number=2; condition2.signal(); }catch (Exception e){ e.printStackTrace(); }finally{ lock.unlock(); } } public void printB(){ lock.lock(); try{ //业务代码:先判断-》执行-》通知 while(number!=2) condition2.await();//等待条件condition2这个条件被唤醒 System.out.println(Thread.currentThread().getName()+"=>BBBBBBB"); //唤醒,唤醒指定的人,C number=3; condition3.signal(); }catch (Exception e){ e.printStackTrace(); }finally{ lock.unlock(); } } public void printC(){ lock.lock(); try{ //业务代码:先判断-》执行-》通知 while(number!=3) condition3.await();//等待条件condition3这个条件被唤醒 System.out.println(Thread.currentThread().getName()+"=>CCCCCCCC"); //唤醒,唤醒指定的人,A number=1; condition1.signal(); }catch (Exception e){ e.printStackTrace(); }finally{ lock.unlock(); } } }
3. 8锁现象
3.1 同一个对象两个synchronized方法
代码如下:
package com.snowdong; import java.util.concurrent.TimeUnit; /** * 8锁就是关于锁的8个问题, * 1、标准情况下,两个线程那个先打印,发信息 or 打电话 * 2、sendSms延迟了4秒,谁先打印呢 */ public class TestLock01 { public static void main(String[] args) { Phone phone=new Phone(); //锁的存在 new Thread(()->{ phone.sendSms(); },"Thread A").start(); new Thread(()->{ phone.call(); },"Thread B").start(); } } class Phone{ //synchronized 锁的对象是方法的调用者! //两个方法用的同一个锁,谁先拿到谁就执行 public synchronized void sendSms() { /*try { //发送信息的方法,先给它睡眠4秒 TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); }*/ System.out.println("发信息"); } public synchronized void call(){ System.out.println("打电话"); } }
运行结果:(在sendSms方法中加不加延迟4秒,都是同一个结果)
发信息 打电话
原因解释:synchronized 锁的对象是方法的调用者。
- 当main主线程中依次执行时,线程A先拿到sendSms方法的调用者phone对象的锁,然后进入延迟;同时线程B去执行phone.call()方法时,发现对象phone已上锁,则进入phone的等待队列中,等待别的线程解锁。
3.2 同一个对象操作synchronized方法和普通方法
代码如下
package com.snowdong; import java.util.concurrent.TimeUnit; /** * 3、增加一个普通方法后!先执行发短信还是Hello? * */ public class TestLock02 { public static void main(String[] args) { Phone2 phone=new Phone2(); //锁的存在 new Thread(()->{ phone.sendSms(); },"Thread A").start(); try { //发送信息的方法,先给它睡眠2秒 TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone.hello(); },"Thread " + "Thread C").start(); } } class Phone2{ //synchronized 锁的对象是方法的调用者! //两个方法用的同一个锁,谁先拿到谁就执行 public synchronized void sendSms() { try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发信息"); } public synchronized void call(){ System.out.println("打电话"); } //这里没有锁!不是同步方法,不受锁的影响 public void hello(){ System.out.println("hello"); } }
运行结果:(在sendSms方法中加不加延迟4秒,都测试)
发现synchronized方法和同步方法没有锁的联系,随机打印(需要加延迟才能出现所有情况)
原因解释:synchronized同步方***锁调用对象,普通方法不需要同步,直接主线程运行就行。
3.3 两个对象操作synchronized方法
代码如下:
package com.snowdong; import java.util.concurrent.TimeUnit; /** * 4、两个对象,两个同步方法,发短信还是打电话? * */ public class TestLock02 { public static void main(String[] args) { //两个对象,两个调用者,两把锁! Phone2 phone1=new Phone2(); Phone2 phone2=new Phone2(); //锁的存在 new Thread(()->{ phone1.sendSms(); },"Thread A").start(); new Thread(()->{ phone2.call(); },"Thread B").start(); } } class Phone2{ //synchronized 锁的对象是方法的调用者! //两个方法用的同一个锁,谁先拿到谁就执行 public synchronized void sendSms() { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发信息"); } public synchronized void call(){ try { //发送信息的方法,先给它睡眠4秒 TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("打电话"); } //这里没有锁!不是同步方法,不受锁的影响 public void hello(){ System.out.println("hello"); } }
运行结果:(在两个方法中都加延迟,测出所有情况)
随机打印
原因解释:synchronized同步方***锁调用对象,两个线程中两个对象调用同步方法时会获取各自调用对象的锁,两把锁之间不关联。
3.4 同一个类的两个静态的同步方法,只有一个对象
代码如下:
package com.snowdong; import java.util.concurrent.TimeUnit; /** * 5、增加两个静态的同步方法,只有一个对象,先打印那个呢 */ public class TestLock03 { public static void main(String[] args) { Phone phone1=new Phone(); //锁的存在 new Thread(()->{ phone1.sendSms(); },"Thread A").start(); new Thread(()->{ phone1.call(); },"Thread B").start(); } } class Phone{ //synchronized 锁的对象是方法的调用者! //static 静态方法,类已加载就有,锁的是Class public static synchronized void sendSms() { try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发信息"); } public static synchronized void call(){ System.out.println("打电话"); } }
运行结果:(在sendSms方法中加上延迟)
发信息 打电话
原因解释:静态的同步方***锁住调用对象的Class
- 所以当下线程A通过对象访问类的静态同步方法时,会拿到Phone类的Class的锁,则线程B也是同样调用静态的同步方法因拿不到Class锁陷入等待。
3.5 同一个类的两个对象,类有两个静态的同步方法
代码如下:
package com.snowdong; import java.util.concurrent.TimeUnit; /** * 6、两个对象,增加两个静态的同步方法对比 */ public class TestLock03 { public static void main(String[] args) { //两个对象的Class类模板只有一个,static,锁的是Class Phone phone1=new Phone(); Phone phone2=new Phone(); //锁的存在 new Thread(()->{ phone1.sendSms(); },"Thread A").start(); new Thread(()->{ phone2.call(); },"Thread B").start(); } } class Phone{ //synchronized 锁的对象是方法的调用者! //static 静态方法,类已加载就有,锁的是Class public static synchronized void sendSms() { try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发信息"); } public static synchronized void call(){ System.out.println("打电话"); } }
运行结果:同上
发信息 打电话
原因解释:静态的同步方***锁住调用对象的Class
- 所以当下线程A通过对象访问类的静态同步方法时,会拿到Phone1类的Class的锁,因为phone1和phone2都是类Phone的实例,所以其对应的Class类模板是同一个,则当线程B通过对象phone2调用类的静态的同步方法,因拿不到Class锁陷入等待。
- 推知:可能跟类的静态方法加载(在类实例化之前就加载了),存储位置(类静态方法跟对象实例的普通方法,不知道存储位置是不是都在方法区)
3.6 一个对象,一个静态的同步方法,一个普通同步方法
代码如下:
package com.snowdong; import java.util.concurrent.TimeUnit; /** * 7、两个对象,增加两个静态的同步方法对比 */ public class TestLock03 { public static void main(String[] args) { //两个对象的Class类模板只有一个,static,锁的是Class Phone phone1=new Phone(); //线程A 通过phone1调用类的静态同步方法 new Thread(()->{ phone1.sendSms(); },"Thread A").start(); /* //捕获 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }*/ //线程B 通过phone1调用对象的普通同步方法 new Thread(()->{ phone1.call(); },"Thread B").start(); } } class Phone{ //synchronized 锁的对象是方法的调用者! //static 静态方法,类已加载就有,锁的是Class public static synchronized void sendSms() { try { //发送信息的方法,先给它睡眠4秒 TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发信息"); } public synchronized void call(){ System.out.println("打电话"); } }
运行结果:是随机的
解释:普通同步方法锁的是调用者对象,静态同步方锁的是Class对象。两个是锁的是不同的对象,两把锁。
3.7 两个对象,一个静态的同步方法,一个普通同步方法
代码如下:
package com.snowdong; import java.util.concurrent.TimeUnit; /** * 8、两个对象,一个静态的同步方法,一个普通的同步方法 */ public class TestLock03 { public static void main(String[] args) { //两个对象的Class类模板只有一个,static,锁的是Class Phone phone1=new Phone(); Phone phone2=new Phone(); //锁的存在 new Thread(()->{ phone1.sendSms(); },"Thread A").start(); /* //捕获 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }*/ new Thread(()->{ phone2.call(); },"Thread B").start(); } } class Phone{ //synchronized 锁的对象是方法的调用者! //static 静态方法,类已加载就有,锁的是Class public static synchronized void sendSms() { try { //发送信息的方法,先给它睡眠4秒 TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发信息"); } public synchronized void call(){ System.out.println("打电话"); } }
运行结果:随机结果
解释:原理同上。
4. 集合类不安全
4.1 List不安全
package com.snowdong; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; //线程不安全指的是集合内部的多个方法是不能保证原子性操作的,(同步操作?) public class TestList { public static void main(String[] args) { //ArrayList集合在多线程下会出现java.util.ConcurrentModificationException 并发修改异常 //List<String> list=new ArrayList<>(); /**解决方案 * List是可重复有序集合 * 1、List<String> list=new Vector<>(); * 2、List<String> list= Collections.synchronizedList(new ArrayList<>()); * 3、List<String> list=new CopyOnWriteArrayList<>(); */ //写入时复制,COW计算机程序设计领域的一种优化策略 //在多个线程调用时,list读取无误,写入时有可能会覆盖以前新add的值,导致错 //读写分离,即先复制一份,写的时候判断现在值跟复制值是否一致,一致则写入。 //源码中CopyOnWriteArrayList().add()使用了Lock锁 List<String> list=new CopyOnWriteArrayList<>(); for(int i=1;i<=30;i++){ new Thread(()->{ list.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(list); }).start(); } } }
4.2 Set不安全
Set是不可重复,无序集合
package com.snowdong; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.UUID; import java.util.concurrent.CopyOnWriteArraySet; /** * 同理可证 : ConcurrentModificationException * //1、Set<String> set = Collections.synchronizedSet(new HashSet<>()); */ public class TestSet { public static void main(String[] args) { // Set<String> set = new HashSet<>();//不安全 //使用Collections工具类 // Set<String> set = Collections.synchronizedSet(new HashSet<>()); Set<String> set = new CopyOnWriteArraySet<>(); for (int i = 1; i <=30 ; i++) { new Thread(()->{ set.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(set); },String.valueOf(i)).start(); } } }
hashSet的底层实现:
public HashSet() { map = new HashMap<>(); } //add set 本质就是map key是无法重复的 public boolean add(E e) { return map.put(e, PRESENT)==null; } private static final Object PRESENT = new Object();//固定值
4.3 Map不安全
Map基本要素:初始容量,最大容量,加载因子
package com.snowdong; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; public class TestMap { public static void main(String[] args) { /* //出现ConcurrentModificationException,默认new HashMap<>(16,0.75); Map<String,String> map=new HashMap<>();*/ //使用Collections工具类 Map<String,String> map= Collections.synchronizedMap(new HashMap<>()); //使用ConcurrentHashMap //Map<String,String> map=new ConcurrentHashMap<>(); for(int i=1;i<=30;i++){ new Thread(()->{ map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5)); System.out.println(map); },String.valueOf(i)).start(); } } }
待定专题补充ConcurrentHashMap的原理
4.4 Callable
- 可以有返回值
- 可以抛出异常
- 方法不同,run()/call()
具体实现方式代码如下:
package com.snowdong; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class TestCallable { public static void main(String[] args) throws ExecutionException, InterruptedException { //线程开启的方式 // new Thread(new Runnable()).start(); // new Thread(new FutureTask<V>()).start();//其中futureTask是Runnable接口的实现类 // new Thread(new FutureTask<V>(new Callable())).start(); MyThread myThread=new MyThread(); //FutureTask构造函数接受Callable接口 FutureTask futureTask=new FutureTask(myThread);//适配类 new Thread(futureTask,"Thread A").start(); new Thread(futureTask,"Thread B").start();//结果会被缓存,效率高 //这个get方法可能会产生阻塞,把它放到最后 //或者使用异步通信来处理 Integer integer= (Integer) futureTask.get(); System.out.println(integer); } } class MyThread implements Callable<Integer> { @Override public Integer call() throws Exception { System.out.println(Thread.currentThread().getName()+" 调用了Call"); return 9527; } }
具体来说:
- 线程中任务执行,同一个任务执行会缓存结果
- 结果可能需要等待,会阻塞
5 常用的辅助类
5.1 CountDownLatch减法器
package com.snowdong; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; public class TestCountDownLatch { public static void main(String[] args) throws InterruptedException { //设置总数为6的计数器,必须要执行任务的时候,在使用 CountDownLatch countDownLatch=new CountDownLatch(6); for (int i = 0; i < 6; i++) { new Thread(()->{ System.out.println(Thread.currentThread().getName()+" go out"); countDownLatch.countDown();//数量-1 },String.valueOf(i)).start(); } //注释或者不注释下行,观看运行结果 countDownLatch.await();//等待计数器归零,然后再向下执行 System.out.println("Close door"); } }
说明:
- countDownLatch.countDown();//数量-1
- countDownLatch.await();//等待计数器归零,然后再向下执行
- 每次有线程调用 countDown() 数量-1,假设计数器变为0,countDownLatch.await() 就会被唤醒,继续
执行!
5.2 CyclicBarrier加法器
可以简单理解为:加法计算器
package com.snowdong; import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput; import org.w3c.dom.ls.LSOutput; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class TestCyclicBarrier { public static void main(String[] args) { /** * 一个案例说明:集齐7颗龙珠召唤神龙 */ //召唤神龙的线程 CyclicBarrier cyclicBarrier=new CyclicBarrier(7,()->{ System.out.println("召唤神龙成功"); }); for (int i = 0; i <7 ; i++) { final int temp=i; //lambda能操作到i么? // 不能,i是for循环类的局部变量,lambda里是局部内部内了,需传参数 new Thread(()->{ System.out.println(Thread.currentThread().getName()+"收集"+temp+"个龙珠"); try { cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } }).start(); } } }
说明:CyclicBarrier 的构造函数,在指定数目线程await发生后,执行Runnable接口实例
/** * Creates a new {@code CyclicBarrier} that will trip when the * given number of parties (threads) are waiting upon it, and which * will execute the given barrier action when the barrier is tripped, * performed by the last thread entering the barrier. * * @param parties the number of threads that must invoke {@link #await} * before the barrier is tripped * @param barrierAction the command to execute when the barrier is * tripped, or {@code null} if there is no action * @throws IllegalArgumentException if {@code parties} is less than 1 */ public CyclicBarrier(int parties, Runnable barrierAction) { if (parties <= 0) throw new IllegalArgumentException(); this.parties = parties; this.count = parties; this.barrierCommand = barrierAction; }
5.3 Semaphore信号量
抢车位的案例代码:
package com.snowdong; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; public class TestSemaphore { public static void main(String[] args) { //线程数量:停车位,限流.定义了一个3组信号量(许可证) Semaphore semaphore=new Semaphore(3); for (int i = 0; i < 6; i++) { new Thread(()->{ //获取许可证 try { semaphore.acquire(); System.out.println(Thread.currentThread().getName()+"抢到了车位"); TimeUnit.SECONDS.sleep(2); System.out.println(Thread.currentThread().getName()+"离开了车位"); } catch (InterruptedException e) { e.printStackTrace(); }finally { semaphore.release(); } }).start(); } } }
运行结果:
Thread-0抢到了车位 Thread-1抢到了车位 Thread-2抢到了车位 Thread-2离开了车位 Thread-1离开了车位 Thread-0离开了车位 Thread-4抢到了车位 Thread-3抢到了车位 Thread-5抢到了车位 Thread-3离开了车位 Thread-5离开了车位 Thread-4离开了车位
说明:
- semaphore.acquire();请求获得许可证。假设如果已满,则等待,直到被释放
- semaphore.release();释放许可证。会将当前的许可证(信号量)+1,然后唤醒等待的线程
- 主要用于,多个共享资源互斥的使用!并发限流,控制最大的线程数