多线程

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

进程三个特征:

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

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

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

并发和并行:

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

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

并发方式:

  共用式的多任务操作策略

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

多线程:

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

  线程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()都需要进行异常捕捉

posted on 2017-07-09 17:33  zawjdbb  阅读(135)  评论(0编辑  收藏  举报

导航