• 【JUC】并发编程学习笔记(一)


    一、基础知识

    1.1、JUC

    JUC是java.util.concurrent并发编程中使用的工具包

    1.2、进程与线程

    进程(Process ) 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。

    线程( thread ) 是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。v

    1.3、线程的状态

    1. NEW,(新建)
    2. RUNNABLE,(准备就绪)
    3. BLOCKED,(阻塞)
    4. WAITING,(不见不散)
    5. TIMED_WAITING,(过时不候)
    6. TERMINATED;(终结)

    1.4、wait/sleep的区别

    ( 1 ) sleep是Thread的静态方法,wait是 Object的方法,任何对象实例都能调用。

    ( 2 ) sleep不会释放锁,它也不需要占用锁。wait 会释放锁,但调用它的前提是当前线程占有锁(即代码要在synchronized中)。

    (3)它们都可以被interrupted方法中断。

    1.5、并发与并行。

    并发:同一时刻多个线程在访问同一个资源,多个线程对一个点·

    例子︰春运抢票 电商秒杀 .…

    并行:多项工作一起执行,之后再汇总

    例子︰泡方便面,电水壶烧水,一边撕调料倒入桶中;

    1.6、管程

    管程 是一种同步机制,保证同一个时间,只有一个线程访问被保护数据或者代码

    jvm同步基于进入和退出,使用管程对象实现的

    1.7、用户线程与守护线程

    用户线程∶自定义线程(主线程结束了,用户线程还在运行, jvm存活)

    守护线程:比如垃圾回收(没有用户线程了,都是守护线程,jvm结束)

    public class Main01 {
        public static void main(String[] args) {
            Thread aa=new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"::"+Thread.currentThread().isDaemon());
                while (true){}
            },"aa");
    
    		//aa设置成守护线程
    		//aa.setDaemon(true);
            aa.start();
            System.out.println(Thread.currentThread().getName()+"over");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    虽然main主方法结束了 但自定义线程aa还在死循环中 那么程序还在运行中( jvm存活)
    在这里插入图片描述
    将aa设置成守护线程 主线程结束 没有用户线程 那么程序停止(jvm结束)
    在这里插入图片描述

    二、Lock锁

    2.1、synchronized

    synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:

    1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号}括起来的代码,作用的对象是调用这个代码块的对象;·
    2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用
      的对象是调用这个方法的对象;
    3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的
      所有对象;
    4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主
      的对象是这个类的所有对象。

    2.1.1、案例

    3个售票员 卖出30张票

    class Ticket{
        private int num =30;
        synchronized void sale(){
           if(num>0){
               System.out.println(Thread.currentThread().getName()+"卖出,"+"剩余:"+(--num ));
           }
        }
    }
    public class SaleTicketBySynchronized  {
        public static void main(String[] args) {
            Ticket ticket=new Ticket();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 40; i++) {
                        ticket.sale();
                    }
                }
            },"aa").start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 40; i++) {
                        ticket.sale();
                    }
                }
            },"bb").start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 40; i++) {
                        ticket.sale();
                    }
                }
            },"cc").start();
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    2.2、Lock

    为锁和等待条件提供一个框架的接口和类,它不同于内置同步和监视器。

    Lock 实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作。

    Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized 代码块执行完或者出现异常之后系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

    2.2.1、案例

    3个售票员 卖出30张票

    import java.util.concurrent.locks.ReentrantLock;
    
    class Ticket{
        private int num =30;
    
        private final ReentrantLock lock=new ReentrantLock();
    
        public void sale(){
            //上锁
            lock.lock();
            try {
                if(num>0){
                    System.out.println(Thread.currentThread().getName()+"卖出,"+"剩余:"+(--num));
                }
            }finally {
                //解锁
                lock.unlock();
            }
        }
    }
    
    public class SaleTicketByLock {
        public static void main(String[] args) {
            Ticket ticket=new Ticket();
    
            new Thread(()-> {
                for (int i = 0; i < 40; i++) {
                    ticket.sale();
                }
            },"aa").start();
    
            new Thread(()-> {
                for (int i = 0; i < 40; i++) {
                    ticket.sale();
                }
            },"bb").start();
    
            new Thread(()-> {
                for (int i = 0; i < 40; i++) {
                    ticket.sale();
                }
            },"cc").start();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    三、线程间的通信

    3.1、案例synchronized 方式

    有一个数开始为0 两个线程 不断+1-1

    class Share{
        private int number=0;
        //+1
        public synchronized void incr() throws InterruptedException {
            if(number!=0){
                this.wait();
            }
            number++;
            System.out.println(Thread.currentThread().getName()+"::"+number);
            //通知其他线程
            this.notifyAll();
        }
        //-1
        public synchronized void decr() throws InterruptedException {
            if(number!=1){
                this.wait();
            }
            number--;
            System.out.println(Thread.currentThread().getName()+"::"+number);
            //通知其他线程
            this.notifyAll();
        }
    }
    public class ThreadDemo01 {
        public static void main(String[] args) {
            Share share=new Share();
    
            new Thread(()-> {
                for (int i = 0; i < 10; i++) {
                    try {
                        share.incr();//+1
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"aa").start();
    
            new Thread(()-> {
                for (int i = 0; i < 10; i++) {
                    try {
                        share.decr();//-1
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"bb").start();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    3.2、虚假唤醒

    在案例中增加两个新线程 也是一加一减 cc dd
    此时数据就会混乱
    在这里插入图片描述

    原因 wait执行时 被唤醒会继续执行(在哪里睡,就在哪里醒)恰巧又是两个加的线程抢到执行权 此时数据就会混乱

    解决 将if 替换为while 不管怎么被唤醒都要执行判断

    3.3、案例lock方式

    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    class Share{
        private int number=0;
        private Lock lock=new ReentrantLock();
        private Condition condition=lock.newCondition();
        //+1
        public void incr() throws InterruptedException {
            lock.lock();
            try {
                while (number!=0){
                    condition.await();
                }
                number++;
                System.out.println(Thread.currentThread().getName()+"::"+number);
                //通知其他线程
                condition.signalAll();
            }finally {
                lock.unlock();
            }
        }
        //-1
        public void decr() throws InterruptedException {
            lock.lock();
            try {
                while (number!=1){
                    condition.await();
                }
                number--;
                System.out.println(Thread.currentThread().getName()+"::"+number);
                //通知其他线程
                condition.signalAll();
            }finally {
                lock.unlock();
            }
        }
    }
    
    public class ThreadDemo02 {
        public static void main(String[] args) {
            Share share=new Share();
    
            new Thread(()-> {
                for (int i = 0; i < 10; i++) {
                    try {
                        share.incr();//-1
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"aa").start();
    
            new Thread(()-> {
                for (int i = 0; i < 10; i++) {
                    try {
                        share.decr();//-1
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"bb").start();
    
            new Thread(()-> {
                for (int i = 0; i < 10; i++) {
                    try {
                        share.incr();//-1
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"cc").start();
    
            new Thread(()-> {
                for (int i = 0; i < 10; i++) {
                    try {
                        share.decr();//-1
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"dd").start();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

    3.4、线程间定制化通信

    启动三个线程,按照如下要求:
    AA打印5次后,BB打印10次后,CC打印15次 循环10次

    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    class ShareResource{
        //1->aa 2->bb 3->bb
        private int flag=1;
        private Lock lock=new ReentrantLock();
        private Condition c1=lock.newCondition();
        private Condition c2=lock.newCondition();
        private Condition c3=lock.newCondition();
    
        public void print5(int loop) throws InterruptedException {
            lock.lock();
            try {
                while (flag!=1){
                    c1.await();
                }
                for (int i = 0; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName()+"::"+i+":: 轮数:"+loop);
                }
                flag=2;
                //通知线程
                c2.signal();
            }finally {
                lock.unlock();
            }
        }
    
        public void print10(int loop) throws InterruptedException {
            lock.lock();
            try {
                while (flag!=2){
                    c2.await();
                }
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName()+"::"+i+":: 轮数:"+loop);
                }
                flag=3;
                //通知线程
                c3.signal();
            }finally {
                lock.unlock();
            }
        }
    
        public void print15(int loop) throws InterruptedException {
            lock.lock();
            try {
                while (flag!=3){
                    c3.await();
                }
                for (int i = 0; i < 15; i++) {
                    System.out.println(Thread.currentThread().getName()+"::"+i+":: 轮数:"+loop);
                }
                flag=1;
                //通知线程
                c1.signal();
            }finally {
                lock.unlock();
            }
        }
    }
    public class ThreadDemo03 {
        public static void main(String[] args) {
            ShareResource shareResource=new ShareResource();
    
            new Thread(()-> {
                for (int i = 0; i < 10; i++) {
                    try {
                        shareResource.print5(i);//-1
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"aa").start();
    
            new Thread(()-> {
                for (int i = 0; i < 10; i++) {
                    try {
                        shareResource.print10(i);//-1
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"bb").start();
    
            new Thread(()-> {
                for (int i = 0; i < 10; i++) {
                    try {
                        shareResource.print15(i);//-1
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"cc").start();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99

    四、集合的线程安全

    4.1、ArrayList线程不安全

    4.1.1、例子

    import java.util.ArrayList;
    import java.util.UUID;
    
    public class ThreadDemo04 {
        public static void main(String[] args) {
    
            ArrayList<String> arrayList=new ArrayList();
    
            for (int i = 1; i < 10; i++) {
                new Thread(()-> {
                    arrayList.add(UUID.randomUUID().toString().substring(0,8));
                    System.out.println(arrayList);
                },String.valueOf(i)).start();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    4.1.2、解决不安全方式一:Vector

    Vector源码在添加时使用了synchronized 关键字 因此效率会低很多

    import java.util.ArrayList;
    import java.util.UUID;
    import java.util.Vector;
    
    public class ThreadDemo04 {
        public static void main(String[] args) {
            //ArrayList arrayList=new ArrayList();
            Vector<String> arrayList=new Vector<>();
            for (int i = 1; i < 10; i++) {
                new Thread(()-> {
                    arrayList.add(UUID.randomUUID().toString().substring(0,8));
                    System.out.println(arrayList);
                },String.valueOf(i)).start();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    4.1.3、解决不安全方式二:Collections

    在定义的时候使用了synchronized 关键字 因此效率会低很多

    import java.util.*;
    
    public class ThreadDemo04 {
        public static void main(String[] args) {
    
            //ArrayList arrayList=new ArrayList();
            //Vector arrayList=new Vector<>();
            List<String> arrayList=Collections.synchronizedList(new ArrayList());
            for (int i = 1; i < 10; i++) {
                new Thread(()-> {
                    arrayList.add(UUID.randomUUID().toString().substring(0,8));
                    System.out.println(arrayList);
                },String.valueOf(i)).start();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    4.1.3、解决不安全方式三:CopyOnWriteArrayList

    import java.util.*;
    import java.util.concurrent.CopyOnWriteArrayList;
    
    public class ThreadDemo04 {
        public static void main(String[] args) {
    
            //ArrayList arrayList=new ArrayList();
            //Vector arrayList=new Vector<>();
            //List arrayList=Collections.synchronizedList(new ArrayList());
            List<String> arrayList = new CopyOnWriteArrayList();
            for (int i = 1; i < 10; i++) {
                new Thread(()-> {
                    arrayList.add(UUID.randomUUID().toString().substring(0,8));
                    System.out.println(arrayList);
                },String.valueOf(i)).start();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    4.2、HashSet线程不安全

    4.2.1、案例

    import java.util.*;
    
    public class ThreadDemo04 {
        public static void main(String[] args) {
            HashSet<String> set=new HashSet<>();
            for (int i = 1; i < 50; i++) {
                new Thread(()-> {
                    set.add(UUID.randomUUID().toString().substring(0,8));
                    System.out.println(set);
                },String.valueOf(i)).start();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    4.2.2、解决方式:CopyOnWriteArraySet

    import java.util.*;
    import java.util.concurrent.CopyOnWriteArraySet;
    
    public class ThreadDemo04 {
        public static void main(String[] args) {
            Set<String> set=new CopyOnWriteArraySet<>();
            for (int i = 1; i < 50; i++) {
                new Thread(()-> {
                    set.add(UUID.randomUUID().toString().substring(0,8));
                    System.out.println(set);
                },String.valueOf(i)).start();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    4.3、HashMap线程不安全

    4.3.1、案例

    import java.util.*;
    
    public class ThreadDemo04 {
        public static void main(String[] args) {
            Map<String,String> set=new HashMap<>();
            for (int i = 1; i < 50; i++) {
                new Thread(()-> {
                    set.put(UUID.randomUUID().toString().substring(0,8),"nacl");
                    System.out.println(set);
                },String.valueOf(i)).start();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    4.3.2、解决方式:ConcurrentHashMap

    import java.util.*;
    import java.util.concurrent.ConcurrentHashMap;
    
    public class ThreadDemo04 {
        public static void main(String[] args) {
            Map<String,String> set=new ConcurrentHashMap<>();
            for (int i = 1; i < 50; i++) {
                new Thread(()-> {
                    set.put(UUID.randomUUID().toString().substring(0,8),"nacl");
                    System.out.println(set);
                },String.valueOf(i)).start();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
  • 相关阅读:
    第9章:React Hooks
    AOP原理分析《三》- AOP总体流程
    Springboot出现@Value注解无法读取配置文件的解决方法
    java毕业设计体育训练队的信息管理系统服务端源码+lw文档+mybatis+系统+mysql数据库+调试
    ACM浅聊
    ChatGPT在电子健康记录和医疗信息查询中的应用前景如何?
    Codeforces Round #823 (Div. 2) A-D
    Riccati 方程求解
    ubuntu20.04安装pip
    模型效果不好?推荐你8种机器学习调参技巧
  • 原文地址:https://blog.csdn.net/lushixuan12345/article/details/127961953