zoukankan      html  css  js  c++  java
  • 多线程

    程序进入内存时,即变成一个进程,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位

    进程三个特征:

      独立性:进程是系统中独立存在的实体,拥有自己独立的资源,有自己私有的地址空间,没有经过进程本身允许的情况下,一个用户进程不能直接访问其他进程的地址空间

      动态性:进程与程序的区别在于,程序是静态的指令集合,而进程是正在系统中活动的指令集合,进程中加入的时间概念,进程有自己的生命周期和各种不同的状态,程序不具备这些

      并发性:多个进程可以在单个处理器上并发执行,多个进程间不会互相影响

    并发和并行:

      并行是同一时刻,多个指令在多个处理器上同时执行

      并发指在同一时刻只有一条指令执行,但多个进程指令被快速轮换,宏观上被同时执行

    并发方式:

      共用式的多任务操作策略

      抢占式多任务操作策略:效率更高更常用

    多线程:

      扩展了多进程,使得同一个进程可以同时并发处理多个任务。

      线程thread也被称为轻量级进程,是进程的执行单元,类似进程在操作系统中的地位,线程在程序中是独立的,并发的执行流,进程初始化后,主线程就被创立。

      线程是进程的组成部分,一个进程有多个线程,一个线程必须有一个父进程,线程可以有自己的堆栈,自己的程序计数器和局部变量,但不能拥有系统资源,与父进程的其他线程共享该进程的全部资源。

      线程是独立运行的,不知道进程中其他线程的存在,执行时抢占式的

      一个线程可以创建和撤销另一个线程,同一个进程中多个线程可以并发执行

    多线程编程优点:

      进程间不能共享内存,线程间非常容易

      系统创建进程要为该进程重新分配系统资源,但创建线程代价很小,多线程来实现多任务并发比多进程效率高

      Java内置了多线程功能支持,简化了多线程编程

    线程的创建和启动:

      1.继承Thread类创建线程类:

        定义Thread类的子类,并重新run()方法,创建Thread子类的实例,调用线程对象的start()启动线程

        Thread.currentThread:Thread类的静态方法,总是返回当前正在执行的线程对象

        getName():Thread类的实例方法,返回调用该方法的线程名称

        获得当前线程对象可以用this

        不能共享线程类的实例变量

      2.实现Runnable接口创建线程类:

        定义Runnable接口的实现类,并重新run()方法,创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread才是真正的线程对象

        SecondThread implements Runnable{}

        SecondThread st = new SecondThread();

        new Thread(st).start();

        new Thread(st,"第二个线程名称").start();

        获得当前线程对象必须使用Thread.currentThread()

        可以共享线程类的实例变量,因为线程对象只是线程类的target

      3.使用Callable和Future创建线程:

        java5后开始支持,模仿C#的任何方法都可以做线程执行体

        Runnable接口增强版,Callable接口提供一个call()方法,可以作为线程载体,功能比run()强大:

          call()可以有返回值,

          call()可以声明抛出异常

        提供一个Callbale对象作为Thread的target,线程执行体就是该Callable对象的call()方法

        Callable不是Runnable接口的子接口,所以Callable对象不能直接作为Thread的target,call()方法还有返回值,所以call()方法不是直接被调用,而是作为线程执行体被调用

        Future接口代表Callable接口里call()方法的返回值,提供了FutureTask实现类,该实现类实现了Futrue接口,也实现了Runnable接口,即可以作为target:

        Futrue接口里控制它关联的Callable任务的公有方法:

          boolean cancel(boolean mayInterruptRunning):试图取消该Future关联的Callable任务

          V get():返回Callable任务里的call()方法的返回值,调用该方法会导致程序阻塞,必须等子线程结束偶才会得到返回值

          V get(long timeout, TimeUnit unit):返回Callable任务里call()方法的返回值,让程序最多阻塞timeout和unit指定的时间,如果指定时间后Callable任务依然没有返回值,将会抛出TimeoutException异常

          boolean isCancelled():如果在Callable任务完成前取消,则返回true

          boolean isDone():如果Callable任务已完成,则返回true

        创建线程步骤:

          创建Callable接口的实现类,并实现call()方法,该call()仿作将作为线程执行体且该call()方法有返回值。再创建Callable实现类的实例,可以使用Lambda表达式创建Callable对象

          使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值

          使用FutureTask对象作为Thread对象的target创建并启动新线程

          调用FutureTask对象的get()方法来获得子线程执行后的返回值

        使用Lambda表达式来创建Callable对象,无需创建Callable实现类和对象:

          FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>()->{...});

    三种创建线程方式对比:

      通过继承Thread类后者实现Runnable、Callable接口都可以实现多线程,实现Runnable接口和实现Callable接口方式基本相同,只是Callable接口的方法有返回值,可以抛出异常。两者归为一种,实现接口方式。

      采用实现接口方式创建多线程优缺点:

      优点:

        线程类只是实现了Runnable接口或者Callable接口,还可以继承其他类

        多个线程可以共享一个target对象,非常适合多个相同线程来处理同一份资源,可以将CPU、代码和数据分开,形成清晰的模型,较好的体现了面向对象思想

      缺点:

        编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法

      采用继承Thread类创建多线程优缺点:

      优点:

        编写简单,访问当前线程直接使用this即可

      缺点:

        因为已经继承了Thread类,所以不能再继承其他父类

    线程的生命周期:

      有新建New,就绪Runnable,运行Running,阻塞Block,死亡Death五种状态

      线程多次在运行和阻塞状态切换

      线程被创建后,进入新建状态,和普通对象一样,仅仅被分配内存,初始化成员变量。

      调用start()方法后,进入就绪状态,java虚拟机会为其创建方法调用栈和程序计数器,此时并没有开始运行,只表示可以运行,何时开始运行取决于JVM里的线程调度器

      处于就绪态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态,处于运行状态的线程数量取决于几个处理器,最高不超多处理器数量

      抢占式的系统为每个线程分配时间片,用完则剥夺该线程资源,让其他线程获得机会。当发生如下情况时,线程将进入阻塞状态:

        线程调用sleep()方法主动放弃所占用的处理器资源

        线程调用了一个阻塞式I/O方法,在该方法返回之前,该线程被阻塞

        线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有,

        线程在等待某个通知notify

        程序调用了线程的suspend()方法将该线程挂起,这方法容易导致死锁,要避免

      阻塞线程在合适时候进入就绪状态,不是运行状态,必须等待线程调度器再次调用它

      对的解除阻塞状态,进入就绪状态措施:

        调用sleep()方法的线程经过的指定时间

        线程调用I/O方法已经返回

        线程成功获得了试图取得的同步监视器

        线程正在等待通知时,其他线程发出了一个通知

        处于挂起状态的线程被调用了resume()恢复方法

      线程有阻塞只能进入就绪状态,无法直接进入运行态

      就绪态和运行态之间的转换通常不受程序控制,而是有系统线程调度所决定,就绪态得到处理器资源,进入运行态,失去处理器资源或者调用yield()方法可以让运行态的线程转入就绪态

      线程结束后,进入死亡态:

        run()或者call()方法执行完成,线程正常结束

        线程抛出一个未捕获的Exception或者Error

        直接调用该线程的stop()方法来结束该线程,容易导致死锁,避免使用

      线程对象的boolean isAlive()方法:线程处于就绪,运行,阻塞三种状态,返回true,处于新建,死亡两种状态,返回false

      不能对死亡的线程调用start(),也不能对新建状态的线程调用两次start(),都会报报IllegalThreadStateException异常

    控制线程:

      join线程:

        Thread提供了让一个线程等待另一个线程完成的方法,当在某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到join()方法加入的线程执行完为止

        join通常由使用线程的程序调用,以将大问题分为小问题,每个小问题分配一个线程,每个小问题都得到处理后,再调用主线程来进一步操作

        三种重载方式:join(),join(long millis),join(long millis, int nanos)

      daemon线程:

        在后台运行,为其他线程提供服务,叫做后台线程,又被称为守护线程,精灵线程,JVM的垃圾回收线程就是典型的后台线程

        特征:如果所有前台线程死亡,后台线程会自动死亡

        调用Thread对象的setDaemon(true)方法可以将指定线程设置成后台线程,必须在start()前调用,否则IllegalThreadStateException

        isDaemon():判断指定线程是否为后台线程

      sleep线程:

        让当前线程暂停一段时间,并进入阻塞状态

        两种重载形式:static void sleep(long millis),static void sleep(long millis, int nanos)

        Thread.sleep(1000)

      yield让步线程:

        让当前线程暂停,但不进入阻塞,只是将线程转入就绪状态

        yield()只是让当前线程暂停一下,让线程调度器重新调度一次,完全有可能yield暂停后,又被调度出来重新执行

        yield后,只有优先级大于等于当前线程的,处于就绪状态的线程,才会获得执行机会

        Thread.yield()

      sleep和yield区别:

        sleep方法暂停线程后,会给其他线程执行机会,不会理会其他线程的优先级,但yield方法只会给优先级相同或者更高的线程执行机会

        sleep方法会将线程转入阻塞状态,直到经过阻塞时间才会进入就绪态,但yield方法强制将线程转入就绪态,因为完成可能yield暂停后,又立刻开始执行

        sleep方法声明抛出InterruptedException异常,所以调用sleep方法要么捕捉该异常,要么显式抛出该异常,但yield方法没有声明抛出任何异常

        sleep方法比yield方法具有更好的移植性,通常不建议使用yield方法来控制并发的线程执行

    改变线程优先级:

      Thread提供了setPriority(int pri),getPriority()方法来设置和返回指定线程的优先级,setPriority方法参数可以是1到10之间的整数,也可以使用以下三个静态常量:

        MAX_PRIORITY:10

        MIN_PRIORITY:1

        NORA_PRIORITY:5

      改变主线程优先级:

        Thread.currentThread().setPriority(6);

      具体优先级层数和系统有关,所以避免使用具体数字,使用三个静态常量

    线程同步:

      同步代码块:

        synchronized(obj){。。。同步代码块。。。},obj即为同步监视器,线程执行同步代码块之前,必须获得对同步监视器的锁定

        java允许使用任何对象作为同步监视器,但一般使用可能被并发访问的共享资源充当同步监视器

      同步方法:

        使用synchronized关键字修饰的方法

        对于synchronized修饰的实例方法,无需指定同步监视器,同步方法的同步监视器是this,也就是调用该方法的对象

        不要对线程安全类的所有方法都同步,只对会改变竞争资源的方法同步

        对于可变类,如果有两种运行环境:单线程和多线程,应该提供两种版本,StringBuilder和StringBuffer就是为了照顾单线程和多线程所提供的类,单线程用StringBuilder保证性能,多线程用StringBuffer保证安全

      释放同步监视器的锁定:

        程序无法显式的释放同步监视器的锁定

        以下情况会释放同步监视器的锁定:

          当前线程的同步方法,同步代码块执行结束,当前线程释放同步监视器

          当前线程的同步方法,同步代码块中遇到break,return终止了该代码块、方法的执行,当前线程释放同步监视器

          当前线程的同步方法,同步代码块出现了未处理的Error或Exception,导致异常结束,当前线程释放同步监视器

          当前线程的同步方法,同步代码块程序执行了同步监视器对象的wait()方法,当前线程暂停,当前线程释放同步监视器

        以下情况线程不会释放同步监视器:

          线程的同步方法,同步代码块时,程序调用了Thread.sleep(),Thread.yield()方法来暂停当前线程的执行,当前线程不会释放同步监视器

          线程的同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,当前线程不会释放同步监视器

      同步锁:

        java5开始,java提供了一种更强大的同步机制——通过显式定义同步锁对象Lock来实现同步

        Lock比起同步监视器更灵活,有Lock,ReadWriteLock两个根接口,并分别提供了ReentranceLock,ReentranceReadWriteLock两个实现类

        使用:

          class X{

            private final ReentranceLock lock = new ReentranceLock();

            public void m(){

              lock.lock();  //加锁

              try{...需要保证线程安全的代码...}

              finally{lock.unlock();}  //释放锁

            }  

          }

        通常建议使用try/finally来确保锁被释放

      死锁:

        有主线程和副线程,主线程有A对象锁,等待对B对象加锁,副线程有B对象锁,等待对A对象加锁,两个线程互相等待对方先释放,构成死锁

        不推荐使用suspend,容易死锁

    线程通信:

      传统的线程通信(对于synchronized实现同步):

        Object类提供的wait(),notify(),notifyAll()三个方法,注意不是Thread类的方法,是Object类

          对于synchronized修饰的同步方法,因为该类的默认实例this就是同步监视器,所以可以直接调用三个方法

          对于synchronized修饰的同步代码块,同步监视器是synchronized后面的对象,必须使用该对象来调用这三个方法

        wait():导致当前线程等待,知道其他线程调用该同步监视器的nofity方法或者notifyAll方法来唤醒线程,三种重载,有参数的即指定时间。会释放锁定

        notify():唤醒在此同步监视器上等待的单个线程,如果有多个线程在等待,则随机唤醒其中一个,只有在当前线程放弃锁定,即使用wait方法后,才可以执行被唤醒的线程

        nofifyAll():唤醒在此同步监视器上等待的所有线程,同样只有当前线程放弃锁定,才能执行被唤醒的线程

      使用Condition控制线程通信(对于Lock实现同步):

        使用Lock实现同步的线程,不存在隐式的同步监视器,所以无法使用wait,notify,notifyAll。对于此情况,java提供了Condition类来保持协调

        Condition可以让得到Lock对象但无法继续执行的线程释放Lock对象,也可以唤醒其他处于等待的线程

        Condition实例被绑定在Lock对象上,获得特定Lock实例的Condition实例,调用Lock对象的newCondition()方法即可:

          private final Condition con = lock.newCondition();

          cond.await();

        Condition类提供了三个方法:

          await():类似于wait(),导致当前线程等待,直到其他线程调用该Condition的signal或者signalAll

          signal():唤醒在此Lock对象上等待的单个线程,多个则随机唤醒一个,只有当前线程放弃对该Lock对象的锁定后,才可以执行被唤醒的线程

          signalAll():唤醒所有线程,只有当前线程放弃对该Lock对象的锁定后,才可以执行被唤醒的线程

      使用BlockingQueue阻塞队列控制线程通信:

        特征:当生产者线程试图向BlockingQueue中放入元素时,如果该队列已满,则该线程被阻塞;当消费者线程试图从BlockingQueue中取出元素时,如果该队列已空,则该线程被阻塞

        BlockingQueue提供两个支持阻塞的方法:

          put(E e):尝试把e放入BlockingQueue中,如满则阻塞

          take():尝试从BlockingQueue的头部取出元素,如空则阻塞

        BlockingQueue继承了Queue接口,可以使用Queue接口中的方法,分为三组:

          在队列尾部插入元素:add(E e), offer(E e), put(E e),当队列已满,分别会抛出异常,返回false,阻塞队列

          在队列头部删除并返回被删除的元素:remove(),poll(),take(),当队列为空,分别会抛出异常,返回false,阻塞队列

          在队列头取出但不删除元素:element(),peek(),当队列为空,分别会抛出异常,返回false

        BlockingQueue有五个实现类:ArrayBlockingQueue,LinkedBlockingQueue,PriorityBlockingQueue,SynchronizedBlockingQueue,DelayBlockingQueue

      使用管道pipe通信

      通过volatile使用while轮询

    线程组和线程的未处理异常:

      java使用ThreadGroup表示线程组,可以对同一批线程进行管理。没显式指定线程属于哪个线程组,则属于默认线程组。在默认情况下,子线程和创建他的线程处于同一个线程组。一旦加入线程组后,线程一直属于该线程组,直到死亡,不能更改

      ThreadGroup定义了非常有用的方法:void uncaughtException(Thread t, Throwable e),该方法可以处理该线程组内部任意线程所抛出的未处理异常

    线程池:

      与数据库连接池类似,线程池在系统启动时即创建大量的空闲线程,程序将一个Runnable或者Callable对象传给线程池,线程池就启动一个线程来执行他们的run()或者call()方法,执行结束后,线程不会死亡,而是返回线程池中成为空闲状态

      线程池可以有效控制系统中并发线程的数量,大量并发线程会导致系统性能急剧下降甚至崩溃,而线程池的最大线程参数可以控制系统中并发线程数不超过此数

      java5前,必须手动实现线程池,java5后,java支持内建线程池,新增了一个Executors工厂类生产线程池,该工厂类包含以下几个静态工厂方法创建线程池:

        ExecutorService newCachedThreadPool():创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程将会被缓存在线程池中

        ExecutorService newFixedThreadPool(int nThreads):创建一个可重用的,具有固定线程数的线程

        ExecutorService newSingleThreadPool():创建一个只有单线程的线程池,相当于调用newFixedThreadPoll(1)

        ScheduledExecutorService newScheduledThreadPool(int n):创建具有指定线程数的线程池,可以在指定延迟后执行线程任务

        ScheduledExecutorService newSingleThreadScheduledExecutor():相当于newScheduledThreadPoll(1)

      java8新增,利用多CPU并行能力,生成的是后台线程池:

        ExecutorService newWorkStealingPool(int parallelism):创建持有足够的线程的线程池来支持给定的并行级别,该方法会使用多个队列来减少竞争

        ExecutorService newWorkStealingPool():newWorkStealingPoll()的简化版本,按cpu个数来设置并行级别

      ScheduledExecutorService是ExecutorService的子类,可以在指定延迟后执行线程任务

      ExecutorService代表尽快执行线程,只要线程池中有空闲线程,就立即执行任务,程序只要将一个Runnable对象或者Callable对象(代表线程任务)提交给该线程池,线程池会尽快执行该任务

      三个方法:

        Futrue<?> submit(Runnable task):一个Runnable对象提交给线程池,线程池有空将会执行Runnable代表的任务。Future对象代表Runnable任务的返回值,但run()犯法没有返回值,所以Future对象在run方法执行结束后返回null

        <T>Future<T> submit(Runnable task, T result):其他同上,但result显式指定线程执行结束后的返回值,Futrue对象在run方法执行后返回result

        <T>Future<T> submit(Callable task):其他同上,Future代表Callable对象里call()方法的返回值

      使用线程池执行线程任务步骤:

        调用Executors类的静态工厂方法创建一个ExecutorService对象,该对象代表一个线程池

        创建Runnable实现类或者Callable实现类的实例,作为线程执行任务

        调用ExecutorService对象的submit()方法来提交线程执行任务

        当不想提交任务时,调用ExecutorService对象的shutdown()方法来关闭线程池

      通过线程池来执行任务,不通过启动线程:

        ExecutorService pool = Executors.newFixedThreadPoll(6);

        Runnable target = ()->{...}  //用Lambda表达式创建Runnable对象

        pool.submit(target);

        pool.submit(target);

        pool.shutdown();

      ForkJoinPool:将一个任务拆分成多个小任务,最终再合并

    线程相关类:

      ThreadLocal类:

        java5后引入了泛型支持:ThreadLocal<T>,可以简化多线程编程时的并发访问

        线程局部变量,就是为每一个使用该变量的线程都提供一个变量值的副本,使每一个线程都可以独立改变自己的副本,而不会和其他线程副本冲突,就好像每一个线程都完全拥有该变量一样

        三个public方法:

          T get():返回此线程局部变量中当前线程的值

          void remove():删除此线程局部变量中当前线程的值

          void set():设置此线程局部变量中当前线程副本的值

        private ThreadLocal<String> tl= new ThreadLocal<>(); tl.get();

        解决多线程对同一变量的访问冲突,从另一个角度解决并发访问,不是通过锁,从而不用进行同步

        不能代替同步机制,两者面向的问题领域不同。如果多个线程需要共享资源,以达到线程之间通信功能,使用同步机制,如果仅仅需要隔离多个线程之间的共享冲突,可以使用ThreadLocal

      包装线程不安全的集合:Collections的类方法

      线程安全的集合类:

        以Concurrent开头的集合类

        以CopyOnWrite开头的集合类

        VSHE四个类

    obj.wait()和Thread.sleep()都需要进行异常捕捉

  • 相关阅读:
    20165328《信息安全系统设计基础》实验二固件程序设计实验报告
    20165328《信息安全系统设计基础》第六周学习总结
    2018-2019-1 20165305 20165319 20165328 实验一 开发环境的熟悉
    2018-2019-1 20165328《信息安全系统设计基础》第四周学习总结
    2018-2019-1 20165328 《信息安全系统设计基础》第三周学习总结及实验报告
    20165328《信息安全系统设计基础》第一周总结
    20165358课程总结
    20165328 实验五《网络安全编程》实验报告
    20165218 2018-2019-1 《信息安全系统》第八章学习总结
    2018-2019-1 20165218 实验三 实时系统
  • 原文地址:https://www.cnblogs.com/zawjdbb/p/7142132.html
Copyright © 2011-2022 走看看