栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JUC 高并发编程

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JUC 高并发编程

目录
  • 1 JUC概述
    • JUC概念
    • 进程、线程概念
      • 1.1 进程 & 线程
      • 1.2 线程状态
      • 1.3 sleep & wait
      • 1.4 并发 & 并行
      • 1.5 管程
      • 1.6 用户线程 & 守护线程
  • 2 Lock接口
    • 2.1 synchronized、Lock 对比、介绍
    • 2.2 eg.卖票
  • 3 线程间通信
  • 4 线程间定制化通信
  • 5 集合的线程安全
    • 5.1 ArrayList线程不安全
    • 5.2 HashSet线程不安全
    • 5.3 HashMap线程不安全
  • 6 多线程锁
    • 6.0 锁的范围
    • 6.1 非公平锁 & 公平锁
    • 6.2 可重入锁
    • 6.3 死锁
  • 7 Callable接口

1 JUC概述 JUC概念

处理线程的工具包 java.util .concurrent 的简称

Java8API地址

进程、线程概念 1.1 进程 & 线程
进程线程
系统中正在运行的一个应用程序 / 运行的程序系统分配处理器时间资源的基本单元 / 进程内独立执行的一个单元执行流
资源分配和调度的最小单位程序执行的最小单位
一个进程可并发多个线程每条线程并行执行不同的任务
1.2 线程状态

Thread.State 线程状态枚举类

线程状态转换图

参考

1.3 sleep & wait
sleepwait
所属类Thread静态方法Object方法 任何实例对象都可调用
时间指定时间可指定(限时等待)可不指定(无限等待)
释放锁释放CPU执行权 不释放同步锁释放CPU执行权、同步锁
使用的地方任何地方都能使用只能在同步代码方法/块中使用(调用前提:当前线程占有锁->代码要在 synchronized 中)
捕获异常必须捕获异常捕获/抛出异常

同:可被 interrupted 方法中断;在哪里睡,就在哪里醒

1.4 并发 & 并行

串行:所有任务按先后顺序执行
并行:同一时刻多个线程在访问同一个资源
并发(concurrent):多项工作一起执行,之后再汇总

1.5 管程

管程-Monitor监视器(OS) 锁(Java):是一种同步机制,保证同一时间,只有一个线程访问被保护数据/代码
JVM同步基于进入(加锁)和退出(解锁),使用管程对象实现(对临界区加锁和解锁)

1.6 用户线程 & 守护线程

用户线程:自定义线程

守护线程:如垃圾回收(后台执行)

2 Lock接口 2.1 synchronized、Lock 对比、介绍
synchronizedLock
存在层次Java关键字 内置特性 托管给ivm执行接口 非Java内置 是Java写的控制锁的代码
释放锁异常->自动unlock(JVM会让线程释放锁)必须在finally中手动unlock,否则死锁
获取锁不可响应中断,会一直等待锁释放等锁线程可响应中断(interrupt)
锁状态无法判断可用trylock()判断是否成功获取锁
锁类型可重入 非公平可重入 非公平(默认)/公平(传参true)
性能竞争资源不激烈,二者差不多(适合少量同步)竞争资源激烈,性能优(提高多线程读效率)
优点使用简单(JDK1.6后优化:适应自旋锁,锁消除,锁粗化,轻量级锁,偏向锁)灵活
底层CPU悲观锁机制-线程获得的是独占锁乐观锁 -CAS(Compare and Swap)实现
调度机制Object wait() notifyAll() notify()-this.notify(),JVM随机唤醒某个等待的线程Condition await() signalAll() signal()-ci.signal(),选择性通知ci

synchronized隐式(内置)锁 & Lock显式锁

  • synchronized好用,简单,性能不差
  • 没有使用到Lock显式锁的特性就不要使用Lock锁了

Lock.java

package java.util.concurrent.locks;
import java.util.concurrent.TimeUnit;
public interface Lock {
	void lock();
	void lockInterruptibly() throws InterruptedException;
	boolean tryLock();
	boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
	void unlock();
	Condition newCondition();
}

Lock同步形式

class X { 
	private final ReentrantLock lock = new ReentrantLock(); 
	// ... 
	public void m() { 
		lock.lock(); 
		// block until condition holds 
		try { 
			// ... method body 
		} finally { 
			lock.unlock(); //Lock发生异常时,若不主动释放锁,会死锁->finally保证不管有无异常都会释放锁
		} 
	} 
} 
2.2 eg.卖票

⭐多线程编程步骤(高内聚 低耦合)

  1. 创建资源类 定义属性、方法
  2. 创建多个线程 调用资源类方法

synchronized实现卖票

//1.创建资源类 定义属性、方法
class Ticket {
    //票数
    private int number=30;
    //卖票
    public synchronized void sale() {   //synchronized自动上锁 去掉关键字 输出全是A卖出的
        if(number>0) {
            System.out.println(Thread.currentThread().getName()+"卖出"+number--+"号票");
        }
    }
}

public class Test1_SaleTicket {
    //2.创建多个线程 调用资源类方法
    public static void main(String[] args) {
        //创建资源类对象
        Ticket ticket=new Ticket();
        //创建3个线程
        new Thread(new Runnable() { //匿名内部类
            @Override
            public void run() {
                //调用资源类方法
                for(int i=0;i<40;i++) {
                    ticket.sale();
                }
            }
        }, "A").start();
        //同理创建线程B、C
    }
}

Lock实现卖票

import java.util.concurrent.locks.ReentrantLock;

//1.创建资源类 定义属性、方法
class Ticket {
    //票数
    private int number=30;
    //创建可重入锁
    private final ReentrantLock lock = new ReentrantLock();
    //卖票
    public void sale() {   //用ReentrantLock手动上锁
        //加锁
        lock.lock();
        try{
            if(number>0) {
                System.out.println(Thread.currentThread().getName()+"卖出"+number--+"号票");
            }
        } finally {
            //解锁
            lock.unlock();
        }
    }
}

public class Test1_LSaleTicket {
    //2.创建多个线程 调用资源类方法
    public static void main(String[] args) {
        //创建资源类对象
        Ticket ticket = new Ticket();
        //创建3个线程
        new Thread(() -> { //Lambda表达式
            //调用资源类方法
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        }, "A").start();    //线程调用start后可能马上创建(OS空闲),也可能等会儿创建(OS忙) 取决于OS
        //同理创建线程B、C
    }
}

线程执行顺序不固定的原因


3 线程间通信

线程间通信模型:共享内存、消息传递

⭐多线程编程步骤

  1. 创建资源类 定义属性、方法
  2. 在资源类操作方法(判断 干活 通知)
  3. 创建多个线程 调用资源类方法
  4. 防止虚假唤醒问题(判断条件要加到while中)

实现线程间交替+1-1操作

  1. 用synchronized关键字实现
    this.wait()/notifyAll();
//1.创建资源类 定义属性、方法
class Share {
    private int i=0;

    //2.在资源类操作方法(在方法中 判断 干活 通知)
    public synchronized void incr() throws InterruptedException {
        //判断
        while(i!=0) {
            this.wait();    //不满足干活条件->在被通知前<等待> 释放锁
        }
        //干活
        i++;
        System.out.println(Thread.currentThread().getName()+":"+i);
        //通知其它线程
        this.notifyAll();
    }

    public synchronized void decr() throws InterruptedException {
        //判断
        while(i!=1) {
            this.wait();    //if的wait:在哪里睡,在哪里醒 被调用时唤醒->虚假唤醒 解决:条件放到while中
        }
        //干活
        i--;
        System.out.println(Thread.currentThread().getName()+":"+i);
        //通知
        this.notifyAll();
    }
}

public class Test2_ThreadSignal {
    //3.创建多个线程 调用资源类方法
    public static void main(String[] args) {
        //创建资源类对象
        Share share=new Share();

        //创建2个线程 交替实现+1-1
        //若是多个线程,等待条件放在if中:+的锁释放后又被+的线程抢到了,会+到>1 -的操作也会-到<0
        new Thread(()->{
            for(int i=0;i<10;i++) {
                try {
                    share.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
		//同理创建线程B、C、D
    }
}
  1. 用Lock接口实现
    new ReentrantLock().newCondition().await()/signalAll();
//1.创建资源类 定义属性、方法
class Share {
    private int i=0;

    //创建lock
    private Lock lock=new ReentrantLock();
    private Condition condition=lock.newCondition();

    //2.在资源类操作方法
    public void incr() throws InterruptedException {
        //上锁
        lock.lock();
        try {
            //判断
            while(i!=0) {
                condition.await();
            }
            //干活
            i++;
            System.out.println(Thread.currentThread().getName()+":"+i);
            //通知
            condition.signalAll();
        } finally {
            //解锁
            lock.unlock();
        }
    }

    public void decr() throws InterruptedException {
        lock.lock();
        try {
            while(i!=1) {
                condition.await();
            }
            i--;
            System.out.println(Thread.currentThread().getName()+":"+i);
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }
}

public class Test2_LThreadSignal {
    //3.创建多个线程 调用资源类方法
    public static void main(String[] args) {
        //创建资源类对象
        Share share=new Share();
        //创建多个线程
        new Thread(()->{
            for(int i=0;i<10;i++) {
                try {
                    share.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
		//同理创建线程B-decr、C-incr、D-decr
    }
}

线程虚假唤醒

  • 问题
  • 原因
  • 解决
  • 分析
    理解虚假唤醒问题:坐飞机(调用线程)要进行安检(判断条件),下飞机(线程睡眠)再上飞机(唤醒线程)还要进行安检(while醒来条件不符合继续睡),否则只安检一次(if)的话可能会有可疑物品带上飞机(程序会往下执行)

4 线程间定制化通信
  • 用 notify()通知时,JVM会随机唤醒某个等待的线程, 使用 Condition 类可以进行选择性通知
  • 在调用 Condition 的 await()/signal()方法前,也需要线程持有相关锁,调用 await()后线程会释放这个锁,在 singal() 调用后会从当前 Condition 对象的等待队列中,唤醒一个线程,唤醒的线程尝试获得锁,一旦成功获得锁就继续执行

A 线程打印5次A,B线程打印10次B,C线程打印15次C,按照
此顺序循环10轮->用Lock Condition的 signal() 实现

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//用 Lock接口 实现 线程间定制化通信
//1.创建资源类 定义属性、方法
class ShareResource {
    //定义标志位 A-1 B-2 C-3
    private int flag=1;

    //创建lock
    private Lock lock=new ReentrantLock();
    private Condition c1=lock.newCondition();
    private Condition c2=lock.newCondition();
    private Condition c3=lock.newCondition();

    //2.在资源类操作方法
    public void print5(int loop) throws InterruptedException {
        //上锁
        lock.lock();
        try {
            //判断
            while(flag!=1) {
                c1.await();
            }
            //干活
            for(int i=1;i<=5;i++) {
                System.out.println("第"+loop+"轮 "+Thread.currentThread().getName()+":"+i);
            }
            flag=2;
            //通知B
            c2.signal();
        } finally {
            //解锁
            lock.unlock();
        }
    }

    public void print10(int loop) throws InterruptedException {
        //上锁
        lock.lock();
        try {
            //判断
            while(flag!=2) {
                c2.await();
            }
            //干活
            for(int i=1;i<=10;i++) {
                System.out.println("第"+loop+"轮 "+Thread.currentThread().getName()+":"+i);
            }
            flag=3;
            //通知C
            c3.signal();
        } finally {
            //解锁
            lock.unlock();
        }
    }

    public void print15(int loop) throws InterruptedException {
        //上锁
        lock.lock();
        try {
            //判断
            while(flag!=3) {
                c3.await();
            }
            //干活
            for(int i=1;i<=15;i++) {
                System.out.println("第"+loop+"轮 "+Thread.currentThread().getName()+":"+i);
            }
            flag=1;
            //通知A
            c1.signal();
        } finally {
            //解锁
            lock.unlock();
        }
    }
}

public class Test2_CustomizedThreadSignal {
    //3.创建多个线程 调用资源类方法
    public static void main(String[] args) {
        //创建资源类对象
        ShareResource resource=new ShareResource();

        //创建多个线程
        new Thread(()->{
            for(int i=1;i<=10;i++) {
                try {
                    resource.print5(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(()->{
            for(int i=1;i<=10;i++) {
                try {
                    resource.print10(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();

        new Thread(()->{
            for(int i=1;i<=10;i++) {
                try {
                    resource.print15(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
    }
}
5 集合的线程安全


演示

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;

//List HashSet HashMap 线程不安全
public class Test3_CollectionUnsafe {
    public static void main(String[] args) {
        //ArrayList线程不安全
//        List list=new ArrayList<>();
        //Vector解决
//        List list=new Vector<>();
        //Collections解决
//        List list= Collections.synchronizedList(new ArrayList<>());
        //CopyOnWriteArrayList解决
        List list=new CopyOnWriteArrayList<>();

        //HashSet线程不安全
//        Set set=new HashSet<>();
        //CopyOnWriteArraySet解决
        Set set=new CopyOnWriteArraySet<>();

        //HashMap线程不安全
//        Map map=new HashMap<>();
        //Hashtable解决
//        Map map=new Hashtable<>();
        //ConcurrentHashMap解决
        Map map=new ConcurrentHashMap<>();

        for(int i=0;i<30;i++) {
            String key=String.valueOf(i);
            new Thread(()->{
                //向集合添加内容
//                list.add(UUID.randomUUID().toString().substring(0,8));
//                set.add(UUID.randomUUID().toString().substring(0,8));
                map.put(key,UUID.randomUUID().toString().substring(0,8));
                //从集合获取内容
//                System.out.println(list);
//                System.out.println(set);
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}
5.1 ArrayList线程不安全

List<…> list=new ArrayList<>();

解决:

  1. Vector
    List<…> list=new Vector<>();

  2. Collections集合工具类

    List<…> list= Collections.synchronizedList(new ArrayList<>());
  3. CopyOnWriteArrayList
    List<…> list=new CopyOnWriteArrayList<>();
    读时共享,写时复刻

思想:拷贝一份

  1. 独占锁效率低:采用读写分离思想解决
  2. 写线程获取到锁,其他写线程阻塞
  3. 复制思想
    当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行 Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。
    这时候会抛出来一个新的问题,也就是数据不一致的问题。如果写线程还没来得及写会内存,其他的线程就会读到了脏数据

CopyOnWriteArrayList原理分析

  1. 动态数组 机制
  • 它内部有个“volatile 数组”(array)来保持数据。在“添加/修改/删除”数据时,都会新建一个数组,并将更新后的数据拷贝到新建的数组中,最后再将该数组赋值给“volatile 数组”, 这就是它叫做 CopyonWriteArrayList 的原因
  • 由于它在“添加/修改/删除”数据时,都会新建数组,所以涉及到修改数据的操作,CopyonWriteArrayList 效率很低;但是单单只是进行遍历查找的话,效率比较高
  1. 线程安全 机制
  • 通过 volatile 和 互斥锁 来实现
  • 通过 volatile数组 来保存数据. 一个线程读取 volatile 数组时,总能看到其它线程对该 volatile 变量最后的写入;就这样,通过 volatile 提供了“读取到的数据总是最新的”这个机制的保证
  • 通过 互斥锁 来保护数据. 在“添加/修改/删除”数据时,会先“获取互斥锁”,再修改完毕之后,先将数据更新到“volatile 数组”中,然后再“释放互斥锁”,就达到了保护数据的目的
5.2 HashSet线程不安全

Set<…> set=new HashSet<>();


异常报错是HashMap,因为HashSet底层基于HashMap实现:

Set元素唯一、无序的原因:

解决:CopyOnWriteArraySet
Set<…> set=new CopyOnWriteArraySet<>();
->

5.3 HashMap线程不安全

Map map=new HashMap<>();

HashMap可存储null的key和value,null作为键只能有一个,null作为值可有多个


解决:

  1. HashTable
    Map map=new Hashtable<>();

HashTable使用synchronized来保证线程安全,在线程竞争激烈的情况下效率非常低下,所以HashTable基本被淘汰,不要在代码中使用它,要保证线程安全的话就使用ConcurrentHashMap

  1. ConcurrentHashMap
    Map map=new ConcurrentHashMap<>();

ConcurrentHashMap通过在部分加锁和利用CAS算法来实现同步
key和Value都不能为null,否则抛出 NullPointerException 异常(图片1011行)

ConcurrentHashMap原理 jdk7和8版本的区别

ConcurrentHashMap基于JDK1.8源码剖析
ConcurrentHashMap如何保证线程安全

6 多线程锁 6.0 锁的范围

锁的8种情况 (锁的范围-是否是同一把锁)

import java.util.concurrent.TimeUnit;

class Phone {
    public static synchronized void sendSMS() throws Exception {
//    public synchronized void sendSMS() throws Exception {
        //停留 4 秒
        TimeUnit.SECONDS.sleep(4);
        System.out.println("------sendSMS");
    }
//    public static synchronized void sendEmail() throws Exception {
    public synchronized void sendEmail() throws Exception {
        System.out.println("------sendEmail");
    }
    public void getHello() {
        System.out.println("------getHello");
    }
}

public class Test4_Lock8 {
    public static void main(String[] args) throws Exception {
        Phone phone=new Phone();
        Phone phone1=new Phone();

        new Thread(()->{
            try{
                phone.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"A").start();

        Thread.sleep(100);  //线程什么时候创建不确定,所以在两线程中间睡眠一下,使效果更明显

        new Thread(()->{
            try{
//                phone.getHello();
//                phone.sendEmail();
                phone1.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"B").start();
    }
}

结论

  • 一个对象里面如果有多个 synchronized 方法,某一个时刻内,只要一个线程去调用其中的一个 synchronized 方法了,其它的线程都只能等待,即某一个时刻内,只能有唯一一个线程去访问这些 synchronized 方法. 因为锁的是当前对象 this(同一把锁),被锁定后,其它的线程都不能进入到当前对象的其它的synchronized 方法
  • 加个普通方法后发现和同步锁无关
  • 换成两个对象后,不是同一把锁了,情况立刻变化
  • 所有静态同步方法用的是同一把锁——类对象本身,和任何实例对象的普通同步方法用的锁是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有竞态条件的

⭐synchronized作用范围(3种同步方法)

作用范围锁的对象
普通方法当前实例对象(对象锁)
静态方法当前类的 Class 对象(类锁)
代码块synchonized 括号里配置的对象(对象锁)

synchronized 实现同步的基础:Java 中的每一个对象都可以作为锁

  1. 修饰普通方法
public class X {
	//锁的是 this (同步方法使用的同步对象为该方法所属类本身的实例对象)
    public synchronized void test() {
    	//code
    }
}
  1. 修饰静态方法
public class X {
	//锁的是 X.clss (类的字节码文件)
    public static synchronized void test() {
    	//code
    }
}
  1. 修饰代码块
public class X {
    public void test() {
    	//锁的是 obj (可以是任意对象,但必须为同一对象)
        synchronized (obj){
            //同步代码块
        }
    }
}
6.1 非公平锁 & 公平锁

非公平锁
效率高 会导致线程饿死


卖票例子
ReentraintLock()无参构造默认用非公平锁

只有线程A占着锁,其它线程饿死


公平锁
效率相对低 资源对线程-雨露均沾 公平锁是为了让CPU发挥多线程的性能


卖票例子,ReentraintLock()有参构造传参


每个线程都能获得锁


6.2 可重入锁

synchronized隐式(内置)锁、Lock显式锁 都是可重入锁(递归锁)

可重入锁在破解第一把锁之后可一直进入到内层结构


演示可重入锁

public class Test5_ReentraintLock {
    //2.同步方法演示可重入锁
    public synchronized void add() {
        add();//因为是可重入锁,所以会递归调用add();若是不可重入锁. 就会等待this对象释放锁,这段代码会死锁
    }

    public static void main(String[] args) {
        new Test5_ReentraintLock().add();   //循环递归调用->最后栈溢出

        //1.同步代码块演示可重入锁
		Object o=new Object();
        new Thread(()->{
            synchronized (o) {
                System.out.println(Thread.currentThread().getName()+":外层");
                synchronized (o) {
                    System.out.println(Thread.currentThread().getName()+":中层");
                    synchronized (o) {
                        System.out.println(Thread.currentThread().getName()+":内层");
                    }
                }
            }
        },"A").start();

		//3.Lock演示可重入锁
        Lock lock=new ReentrantLock();
        new Thread(()->{
            try {
                lock.lock();    //上锁
                System.out.println(Thread.currentThread().getName()+":外层");
                try {
                    lock.lock();    //上锁
                    System.out.println(Thread.currentThread().getName()+":内层");
                } finally {
                    lock.unlock();  //解锁
                }
            } finally {
                lock.unlock();  //解锁
            }
        },"B").start();

        new Thread(()->{
            lock.lock();    //若是另一个线程不解锁 这个线程就得不到锁 一直等待不能执行
            System.out.println(Thread.currentThread().getName());
            lock.unlock();
        },"C").start();
    }
}
  1. 注释掉线程B的一个unlock:

6.3 死锁

deadlock 多个进程互不相让,都得不到足够的资源(永久性阻塞)


演示死锁(记住 面试手撕代码)

import java.util.concurrent.TimeUnit;

public class Test6_DeadLock {
    static Object a=new Object();
    static Object b=new Object();

    public static void main(String[] args) {
        new Thread(()->{
            synchronized (a) {
                System.out.println(Thread.currentThread().getName()+"持有锁a,试图获取锁b");
                //睡眠1s让线程创建确定,使死锁效果更明显
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (b) {
                    System.out.println(Thread.currentThread().getName()+"获取锁b");
                }
            }
        },"A").start();

        new Thread(()->{
            synchronized (b) {
                System.out.println(Thread.currentThread().getName()+"持有锁b,试图获取锁a");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (a) {
                    System.out.println(Thread.currentThread().getName()+"获取锁a");
                }
            }
        },"B").start();
    }
}

死锁验证方式

  1. jps [= Lunix: ps -ef 查询当前正在运行的进程;jdk提供的查看当前java进程的小工具,可看做 JavaVirtual Machine Process Status Tool 的缩写]
  2. jstack [JVM自带堆栈跟踪工具]

    PATH配置了JAVA_HOME就可以在IDEA终端中使用命令

7 Callable接口
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/292592.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号