• 并发编程专题


    一、概述

      串行:一个线程在处理操作;

      并行:多个线程在处理操作;

      并发编程:在多线程环境下,应用程序的执行;

      并发编程的目的:同分运用到资源,提供程序的效率

      什么情况下用到并发编程:

        1.在线程阻塞时,导致应用程序停止;

        2.处理任务时间过长,可以创建子任务,来进行分段处理;

        3.间断任务执行;

    二、并发编程中待解决的问题

    1、并发编程中频繁上下文切换的问题

      即使是单核处理器也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现这个机制;

      时间片是CPU分配给各个线程的时间,因为时间非常短,所以CPU通过不停地切换线程执行,让我们感觉多个线程是同时执行的,时间片一般是十几毫秒;

      CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下一次切换回这个任务时,可以再加载这个任务的状态。所以任务从保存到再加载的过程就是一次上下文切换;

      这就像我们同时读两本书,当我们读一本英文的技术书时,发现某个单词不认识,于是打开中英文字典,但是在的放下英文技术书之前,大脑必须先记住这本书读到了多少页的第多少行,等查完单词之后,能够继续读这本书。这样的切换是会影响读书效率的,同样上下文切换回影响多线程的执行速度;

    2、如何减少上下文性能开销

      ①无锁并发编程;

      ②CAS;

      ③使用最少线程数量;

      ④协程:在单线程环境下进行多任务的调度,可以在多任务之间进行任务切换;

    3、并发编程中死锁问题

      锁是一个非常有用的工具,运用场景非常多,因为它使用起来非常简单,而且易于理解。但同时它也会带来一些困扰,那就是可能会引起死锁,一旦产生死锁,就会造成系统功能不可用; 

    模拟死锁代码实现:

    package com.zn;
    
    public class DeadLockDemo {
        //线程
        private static final Object obj_A =new Object();
        private static final Object obj_B=new Object();
    
        public static void main(String[] args){
            //A线程
            new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (obj_A){
                        System.out.println("使用obj_A线程!");
                        //延迟时间
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //抢占B线程
                        synchronized (obj_B){
                            System.out.println("抢占B线程");
                        }
                    }
                }
            }).start();
    
            //B线程
            new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (obj_B){
                        System.out.println("使用obj_B线程!");
                        //延迟时间
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //抢占A线程
                        synchronized (obj_A){
                            System.out.println("抢占A线程");
                        }
                    }
                }
            }).start();
        }
    }

    控制台效果:

      

       线程A和线程B互相等待对象释放锁;

      一旦出现死锁,业务是可感知的,因为不能继续提供服务了,可以通过jstack工具查看到底哪个线程出现了问题

    jstack命令查看:

    jstack 18208

      

    死锁问题:

      

    4避免死锁的方法

      ①避免一个线程同时获取多个锁;

      ②避免一个线程在锁内同时占用多个资源,尽量保证每一个锁只占用一个资源;

      ③尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制;

      ④破坏请求和保持条件:在申请资源时,一次性将资源都申请到;

      ⑤破坏不可占用条件:抢占资源如果不满足,那就释放所有资源,以后如果需要则再次申请即可;

      ⑥破坏循环等待条件;

    5线程安全问题

      多个线程同时操作同一个资源,可能会造成资源数据不安全问题

     代码实现:

    package com.zn;
    
    import java.util.concurrent.CountDownLatch;
    
    public class UnsafeThread {
        //资源
        private static int num=0;
        //计算线程数量
        private static CountDownLatch countDownLatch=new CountDownLatch(10);
        //对资源进行操作
        public static void inCreate(){
            num++;
        }
    
        public static void main(String[] args) throws InterruptedException {
            for (int i=0;i<10;i++){
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for (int j=0;j<100;j++){
                            inCreate();
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            //每个线程执行完毕,让计数-1
                            countDownLatch.countDown();
                        }
                    }
                }).start();
            }
            //等待计算器为0或者小于执行await下面的代码
            countDownLatch.await();
            System.out.println(num);
        }
    }

    控制台效果:

      

    6、解决线程不安全问题 

    ①同步方法

     代码实现:

    package com.zn;
    
    import java.util.concurrent.CountDownLatch;
    
    public class UnsafeThread {
        //资源
        private static int num = 0;
        //计算线程数量
        private static CountDownLatch countDownLatch = new CountDownLatch(10);
    
        //对资源进行操作
        public static synchronized void inCreate() {
            num++;
        }
    
        public static void main(String[] args) throws InterruptedException {
            for (int i = 0; i < 10; i++) {
                new Thread(() -> {
                    for (int j = 0; j < 100; j++) {
                        inCreate();
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //每一个线程执行完毕,让计数-1
                    countDownLatch.countDown();
                }).start();
    
            }
            //等待计数器为0或者小于0执行await下面代码
            countDownLatch.await();
            //获取到当前计数器中的线程数量
            /*while (true){
                if(countDownLatch.getCount()<=5){
                    System.out.println(num);
                        break;
                    }
                }*/
            System.out.println(num);
    
    
        }
    }

     控制台效果:

      

    使用synchronized修改的方法叫做同步方法,保证该线程执行该方法的时候,其他线程只能等着;

      同步锁:1.非static方法,同步锁是this;

          2.static方法,使用当前方法所在的字节码对象;

      注意:synchronized不能修饰run方法,修饰之后,一个线程就执行了所有的功能,线程出现串行,相当于单线程;   

    ②同步代码块

     代码实现:

    package com.zn;
    
    import java.util.concurrent.CountDownLatch;
    
    public class UnsafeThread {
        //资源
        private static int num = 0;
        //计算线程数量
        private static CountDownLatch countDownLatch = new CountDownLatch(10);
    
        //对资源进行操作
        public static void inCreate(){
            synchronized (UnsafeThread.class){
                num++;
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            for (int i = 0; i < 10; i++) {
                new Thread(() -> {
                    for (int j = 0; j < 100; j++) {
                        inCreate();
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //每一个线程执行完毕,让计数-1
                    countDownLatch.countDown();
                }).start();
    
            }
            //等待计数器为0或者小于0执行await下面代码
            countDownLatch.await();
            //获取到当前计数器中的线程数量
            /*while (true){
                if(countDownLatch.getCount()<=5){
                    System.out.println(num);
                        break;
                    }
                }*/
            System.out.println(num);
    
    
        }
    }

     控制台效果:

      

      synchronized(同步锁){

            }

      对象的同步锁只有一个概念,可以想象在对象上标记一个锁;

      java程序运行可以使用任何对象作为同步监听对象,但是一般我们将当前i并发访问的共同资源(多个线程同步共享的资源对象)所谓同步监听对象;

      注意:在任何时候,只允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他线程只能在外面等;

    ReentrantLock(锁机制)

      锁是一种通过多个线程控制对共享资源的访问工具。通常,一个锁提供对共享资源的独占访问:在一个时间只有一个线程可以获取锁和所有访问共享资源,需要先获得锁;

     代码实现:

    package com.zn;
    
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class UnsafeThread {
        //资源
        private static int num = 0;
        //计算线程数量
        private static CountDownLatch countDownLatch = new CountDownLatch(10);
    
        //对资源进行操作
        private static ReentrantLock reentrantLock=new ReentrantLock();
        public static void inCreate(){
            //上锁
            reentrantLock.lock();
            num++;
            //释放搜
            reentrantLock.unlock();
        }
    
        public static void main(String[] args) throws InterruptedException {
            for (int i = 0; i < 10; i++) {
                new Thread(() -> {
                    for (int j = 0; j < 100; j++) {
                        inCreate();
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //每一个线程执行完毕,让计数-1
                    countDownLatch.countDown();
                }).start();
    
            }
            //等待计数器为0或者小于0执行await下面代码
            countDownLatch.await();
            //获取到当前计数器中的线程数量
            /*while (true){
                if(countDownLatch.getCount()<=5){
                    System.out.println(num);
                        break;
                    }
                }*/
            System.out.println(num);
    
    
        }
    }

    控制台效果:

      

  • 相关阅读:
    SQL Server 中的事务与事务隔离级别以及如何理解脏读, 未提交读,不可重复读和幻读产生的过程和原因
    微软BI 之SSIS 系列
    微软BI 之SSIS 系列
    微软BI 之SSIS 系列
    微软BI 之SSIS 系列
    微软BI 之SSIS 系列
    微软BI 之SSAS 系列
    微软BI 之SSRS 系列
    微软BI 之SSRS 系列
    配置 SQL Server Email 发送以及 Job 的 Notification通知功能
  • 原文地址:https://www.cnblogs.com/Zzzzn/p/12518116.html
Copyright © 2020-2023  润新知