• 线程的学习5


    并发

    由于同一进程的多个线程共享同一块存储空间,在带来方便的同时也带来了访问冲突问题(同一个对象被多个线程同时操作),即并发问题。并发问题可以通过线程同步来解决,即队列+锁机制。

    线程同步

    线程同步:其实就是就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕,下一个线程使用。为了保证数据在方法中被访问时的正确性,在访问时加入锁机制(synchronized),当一个线程获得对象的排他锁,独占资源,其他线程必须等待,使用后释放锁即可。

    示例:买票
    package com.xct.test4;
    //测试买票(线程不安全)
    public class UnSafeBuyTicket {
        public static void main(String[] args) {
            BuyTicket b = new BuyTicket();
            new Thread(b,"张三").start();
            new Thread(b,"李四").start();
            new Thread(b,"黄牛").start();
        }
    }
    
    //买票
    class BuyTicket implements Runnable{
        //票
        private int ticketNum = 500;
        boolean flag = true;//外部停止的方式
        @Override
        public void run() {
            //买票
            while (flag){
                try {
                    buy();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        //买票的方法
        //同步方法 synchronized 锁的是this
        private synchronized void buy() throws InterruptedException {
            //判断是否有票
            if(ticketNum <= 0){
                flag = false;
                return;
            }
            //模拟网络延时
            Thread.sleep(100);
    
            System.out.println(Thread.currentThread().getName()+"买到了第"+ticketNum--+"张票");
        }
    }
    
    • 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
    示例:取钱
    package com.xct.test4;
    //测试取钱(线程不安全)
    public class UnSafeBank {
        public static void main(String[] args) {
            //账户
            Account account = new Account(1000,"结婚基金");
            Drawing you = new Drawing(account,50,"你");
            Drawing boyFriend = new Drawing(account,100,"女朋友");
            you.start();
            boyFriend.start();
        }
    
    
    
    }
    
    //模拟账户
    class Account{
        int money;//余额
        String name;//卡名
    
        public Account(int money, String name) {
            this.money = money;
            this.name = name;
        }
    }
    
    //模拟取款
    class Drawing extends Thread{
        Account account;//账户
        int drawMoney;//取了多少钱
        int nowMoney;//现在手里有多少钱
    
        public Drawing(Account account, int drawMoney, String name) {
            super(name);
            this.account = account;
            this.drawMoney = drawMoney;
        }
    
        @Override
        //synchronized 默认锁的是this
    
        //取钱
        public  void run() {
    
            //锁银行的账户
            synchronized (account){
                //判断有没有钱
                if(account.money-drawMoney<0){
                    System.out.println(Thread.currentThread().getName()+"钱不够了");
                    return;
                }
    
                //模拟银行取钱延时(sleep可以放大问题的发生性)
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                //卡内余额=余额-你取的钱
                account.money = account.money-drawMoney;
                //你手里的钱=你手里的钱+取得钱
                nowMoney = nowMoney+drawMoney;
    
                System.out.println(account.name+"余额为"+account.money);
                System.out.println(this.getName()+"手里的钱为"+nowMoney);
            }
    
        }
    }
    
    • 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
    示例:线程不安全的集合:ArrayList
    package com.xct.test4;
    
    import java.util.ArrayList;
    import java.util.List;
    
    //不安全的集合 ArrayList
    public class UnSafeList {
        public static void main(String[] args) throws InterruptedException {
            List<String> list = new ArrayList<>();
            for (int i = 0; i < 10000; i++) {
                new Thread(()->{
                    synchronized (list){
                        list.add(Thread.currentThread().getName());
                    }
                }).start();
            }
            //放大问题的发生性
            Thread.sleep(1000);
    
            //输出list的长度
            System.out.println(list.size());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    小结:
    1. 同步方法:用synchronized修饰需要被多个线程操作的方法。
    2. synchronized(obj){}称为同步代码块。obj称为同步监视器。
    3. 同步方法锁的是this,就是这个对象本身,同步方法有弊端:方法里面需要修改的内容才需要锁,锁的太多,浪费资源。
    4. sleep可以放大问题的发生性。
    补充:JUC安全类型的集合 CopyOnWriteArrayList
    package com.xct.test4;
    
    import java.util.concurrent.CopyOnWriteArrayList;
    
    //测试JUC安全类型的集合
    public class TestJUC {
        public static void main(String[] args) throws InterruptedException {
            CopyOnWriteArrayList<String> copyOnWriteArrayList = new CopyOnWriteArrayList<String>();
            for (int i = 0; i < 10000; i++) {
                new Thread(()->{
                    copyOnWriteArrayList.add(Thread.currentThread().getName());
                }).start();
            }
    
            //放大问题的发生性
            Thread.sleep(1000);
            System.out.println(copyOnWriteArrayList.size());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    死锁

    多个线程互相抱着对方需要的资源,然后形成僵持状态。

    产生死锁的条件
    1. 互斥条件:一个资源每次智能被一个进程使用。
    2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
    3. 不剥夺条件:进程已获得的资源,在未使用完之前,不得强行剥夺。
    4. 循环等待条件:若干个进程之间形成一种头尾相接的循环等待资源关系。
    示例:
    package com.xct.test5;
    //测试死锁问题(两个女孩化妆,互相抱着对方的资源,形成僵持状态)
    public class TestSynchronized {
        public static void main(String[] args) {
            MakeUp m1 = new MakeUp(0,"小红");
            MakeUp m2 = new MakeUp(1,"小明");
            m1.start();
            m2.start();
        }
    }
    
    //口红
    class LipStick{
    
    }
    
    //镜子
    class Mirror{
    
    }
    
    //化妆类
    class MakeUp extends Thread{
        //每份资源只有一份,用static
         static LipStick lipStick = new LipStick();
         static Mirror mirror = new Mirror();
        private int choice; // 每个女孩的选择
        private String girlName;//女孩的名字
    
        public MakeUp(int choice, String girlName) {
            this.choice = choice;
            this.girlName = girlName;
        }
    
        @Override
        public void run() {
            //化妆
            try {
                makeUp();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        //化妆的方法
        private void makeUp() throws InterruptedException {
            if(choice == 0){
                synchronized (lipStick){
                    System.out.println(this.girlName+"持有口红的锁");
                    Thread.sleep(1000);
                    synchronized (mirror){//一秒钟后想获取镜子的锁
                        System.out.println(this.girlName+"持有镜子的锁");
                    }
                }
    
    
            }else{
                synchronized (mirror){
                    System.out.println(this.girlName+"持有镜子的锁");
                    Thread.sleep(2000);
                    synchronized (lipStick){//两秒钟后想获取口红的锁
                        System.out.println(this.girlName+"持有口红的锁");
                    }
                }
    
            }
        }
    }
    
    • 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

    Lock锁(JUC包下)

    ReentrantLock(可重入锁)类实现了Lock。

    示例:
    package com.xct.test5;
    
    import java.util.concurrent.locks.ReentrantLock;
    
    //测试Lock
    public class TestLock {
        public static void main(String[] args) {
            TestLock2 testLock2 = new TestLock2();
            new Thread(testLock2).start();
            new Thread(testLock2).start();
            new Thread(testLock2).start();
        }
    }
    
    class TestLock2 implements Runnable{
    
        //定义票数
        private int tickNums = 10;
    
        //定义lock锁
        private ReentrantLock lock = new ReentrantLock();
    
        @Override
        public void run() {
            while (true){
                //开启lock锁
                try {
                    lock.lock();
                    if(tickNums > 0){
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(tickNums--);
                    }else{
                        break;
                    }
                }finally {
                    //关闭锁
                    lock.unlock();
                }
    
            }
    
        }
    }
    
    • 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

    synchonized与Lock的对比

    1. Lock是显示锁,需要手动开启和关闭锁。
    2. synchronized是隐式锁,出了作用域自动释放。
    3. Lock只有代码块锁,synchronized有代码块锁和方法锁。

    线程通信

    线程通信的两种方式:

    1. 管程法:生产者将生产好的数据放入缓冲区,消费者从缓冲区拿数据。
    2. 信号灯法:使用标志位 boolean flag = true.
    示例:管程法
    package com.xct.test5;
    //测试线程通信1   使用缓冲区  管程法
    public class TestPC {
        public static void main(String[] args) {
            SynContainer synContainer = new SynContainer();
            new Provider(synContainer).start();
            new Consumer(synContainer).start();
        }
    }
    
    //生产者
    class Provider extends Thread{
        SynContainer synContainer;
    
        public Provider(SynContainer synContainer) {
            this.synContainer = synContainer;
        }
    
        //生产
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                synContainer.push(new Product(i));
                System.out.println("生产了"+i+"个产品");
            }
        }
    }
    
    //消费者
    class Consumer extends Thread{
        SynContainer synContainer;
    
        public Consumer(SynContainer synContainer) {
            this.synContainer = synContainer;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println("消费了第"+synContainer.pop().id+"个产品");
            }
        }
    }
    
    //产品
    class Product{
        int id;//产品编号
    
        public Product(int id) {
            this.id = id;
        }
    }
    
    //缓冲区
    class SynContainer{
    
        //容器大小
        Product[] products = new Product[10];
        //容器计算器
        int count = 0;
    
        //生产者放入产品
        public synchronized void push(Product product){
            // 如果已满,等待消费者消费
            if(count == products.length){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
                //如果容器大小未满,放入产品
                products[count] = product;
                count++;
    
                //可以通知消费者消费
            this.notifyAll();
        }
    
        //消费者消费产品
        public synchronized Product pop(){
            //判断是否消费
            if(count == 0){
                //等待生产者生产
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            //如果消费,拿出产品
            count--;
            Product pro = products[count];
    
            //拿出后,通知生产者生产
            this.notifyAll();
            return pro;
        }
    }
    
    //运作
    class RunUp implements Runnable{
    
        @Override
        public void run() {
    
        }
    }
    
    • 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
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    示例:信号灯法
    package com.xct.test5;
    //测试线程通信2  设置标志位  信号灯法
    public class TestPC2 {
        public static void main(String[] args) {
            TV tv = new TV();
            new Player(tv).start();
            new Watcher(tv).start();
        }
    }
    
    //生产者  演员
    class Player extends Thread{
        TV tv;
    
        public Player(TV tv) {
            this.tv = tv;
        }
    
        //演员表演
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                if(i%2==0){
                    tv.play("快乐大本营正在播放");
                }else{
                    tv.play("广告中");
                }
            }
        }
    }
    
    //消费者 观众
    class Watcher extends Thread{
        TV tv;
    
        public Watcher(TV tv) {
            this.tv = tv;
        }
    
        //观众观看
    
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                tv.watch();
            }
        }
    }
    
    //产品 节目
    class TV{
         String voice;//节目
        private boolean flag = true;//标志位
    
    
        //演员表演
        public synchronized void play(String voice){
            if(!flag){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("演员演了"+voice);
            //通知观众观看
            this.notifyAll();
            //更新节目与标志位
            this.voice = voice;
            this.flag = !this.flag;
        }
    
        //观众观看
        public synchronized void watch(){
            if(flag){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("观众观看了"+voice);
            //通知演员表演
            this.notifyAll();
            this.flag = !this.flag;
        }
    }
    
    • 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

    线程池

    线程池就是提前创建若干个线程,如果有任务需要处理,线程池里的线程就会处理任务,处理完之后线程并不会被销毁,而是等待下一个任务。由于创建和销毁线程都是消耗系统资源的,所以当你想要频繁的创建和销毁线程的时候就可以考虑使用线程池来提升系统的性能。
    JDK5起提供了线程池相关的API:ExecutorService和Executors。

    示例:
    package com.xct.test5;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    //测试线程池
    public class TestPool {
        public static void main(String[] args) {
            //创建服务,创建线程池
            //newFixedThreadPool  参数大小为线程池大小
            ExecutorService service = Executors.newFixedThreadPool(10);
    
            //执行
            service.execute(new MyThread());
            service.execute(new MyThread());
            service.execute(new MyThread());
            service.execute(new MyThread());
    
            //关闭服务
            service.shutdown();
        }
    }
    
    class MyThread implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }
    
    • 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
  • 相关阅读:
    SQLite 3.37.0 发布,支持严格的字段数据类型
    CentOS中的rename命令
    LangChain入门学习笔记(七)—— 使用检索提高生成内容质量
    【HHO-KELM预测】基于哈里斯鹰算法优化核极限学习机回归预测研究(Matlab代码实现)
    GPT-4从0到1搭建一个Agent简介
    chatGPT底层原理是什么,为什么chatGPT效果这么好?三万字长文深度剖析-下
    在hugging face上发布自己的模型 (ubuntu 19.0)
    el-table树形数据隐藏子选择框
    Meta-World:多任务、持续学习、终身学习、元学习、强化学习的基准和评估
    国考数量关系一道关于发车间隔的经典题目
  • 原文地址:https://blog.csdn.net/m0_57082679/article/details/127706148