警醒自己不断学习和成长
正式学习的准备工作
JUC并发编程
1.什么是JUC
JUC就是java.util .concurrent工具包的简称。这是一个处理线程的工具包,JDK 1.5开始出现的。
2.进程和线程回顾
什么是进程和线程?
进程:是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用,例如:QQ.exe
线程:是拥有资源和独立运行的最小单位,也是程序执行的最小单位.
一个进程可以包含多个线程,一个进程至少有一个线程!Java程序至少有两个线程:GC,main
并发、并行
并发:多个线程操作同一个资源,交替执行的过程!
并行:多个线程同时执行!只有在多核CPU下才能完成!
使用多线程或者并发编程的目的:提高效率,让CPU一直工作,达到最高处理性能!
线程有几种状态
线程有6种状态
public enum State { // java能够创建线程吗? 不能! // 新建 NEW, // 运行 RUNNABLE, // 阻塞 BLOCKED, // 等待 WAITING, // 延时等待 TIMED_WAITING, // 终止! TERMINATED;}
Java不能够创建线程!!!(线程是操作系统的资源)
线程的状态切换
wait/sleep的区别
1、类不同!
wait——Object类 sleep——Thread类在juc编程中,线程休眠怎么实现? //时间单位TimeUnit.SECONDS.sleep(3);?
2、会不会释放资源
sleep:抱着锁睡得,不会释放锁;wait会释放锁。
3、使用的范围是不同的
wait和notify是一组,一般在线程通信的时候使用;
sleep就是一个单独的方法,在哪里都可以调用。
4、关于异常
sleep需要捕获异常
3.Lock锁
synchronized 传统的方式
代码:
package com.rudd.demo;?import java.util.concurrent.TimeUnit;?/** * 传统的synchronized * 企业级开发: * 1.架构:高内聚,低耦合 * 2.套路:线程操作资源类,资源类是单独的。 */public class Test1 {? public static void main(String[] args) { //1.新建资源类 Ticket ticket = new Ticket(); //2.线程操纵资源类 new Thread(()->{ for (int i = 1; i < 41; i++) { ticket.saleTicket(); } },"A").start(); new Thread(()->{ for (int i = 1; i < 41; i++) { ticket.saleTicket(); } },"B").start(); new Thread(()->{ for (int i = 1; i < 41; i++) { ticket.saleTicket(); } },"C").start(); }}//单独的资源类应该只有:属性和方法class Ticket{? private int number = 30;? //synchronized 关键字 public synchronized void saleTicket(){ if(number>0){ System.out.println(Thread.currentThread().getName()+"卖出第"+(number--)+"票,还剩:"+number); } }}
Lock锁
代码:
package com.rudd.demo;?import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;?public class Test02 {? public static void main(String[] args) { //1.新建资源类 Ticket2 ticket = new Ticket2(); //2.线程操纵资源类,所有的函数式接口都可以用lambda表达式简化 //lambda表达式:(参数)->{具体的代码} new Thread(()->{ for (int i = 1; i < 41; i++) { ticket.saleTicket(); } },"A").start(); new Thread(()->{ for (int i = 1; i < 41; i++) { ticket.saleTicket(); } },"B").start(); new Thread(()->{ for (int i = 1; i < 41; i++) { ticket.saleTicket(); } },"C").start(); }}?class Ticket2{ /* * 使用Lock,它是一个对象 * ReentrantLock 可重入锁 * ReentrantLock 默认是非公平锁 * 非公平锁:不公平(插队,后面的线程可以插队) * 公平锁:公平(只能排队,后面的线程无法插队) */ private Lock lock = new ReentrantLock(); private int number = 30;? public void saleTicket(){ //加锁 lock.lock(); try { if(number>0){ System.out.println(Thread.currentThread().getName()+"卖出第"+(number--)+"票,还剩:"+number); } }catch (Exception e){ e.printStackTrace(); }finally { //解锁 lock.unlock(); } }}
synchronized和Lock的区别
- synchronized是一个关键字;Lock是一个对象
- synchronized无法尝试获取锁,Lock可以尝试获取锁,会进行判断;
- synchronized会自动释放锁(a线程执行完毕,b如果异常了,也会释放锁),Lock锁是手动释放锁,如果不释放就会死锁。
- synchronizedsynchronizedsynchronized(线程A(获得锁,如果阻塞),线程B(等待,一直等待));Lock可以尝试获取锁,失败了之后就放弃。
- synchronized一定是非公平的锁,但是Lock锁可以是公平的,通过参数设置;
- 代码量特别大的时候,我们一般使用Lock实现精准控制,synchronized适合代码量较小的同步问题。
4.生产者和消费者
线程和线程之间本来是不能通信的,但是有时候我们需要线程之间可以协调操作:等待唤醒机制
synchronized
package com.rudd.demo;/* synchronized? 目的:有两个线程:A B,还有一个初始值为0的变量 实现两个线程交替执行,对该变量进行+1和-1操作,交替10次。? 传统的wait和notify方法不能实现精准唤醒。 */public class Test03 { public static void main(String[] args){ Data data = new Data(); //负责+1操作 new Thread(()->{ for (int i = 0; i < 10; i++) { try { data.increment(); }catch (Exception e){ e.printStackTrace(); } } },"A").start(); //负责-1操作 new Thread(()->{ for (int i = 0; i < 10; i++) { try { data.decrement(); }catch (Exception e){ e.printStackTrace(); } } },"B").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { data.increment(); }catch (Exception e){ e.printStackTrace(); } } },"C").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { data.decrement(); }catch (Exception e){ e.printStackTrace(); } } },"D").start(); }}?//资源类//线程之间的通信:判断 执行 通知class Data{ private int number = 0;? public synchronized void increment() throws Exception{ //不要用if,会导致需要唤醒 while(number!=0){//1.判断是否需要等待 this.wait(); } number++;//2.执行 System.out.println(Thread.currentThread().getName()+":::"+number); this.notifyAll();//3.通知:唤醒所有线程 }? public synchronized void decrement() throws Exception{ //不要用if,会导致需要唤醒 while(number!=1){//1.判断是否需要等待 this.wait(); } number--;//2.执行 System.out.println(Thread.currentThread().getName()+":::"+number); this.notifyAll();//3.通知:唤醒所有线程 }}
用if判断可能会导致虚假唤醒
Lock和Condition实现精准唤醒
传统的监视器:Object,JUC的监视器:Condition
package com.rudd.demo;?import sun.awt.windows.ThemeReader;?import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;?/* 实现线程交替执行: 主要的实现目标:精准的唤醒线程! 三个线程:A,B,C 三个方法:A p5,B p10,C p15一次循环。? */public class Test04 { public static void main(String[] args) { Data2 data = new Data2(); new Thread(() -> { for (int i = 0; i < 10; i++) { data.print5(); } }, "A").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { data.print10(); } }, "B").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { data.print15(); } }, "C").start(); }}??class Data2 { Lock lock = new ReentrantLock(); Condition condition1 = lock.newCondition(); Condition condition2 = lock.newCondition(); Condition condition3 = lock.newCondition(); private int number = 1;//1-A线程;2—B线程;3—C线程? public void print5() { lock.lock(); try { //1.判断 while (number != 1) { condition1.await();//等待 } //2.执行 for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "\t" + i); } //3.通知第二个线程干活 number = 2; condition2.signal();//唤醒 } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } }? public void print10() { lock.lock(); try { //1.判断 while (number != 2) { condition2.await(); } //2.执行 for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + "\t" + i); } //3.通知第三个线程干活 number = 3; condition3.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } }? public void print15() { lock.lock(); try { //1.判断 while (number != 3) { condition3.await(); } //2.执行 for (int i = 0; i < 15; i++) { System.out.println(Thread.currentThread().getName() + "\t" + i); } //3.通知第一个线程干活 number = 1; condition1.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } }}
5.八锁现象,搞懂锁
synchronized实现同步的基础:
1、普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁
2、静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
3、同步代码块,锁是括号里面的对象,对给定对象加锁,进入同步代码块前要获得给定对象的锁。
6.集合类不安全
List
安全的List解决方案:
List<String> list1 = new Vector<String>();List<String> list2 = Collections.synchronizedList(new ArrayList<String>());/* CopyOnWrite:写入时复制(COW思想) CopyOnWriteArrayList,是一个写入时复制的容器,它是如何工作的呢?简单来说,就是平时查询的时候,都不需要加锁,随便访问,只有在写入/删除的时候,才会从原来的数据复制一个副本出来,然后修改这个副本,最后把原数据替换成当前的副本。修改操作的同时,读操作不会被阻塞,而是继续读取旧的数据。*/List<String> list3 = new CopyOnWriteArrayList<String>();//class CopyOnWriteArrayList;public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; setArray(newElements); return true; } finally { lock.unlock(); }}?
Set
安全的Set解决方案:
Set<String> set1 = Collections.synchronizedSet(new HashSet<String>());Set<String> set2 = new CopyOnWriteArraySet<String>();
HashSet的底层就是:HashMap
Map
安全的Map解决方案:
Map<String,String> map1 = new Hashtable<String,String>(); Map<String,String> map2 = Collections.synchronizedMap(new HashMap<String,String>()); Map<String,String> map3 = new ConcurrentHashMap<String,String>();
HashMap的底层数据结构:链表+红黑树。
原文地址:https://www.cnblogs.com/tianxc/p/12398206.html