在学习Java中多线程时要掌握的技巧

    作者:课课家教育更新于: 2016-03-07 14:22:35

    大神带你学编程,欢迎选课

      1、使用new Thread(runnableObj)方式,而非继承自Thread。

      对于耗时的任务,应放到线程中执行

      调用new Thread(runnable).start()方法启动线程,将会在线程中调用对应的runnalbe.run方法

    在学习Java中多线程时要掌握的技巧_Java继承_Java编程_课课家

      2、中断线程的方式:调用interrupt方式,会置位线程中断状态。检查这个中断位可判断线程是否被中断:Thread.currentThread().isInterrupted().

      currentThread可用于获取当前线程。

      sleep方法会清除中断状态并抛出InterruptedException。

      interrupt()会向线程发中断请求,并将中断状态置为true。

      interrupted()方法会检测是否线程被中断了,如果是会清除中断状态。

      线程状态:New,Runnable可运行(调用start后的状态,正在执行的线程状态以及等待执行的都是可运行),Blocked被阻塞,Waitting,Timed waiting计时等待,Terminated。

      被阻塞的情况:1、等待锁被释放;2、当线程等待另一个线程通知调度器一个条件时,它自己进入等待状态;方法带有超时参数,超时时会进入计时等待状态。如Thread.sleep,Object.wait(),Thread.join(),Lock.tryLock(),Condition.await()

      3、join()等待终止指定的线程,join(long mills)等待指定线程死亡或经过指定的毫秒数;

      Thread.State getState()获取线程的状态

      4、线程继承自父线程的优先级。用setProperty调整优先级,取值0~10,值越大优先级越高。

      static void yield()此方法导致当前执行线程处于让步状态,有同优先级线程存在时,会优先执行。

      5、this.setDaemon(true)使线程转换为守护线程,此方法必须在线程启动前调用。守护线程的用途是为其他线程提供服务。当只剩下守护线程时,虚拟机退出。守护线程应该永远不要去访问固定资源,如文件、数据库,因为它在任何时候都可能在一个执行过程中发生中断。

      6、线程的run不能抛出被检测的异常。未被检测的异常会导致线程终止,线程死亡前异常会被传递到一个用于未捕获异常的处理器。该处理器实现了Thread.UncaughtExcetptionHandler接口。没安装默认处理器的话,默认处理器为空。不为独立的线程安装处理器,其处理器就是ThreadGroup对象

      不要在Catch语句中处理可被传递的异常。

      javap -c -v Bank 用于对Bank.class进行反编译

      7、采用synchronized保证代码块的并发访问。

      ReentrantLock是一个锁对象,可用于控制并发。在一个公共访问代码中声明一个锁对象。在try之前获得锁,finally里释放锁。而非将锁对象作为线程的成员。

      锁是可重入的,线程可以重复获取已持有的锁。锁保持一个持有计数,在临界区内调用其他方法时,计数加1,所调用的方法退出时,锁减1.

      留心临界区的异常处理,不要因异常而导致跳出临界区。

      8、条件对象用于线程等待某个条件满足时执行操作。单纯的锁无法满足这种场景。

      一个锁可以关联多个条件变量Condition。bankLock.newCondition()创建锁关联的条件变量。在临界区内,while(condition no statisfy)condition.await();在循环体中调用是必须的。 通过调用条件变量中的await方法会进入该条件的等待集,阻塞,等待另一个线程调用同一条件上的signalAll方法(会解除所有等待这一条件的线程的阻塞,使得被解除阻塞的线程可以在当前线程退出阻塞之后,通过竞争实现对象的访问)。signal()方法则是随机解除一个阻塞的线程,这个方式需保证被解除阻塞的线程可以执行,否则会造成死锁。

      每个条件对象管理那些已经进入了被保护的代码段,但还不能运行的线程。

      每个对象内都有一把锁,要调用synchronized声明的对象成员方法,必须事先获得内部的对象锁。

      内部的对象锁只有一个关联的条件对象,wait会将线程添加到等待集中,notify/notifyAll会解除等待线程的阻塞状态。只能在synchronized内部使用wait和notify方法实现条件对象等待某个条件实现的功能。模式与await和signal相似。

      一般不要用synchronized和Condition,优先用synchronized。

      线程有本地缓存和寄存器,可以暂存内存中的值。如果使用锁来执行并发访问共享变量,编译器被要求在必要的时候刷新本地缓存(更新共享变量在本地的值)来保持锁的效应。

      volatile为同步访问提供了免锁机制,但是不保证原子性。如果共享变量除了赋值外,不完成其他操作,可以将其声明为volatile。

      atomic对象可以原子方式对变量做加减。

      9、public static final ThreadLocaldataFormat=

      new ThreadLocal(){

      protect SimpleDateFormat initialValue(){

      return new SimpleDateFormat(yyyy-MM-dd);

      }

      }

      10、锁超时: if(myLock.tryLock(100,TimeUnit.MILLISECONDS)){...} 超时时抛出InterruptedException,可用于打破死锁。

      myCondition.await(100,TimeUnit.MILLISECONDS)

      11、读线程多写线程少用ReentrantReadWriteLock。

      private rwLock = new ReentrantReadWriteLock();

      rLock=rwLock.readLock();该所被所有读线程共用,会排斥写操作在读之前用rLock.lock();结束后用rLock.unlock();

      wLock=rwLock.writeLock();排斥其他的读操作和写操作n。在写之前用wLock.lock();结束后用wLock.unlock();

      12、阻塞队列BlockingQueue(这个有几种继承类:LinkedBlockingQueue, LinkedBlockingDeque, ArrayBlockingQueue)就是一个生产者消费者模型中的用于存放共享数据的队列。该队列有考虑生产者消费者间的并发操作,处理生产者消费者速度的负载平衡。用offer添加一个元素、poll移出并返回队头、peek返回队头元素(这三种操作可指定操作的超时时间)。

      PriorityBlockingQueue优先队列。

      13、线程安全集合:阻塞队列、ConcurrentHashMap, ConcurrentSkipListMap, ConcurrentSkipListSet(这两个是线程安全的有序集,需事先Comparable接口), ConcurrentLinkedQueue. 这些集合的size()内部需要通过遍历来确定集合大小,这与一般的集合不同。

      这些集合返回的是弱一致性的迭代器,不一定能返回被修改后的值,但不会将同一个值返回两次,不会抛出ConcurrentModificationException。

      ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrentLevel);中参数是指定集合的初始容量,默认为16.concurrentLevel是并发写者线程的估计数目,表明可供多达16个写进程同时写,多了会阻塞剩下的线程。

      ConcurrentHashMap.putIfAbsent(k,v)是在该键不存在时插入kv对,否则返回键k对应的值。

      写数组的拷贝:

      CopyOnWriteArrayList和CopyOnWriteArraySet。写线程对底层数据做修改。如果读线程数大于写线程数,则这类集合很适合。读写可不用同步就可保持一致性。

      任何集合类通过同步包装器可变为线程安全的,通过给集合加入锁来实现的,这不推荐使用,因为还是需要在访问的代码中加入synchronized(synchashMap){...},推荐使用ConcurrentXXX集合:

      ListsyncArrayList = Collections.synchronizedList(new ArrayList());

      HashMaPSyncHashMap = Collections.synchronizedMap(new HashMap());

      对于经常被修改的数组列表,采用synchronized(arrayList){}会比CopyOnWriteArrayList性能更好。

      14、回调Callable和Future。 Runnalbe是无返回值无参数的异步方法,Callable和Future有返回值。Callable{ E call();}只有一个方法返回值类型为E的call().

      Future保存了异步计算结果,其get(long timeout,TimeUnit)可指定超时时间,超时时中断。如果Future还在执行,则isDone()返回false。cancle()方法可取消该运算。

      FutureTask包装器,可将Callable转化为Future何Runnable。

      CallablemyCompution=..;

      FutureTasktask= new FutureTask(myCompution);//构造一个既是Future又是Runnable的对象。

      Thread t= new Thread(task);

      t.start();

      ...

      Integer result=task.get();//对get调用会阻塞直到有可获得的结果或者超时为止。

      15、执行器Executor

      如果程序中有大量生命期很短的线程,最好使用线程池。线程池中包含了许多准备运行的空闲线程。将Runnable交给线程池,就会有一个线程调用Runnable.run()。run退出时线程又回到线程池等待下一次运行。

      使用线程池可减少并发线程数。

      Executor中有许多静态方法可用于构建线程池:newCachedThreadPool空闲线程被保存60s;newFixedThreadPool固定线程数且空闲线程一直保存,如果任务数大于线程数,未得到执行的任务会放到队列中等待空闲线程可用;newSingleThreadPool只有一个线程,用于顺序执行。这些方法返回实现了ExecutorService接口的ThreadPoolExecutor对象。

      可用ExecutorService接口中的Futuresubmit(Runnable,T result);Futuresubmit(CallableTask)方法提交Runnable和Callable对象给ExecutorService。

      线程池用完后调用shutdown()关闭线程池。

      步骤:

      a、Executors.newFixedThreadPool()

      b、submit提交Runnable或Callable对象;

      c、获取Future对象result,并调用result.get();

      d、关闭线程池shutdown()

      15、预定执行(定时任务)

      ScheduledExecutorService接口中有为预定执行或重复执行的任务设计的方法。

      ScheduledExecutorService ss=Executors.newScheduledThreadPool();

      schedule(Callable/Runnable,initDelayTime,TimeUnit);

      scheduleAtFixedRate(Callable/Runnable,initDelayTime,long period,TimeUnit);//每隔period执行一次

      16、控制任务组

      Executor可用于控制一组相关任务。

      invokeAny:多个任务执行,只要有一个执行完成就可以结束了。

      invokeAll:多个任务执行,所有任务都执行完成才可以结束。使用方式如下:

      List> tasks=...;

      List> results=executor.invokeAll(tasks);

      for(Futureres:results){//顺序遍历在第一个任务耗时很长的情况下会花很多时间进行等待,推荐用ExecutorCompletionService按结果产生顺序进行保存,更有效

      processFuturer(result.get());

      }

      17、fork-join框架(大任务划分为小任务,并行执行后merge)

      RecursiveTask用于有计算结果返回,RecursiveAction用于无计算结果返回。二者的compute用于生成并调用子任务并合并结果。

      class Counter extends RecursiveTask{//分治算法

      protect Integer compute(){

      if(to-from

      else{

      int mid=(low+high)>>1;

      Counter first=new Counter(from,mid);//拆分子任务

      Counter second=new Counter(mid+1,high);

      invokeAll(first,second);//调度所有子任务

      return first.join()+second.join();//合并结果

      }

      }

      }

      main(){

      ForkJoinPool pool=new ForkJoinPool();

      pool.invoke(counter);

      System.out.println(counter.join());

      }

      18、同步器

      提供了实现线程间相互合作的类:

      CyclicBarrier:等待一定数目的线程都到达一个公共屏障,在执行一个处理屏障的动作。

      CyclicBarrier barrier=new CyclicBarrier(numThreads,barrierAction);//当numThreads个线程都到达屏障时执行barrierAction

      Runnable{

      public void run(){

      doSomething...

      barrier.await();//等待直到屏障打开,可设置超时时间,超时时抛异常,会导致其他等待await的线程都抛BrokenBarrierException异常。

      ...

      }

      }

      CountDownLatch:允许等待直到计数器为0。用于当一个或多个线程需等到指定数目事件发生时。这是一次性的,只可用一次。

      Exchanger:允许两个线程在要交换对象准备好时交换对象。两个线程在同一个数据结构的两个不同实例上,一个向实例添加数据,另一个用于清除数据

      SEMaphore:允许线程等待直到被允许执行为止。用于限制访问资源的线程数。

      信号量管理了许多permits,用于限制通过的线程数量。信号量仅维护一个计数。其他线程通过调用release()释放许可permits。

      SynchronousQueue:允许一个线程把对象交给另一个线程。在没显式同步时,两个线程将一个对象从一个线程传到另一个线程。

课课家教育

未登录