zoukankan      html  css  js  c++  java
  • 笔记:多线程

    多线程程序在较低的层次上扩展了多任务的概念:一个程序同时执行多个任务,通常每个任务称为一个线程(thread),他是线程控制的简称,可以同时运行一个以上线程的程序称为多线程程序(multithreaded);多线程和多进程有哪些区别呢,本质的区别在于每个进程拥有自己的一整套变量,而线程则是共享数据,Java中启动一个线程的代码如下:

    // 线程任务的具体实现接口

        public interface Runnable {

    public abstract void run();

        }

    // 启用新线程执行任务

        Runnable runnable = new MyRunnable();

        Thread newThread = new Thread(runnable);

       

        newThread.start();

    • 中断线程

      当线程的run方法执行方法体中最后一条语句后,并经由执行return语句返回时,或者出现了在方法中没有捕获的异常时,线程将终止;还有一种可以强制线程终止的方法,interrupt 方法可以用来请求终止线程,示例如下:

      while(Thread.currentThread().isInterrupted == false && more work to do){

          do more work

      }

      如果线程被阻塞,就无法检测中断状态,如果在一个阻塞的线程(sleep或wait)上调用interrupt方法时,阻塞调用将会抛出InterruptedException异常,因此标准的线程run方法应具有如下形式:

      public void run(){

          try{

              ...

              while(Thread.currentThread().isInterrupted() == false && more work to do){

                  // do more work

              }

                

              // 返回结果

          }

          catch(InterruptedException e){

              // 线程被阻塞,需要中断线程返回

          }

          finally{

              // 释放资源

          }

      }

      如果在每次工作迭代之后都调用sleep方法(或者其他可中断方法),isInterrupted检测将没有任何用处,如果在中断状态被置为时调用sleep方法,他不会休眠,相反他将清除这个状态,并抛出 InterruptedExcpetion。

    • 线程状态
    • 新创建(New)

      当使用New操作符创建一个新线程时,该线程还没有开始运行,这时他的状态是新创建(New)。

    • 可运行(Runnable)

      一旦调用start方法,线程将处于可运行(Runnable)状态,一个可运行的线程可能正在运行也可能没有运行,这取决与操作系统给线程提供运行的时间。

    • 被阻塞(Blocked)、等待(Waiting)和计时等待(Timed Waiting)

      当线程处于被阻塞或等待状态时,线程暂时不活动,不运行任何代码且消耗最小的资源,知道线程调度器重新激活他。

      • 被阻赛(Blocked):当一个线程试图获取一个内部的对象锁(不是 java.util.concurrent库的锁),而该锁被其他线程持有,则该线程进入阻塞状态,当所有线程释放该锁,并且线程调度器允许本线程持有该锁时,该线程将变成非阻塞状态。
      • 等待(Waiting):当线程等待另一个线程通知调度器一个条件时,该线程将进入等待状态,例如,在调用 Object.wait 方法或者Thread.join方法,或者等待 java.util.concurrent库的Lock或Condition时,就会出现这个情况。
      • 计时等待(Timed Waiting):有几个方法具有超时参数,调用他们导致线程进入计时等待(Timed Waiting),这一状态将一直保持到超时期满或者接收到适当通知,带有超时参数的方法有 Thread.sleep、Object.wait、Thread.join、Lock.tryLock以及 Condition.await方法。
    • 被终止(Terminated)

      当线程run方法正常退出而自然死亡;因为一个没有捕获的异常终止了run方法而意外死亡,此时线程状态为被终止。

    • 线程优先级

      在Java程序中,每个线程有一个优先级,默认情况下,一个线程继承他的父线程优先级,可以用 setPriority方法提高或降低任何一个线程优先级,可以设置为MIN_PRIORITY(Thread类中定义为1)与MAX_PRIORITY(Thread类中定义为10)之间的任何值,NORM_PRIORITY(默认优先级)被定义为5。每当线程调度器有机会选择新线程时,他首先选择具有高优先级的线程,但是线程优先级时依赖与系统的,Java的线程优先级被映射到宿主主机平台的优先级上,优先级个人数也许更多,也许更少。

    • 守护线程

      守护线程的唯一用途时为其他线程提供服务,如果只剩下守护线程,虚拟机都退出了,因此,守护线程应该永远不去访问固有资源,如文件、数据库,因为他任何时候甚至在一个操作的中间发生中断,可以通过调用Thread实例的 setDaemon(true) 来将线程转换为守护线程,必须在线程启动之前调用。

    • 未捕获异常处理器

      线程的run方法不能抛出任何被检测的异常,但是,不被检测的异常会导致线程终止,在这种情况下,线程就死亡了,不需要任何catch子句来处理可以被传播的异常,就在线程死亡之前,异常被传播到一个用于未捕获异常的处理器,该处理器必须实现 Thread.UncaughtExceptionHandler接口的类,该接口只有一个方法定义:

        public interface UncaughtExceptionHandler {

              void uncaughtException(Thread t, Throwable e);

        }

      可以使用 setUncaughtExceptionHandler方法未任何线程安装一个处理器,也可以使用Thread类的静态方法 setDefaultUncaughtExceptionHandler 为所有线程安装一个默认处理器。

    • 同步锁对象

      有两种机制防止代码块受并发访问的干扰,Java语言提供一个synchronized 关键字达到这目的,并且在 Java SE5.0 引入了 ReentrantLock类(重入锁),synchronized关键字自动提供一个锁以及相关条件,使用 ReentrantLock类的代码示例如下:

      package org.drsoft.mybatisExamples.example_thread;

         

      import java.util.concurrent.locks.Lock;

      import java.util.concurrent.locks.ReentrantLock;

         

      public class Bank {

          private Lock bankLock = new ReentrantLock();

         

          public void transfer(int form, int to, int amount) throws InterruptedException {

              bankLock.lock();

              try {

                  System.out.println("ThreadID=" + Thread.currentThread().getId() + " Form=" + form + " To=" + to+ " Amount=" + amount);

                  Thread.sleep((int) (10 * Math.random()));

              } finally {

                  bankLock.unlock();

              }

          }

      }

    • 条件锁对象

      通常,线程进入临界区,却发现在某一条件满足之后才能执行,要使用一个条件对象来管理哪些已经获得了一个锁,当是却不能做有用工作的线程,一个锁对象可以有一个或者多个相关的条件对象,可以使用 newCondition方法获得一个条件,可以调用条件的 await方法阻塞线程,直到另一个线程的调用同一个条件上的 signalAll方法重新激活因为这个条件等待的所有线程,示例代码如下:

      package org.drsoft.mybatisExamples.example_thread;

         

      import java.util.concurrent.locks.Condition;

      import java.util.concurrent.locks.Lock;

      import java.util.concurrent.locks.ReentrantLock;

         

      public class Bank {

          private Lock bankLock = new ReentrantLock();

          private Condition toCondition;

         

          public Bank() {

              toCondition = bankLock.newCondition();

          }

         

          public void transfer(int form, int to, int amount) throws InterruptedException {

              bankLock.lock();

              try {

                  if (to == 2) {

                      System.out.println("ThreadID=" + Thread.currentThread().getId() + " await");

                      toCondition.await();

                  }

                  System.out.println("ThreadID=" + Thread.currentThread().getId() + " Form=" + form + " To=" + to+ " Amount=" + amount);

                  toCondition.signalAll();

                  Thread.sleep((int) (10 * Math.random()));

              } finally {

                  bankLock.unlock();

              }

          }

      }

    • synchronized关键字

      使用 synchronized 关键字声明在方法中,将保护整个方法,也就是说,要调用该方法,线程必须获得内部得对象锁,代码示例如下:

      public synchronized void method(){

          //method body

      }

      等价与如下代码:

      public void method(){

          this.intrinsicLock.lock();

          try{

                 // method body

          }

          finally{

               this.intrinsicLock.unlock();

          }

       }

      内部锁只有一个相关条件,wait方法添加一个线程到等待集,notifyAll或notify方法解除等待线程得阻塞状态,可以使用 synchronized关键字实现前面得代码:

      package org.drsoft.mybatisExamples.example_thread;

         

      public class Bank {

         

          public synchronized void transfer(int form, int to, int amount) throws InterruptedException {

              if (to == 2) {

                  System.out.println("ThreadID=" + Thread.currentThread().getId() + " await");

                  wait();

              }

              System.out.println(

                      "ThreadID=" + Thread.currentThread().getId() + " Form=" + form + " To=" + to +

      " Amount=" + amount);

              notifyAll();

              Thread.sleep((int) (10 * Math.random()));

          }

      }

      将静态方法声明为 synchronized也是合法的,如果调用这种方法,该方法获得相关的类对象的内部锁,因此,没有其他线程可以调用同一个类的这个或任何其他的同步静态方法。

    • 同步阻塞

      每一个Java对象有一个锁,线程可以通过调用同步方法获得锁,还有另一种机制可以获得锁,通过进入一个同步阻塞,当线程进入如下形式的阻塞:

      synchronized(obj){

          //critical section

      }

      有时候程序员使用一个对象的锁来实现额外的原子操作,实际上称为客户端锁定。

    • volatile关键字

      如果向一个变量写入值,而这个变量接下来可能会被另一个线程读取,或者,从一个变量读值,而这个变量可能是之前被另一个线程写入的,此时必须使用 volatile关键字标识,volatile关键字为实例域的同步访问提供了一种免锁机制,如果声明一个域为 volatile,那么编译器和虚拟机就知道这该域是可能被另一个线程并发更新的,注意:volatile关键字不能提供原子性操作。

    • final变量

      还有一种方法可以安全的访问一个共享域,即这个域声明为 final时,例如如下代码:

      final Map<String,Double> accountMap = new HashMap<String,Double>();

      如果不声明为 final,就不能保证其他线程看到的是实例化的值,有可能看到的只是 null,而不是新构造的HashMap。

    • 原子性

      java.util.concurrent.atomic包中有很多类使用了高效的机器指令来保证其他操作的原子性,例如,AtomicInteger类,提供了方法 incrementAndGet decrementAndGet方法,他们分别以原子方式将一个整数自增或自减,另外这个包中还提供了 AtomicBoolean、AtomicLong和AtomicReference等相关类。

    • 线程局部变量

      有时候可能避免共享变量,使用ThreadLocal辅助类为各个线程提供各自的实例,示例代码如下:

      package org.drsoft.mybatisExamples.example_thread;

         

      import java.text.SimpleDateFormat;

      import java.util.Date;

         

      public class ThreadLocalExample {

          public static final ThreadLocal<SimpleDateFormat> dataFormat = new ThreadLocal<SimpleDateFormat>(){

              protected SimpleDateFormat initialValue(){

                  return new SimpleDateFormat("yyyy-MM-dd");

              }

          };

            

          public static void main(String[] args) {

              String dateStamp = dataFormat.get().format(new Date());

              System.out.println("dateStamp="+dateStamp);

          }

      }

      在一个指定的线程首次调用 get 方法时,会调用initialValue方法进行初始化,在此之后,get方法会返回属于当前线程的哪个实例。

    • 读写锁(ReentrantReadWriteLock)

      如果很多线程从一个数据结构读取数据,而很少的线程修改其中的数据的话,读写锁时十分有用的,在这种情况下,允许读数据线程访问共享时合适的,写线程依然必须时互斥访问的,下面是使用读写锁的必要步骤:

      • 构造一个ReentrantReadWriteLock对象:

        private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

      • 抽取读锁和写锁:

        private Lock readLock = rwl.readLock();

        private Lock writeLock = rwl.writeLock();

      • 对所有的获取方法加读锁:

                    public double getTotalBalance(){

                        realLock.lock();

                        try{

                        // 读取数据代码

                        }

                        finally{realLock.unlock();}

                    }

      • 对所有修改方法加写锁:

                    public void transfer(…){

                        writeLock.lock();

                        try{

                        // 写入数据代码

                        }

                        finally{writeLock.unlock();}

                    }

    • Callable Future

      前面创建线程对象时,使用的是 Runnable接口,该接口封装异步运行的任务,是没有参数和返回类型的异步返回;Callable 和 Runnable类似,但Callable是有返回值,该接口只有一个方法 call,接口定义如下:

      public interface Callable<V> {

              V call() throws Exception;

      }

      其类型参数是call方法的返回值的类型;Futrue保持异步计算的结果,可以启动一个计算,将Futrue对象交给线程,Futrue 对象的所有者在结果计算好之后就可以获取他,Futrue接口定义如下:

      public interface Future<V> {

              boolean cancel(boolean mayInterruptIfRunning);

              boolean isCancelled();

              boolean isDone();

              V get() throws InterruptedException, ExecutionException;

              V get(long timeout, TimeUnit unit)

                      throws InterruptedException, ExecutionException, TimeoutException;

      }

      get方法的调用被阻塞,可以设置一个超时时间,如果计算完成则会获取计算结果,如果在超时时间到达后,则会抛出 TimeoutException 异常,如果调用方法被中断,则会抛出 InterruptedException 异常,调用实例如下:

              LogCallable callable = new LogCallable("高优先级线程");

              FutureTask<Integer> futureTask = new FutureTask<Integer>(callable);

              Thread newMaxPriorityThread = new Thread(futureTask);

              newMaxPriorityThread.setPriority(Thread.MAX_PRIORITY);

              newMaxPriorityThread.setDaemon(false);

              newMaxPriorityThread.start();

         

              int val = futureTask.get();

              System.out.println("Thread value is " + val);

         

         

       

       

       

       

       

       

       

  • 相关阅读:
    译文高效的JavaScript.
    JavaScript 全半角转换
    js表单验证
    Js事件大全
    Javascript下的urlencode编码解码方法decodeURIComponent()
    加速Javascript:DOM操作优化
    javascript验证日期的函数
    javascript里面的小数计算出现近似值的解决办法
    【busybox】busybox使用总结 01
    Assemble 汇编语言的种类
  • 原文地址:https://www.cnblogs.com/li3807/p/6765039.html
Copyright © 2011-2022 走看看