zoukankan      html  css  js  c++  java
  • 多线程的使用01

    1 为什么使用多线程

      1.1 发挥多核cpu的优势

        单核CPU上的多线程是假的多线程,同一时间处理器只会处理一段逻辑,只是在多个线程之间进行快速切换

        多核CPU才能实现真正的多线程,同时处理多个逻辑,充分利用CPU

      1.2 防止阻塞

        单核CPU不仅不能发挥多线程的优势,反而因为多个线程的切换,反而降低整体效率。

        但是单核CPU还是要使用多线程,防止阻塞。这样在一个线程卡死以后,不会影响其他线程的正常运行。

      1.3 便于建模

        对于一个大的而且复杂的任务,可以分为多个小的简单的任务进行程序建模。

    2 start()和run()方法的区别

      start()方法来启动线程,真正实现多线程。无需等待run()方法体的执行而直接执行下面的代码。

        通过调用Thread来的start()方法启动一个线程,此时该线程处于就绪(可运行)状态,但并没有运行,一旦得到cpu时间片,就开始执行run()方法,称为线程体。

      run()只是个普通的方法,并不会创建新的线程也不会执行调用线程的代码。

    3 CyclicBarrier和CountDownLatch的区别

      3.1 CyclicBarrier在某个线程中调用await()方法后,该线程就停止运行,知道所有的线程到到进入到barrier状态,执行完CyclicBarrier中定义的run()方法后再执行该线程之后的代码。

        而CountDownLatch,当线程运行到await()时,只要CountDownLatch中的数值减到0,该程序就会继续运行。

      3.2 CyclicBarrier的await()只是让当前线程停止运行,而CountDownLatch的await()会让所有的线程都进入停止运行

      3.3 CyclicBarrier可重复使用,而CountDownLatch不可重复使用

    public static void main(String[] args) {
            
            /**
             * CountDownLatch 的三个重要方法
             * await() :调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
             * await(long timeout, TimeUnit unit) :和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
             * countDown() :将count值减1,如果计数到达零,则释放所有等待的线程。
             */
            //定义一个计数器,并设置计数器的初始值
            final CountDownLatch latch=new CountDownLatch(2);
            
            /**
             * 通过它可以实现让一组线程等待至某个状态之后再全部同时执行。
             * 叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。
             * 我们暂且把这个状态就叫做barrier,当调用await()方法之后,线程就处于barrier了。
             */
            final CyclicBarrier barrier=new CyclicBarrier(2, ()->{
                System.out.println("所有到子线都进入到了barrier状态");
            });
            
            for(int i=0;i<2;i++){
                //定义一个子线程
                new Thread(()->{
                    System.out.println("子线程"+Thread.currentThread().getName()+"开始运行");
                    try {
                        Thread.sleep(3000);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("子线程"+Thread.currentThread().getName()+"运行结束");
                    
                    //将计数器的值减1
                    latch.countDown();
                    System.out.println("子线程"+Thread.currentThread().getName()+"减一之后");
                    
                    try {
                        Thread.sleep(2000);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    
                    System.out.println("子线程"+Thread.currentThread().getName()+"进入到barrier");
                    try {
                        //让该线程进入到barrier状态,暂停运行,等所有的线程都进入到barrier状态后,
                        //执行barrier中定义的run()方法后运行之后的额代码
                        barrier.await();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("子线程"+Thread.currentThread().getName()+"barrier之后的代码");
                    
                    
                }).start();
            }
            
            System.out.println("主线程"+Thread.currentThread().getName()+"开始运行");
            try {
                System.out.println("等待两个子线程运行结束");
                
                //将主线程挂起,等待计算器的值变为0后再向下执行
                latch.await();
                
                //将主线程挂起,等待2000毫秒,如果计数器的值还没有变为0,就向下执行
                //latch.await(2000,TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            System.out.println("两个子线程运行结束");
            System.out.println("主线程"+Thread.currentThread().getName()+"运行结束");
        }

    4 volatile关键字

      4.1 并发编程的三个概念:原子性,可见性和有序性

        4.1.1 原子性:即一个或多个操作要么全部执行且执行的过程不会被任何因素打断,要么就都不执行。

        4.1.2 可见性:指多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程就能立即看到修改的值。

        4.1.3 有序性:程序执行的顺序按照代码的先后顺序执行。

      4.2 java语音本身对并发编程的保证

        4.2.1 原子性

          在java中对基本数据类型的变量的读取和赋值操作是原子性操作的(简单的读取和赋值,变量间的赋值不是原子性的)

          java1.5之前可用通过Syschronized和Lock来实现,java1.5之后可以通过java.util.concurrent.atomic包下提供了一些原子操作类实现

        4.2.2 可见性

          通过volatitle实现可见性:

            被volatitie修饰的变量修改后会立即更新到主存中,其他线程需要读取时会从主存中读取

            而普通变量不能保证可见性的原因是,修改后什么时候更新到主存中是不确定的

          通过Syschronized和Lock保证可见性

            保证同一时刻只有一个线程获取锁然后执行同步代码,并在释放锁之前将变量的修改刷新到主存中

        4.2.3 有序性

          在java内存模型中,允许编译器和处理器对指令进行重排序,但重排序不会影响单线程程序的执行,却会影响到多线程并发执行的正确性。

          volatitle可以保证一定的有序性,但要保证完整的有序性还是需要使用Syschronized和Lock来实现

          java内存具有一些先天的有序性,即不通过任何手段就能保证的有序性,称为“happens-before原则”(先行发生原则)

      4.3 volatitle对并发编程三特性的影响

        4.3.1 可见性 保证了不同线程对共享变量的可见性

        4.3.2 原子性 volatitle不能保证对变量的任何操作都是原子性的

        4.3.3 有序性 禁止了指令重排序

          当执行到volatitle变量的读取和写操作时,其前面的操作肯定是全部执行完了,且结果已经对后面的操作可见,而在此后面的代码肯定没有执行到

      4.4 volatitle使用条件:保证原子性操作,才能保证使用volatitle关键字的程序在并发时能够正确执行

        4.4.1 对变量的写操作不依赖于当前值

        4.4.2 该变量没有包含在具有其他变量的不变式中

    5 sleep()和wait()的区别

      两者都可以让程序放弃CPU一段时间,不同点在于如果线程持有某个对象的监视器,sleep不会放弃这个对象的监视器,而wait()会放弃这个对象的监视器。

    6 ThreadLocal的作用

      使用ThreadLocal维护变量时,ThreadLocal为每个使用改变了的线程提供独立的变量副本,所以每一个线程都可以独立的改变自己的副本,而不影响其他线程中的副本。

    7 wait()和notify()/notifyAll()在放弃对象监视器的区别

      wait()会立即释放对象监视器

      notify()/notifyAll()会等待线程剩余代码执行完毕后才会释放对象监视器

    Integer lock=3;
    synchronized (lock) {
        //仅当对象obj的监视器被当前线程持有的时候才会返回true
        boolean b=Thread.holdsLock(lock);
        System.out.println("该线程拥有该对象监视器:"+b);
    }

    8 死锁

    Integer a=3;
            Integer b=4;
            new Thread(()->{
                    synchronized (a) {
                        System.out.println("线程"+Thread.currentThread().getName()+"获取到a锁,正在等待b锁");
                        try {
                            Thread.sleep(3000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        synchronized (b) {
                            System.out.println("线程"+Thread.currentThread().getName()+"获取到b锁");
                        }
                    }
            }).start();
            
            new Thread(()->{
                    synchronized (b) {
                        System.out.println("线程"+Thread.currentThread().getName()+"获取到b锁,正在等待a锁");
                        try {
                            Thread.sleep(3000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        synchronized (a) {
                            System.out.println("线程"+Thread.currentThread().getName()+"获取到a锁");
                        }
                    }
            }).start();
        }

      8.1 死锁问题定位

        8.1.1 jps获取当前虚拟机进程的pid

          

        8.1.2 jstack 打印堆栈信息

          

          

          两个线程各自持有对方在等待的锁,故而造成死锁。

        8.1.3 使用taskkill 命令结束该进程

          

      8.2 避免死锁的方法

        8.2.1 让程序每次至多只能获得一个锁

        8.2.2 尽量减少嵌套的加锁

        8.2.3 通过使用Lock类的tryLock方法去尝试获取锁,这个方法可以指定超时时间,超时后返回失败信息并释放锁。

    9 Thread.sleep(0)的作用

      由于java采用抢占式的线程调度算法,为了让优先级比较低的线程也能获取到CPU控制权,使用Thread.sleep(0)手动触发一次操作系统分配时间片的操作,来平衡CPU控制权。

        

  • 相关阅读:
    Python中Pickle模块的dump()方法和load()方法
    python的@classmethod和@staticmethod的区别和使用
    Python 正则表达式
    Python 函数
    Python time和datetime
    python 文件操作
    Python 集合的交差并补操作及方法
    python 字典相关函数和操作方法
    python 列表(list)操作及函数
    python 深浅拷贝
  • 原文地址:https://www.cnblogs.com/lifeone/p/7872313.html
Copyright © 2011-2022 走看看