java基础与进阶程序猿程序员

Thinking In Java并发

2016-04-13  本文已影响474人  srtianxia

***使用并发时要多疑而自信 ***

线程的运行态.png

Executor(p656)

使用执行器Executor来管理Thread对象,简化了并发编程,Executor在客户端和任务执行器之间提供了一个间接层,与客户端执行任务不同,这个中介对象执行任务,使用Executor,可以管理异步任务的执行,无须显示的管理线程的生命周期。

** 非常常见的情况是使用单个Executor用来创建和管理系统中的所有任务**


ExecutorService ------- 具有生命周期的Executor(具有execute shutdown等方法)

**可以直接创建一个Executor(new一个你所需要的线程池) 或者使用Executor的静态方法来创建一个ExecutorService **

Executors类型
从任务中产生返回值(p658)

实现Callable接口而不是Runnable接口,Callable是一个具有类型参数的泛型,要使用ExecutorServicesubmit方法来调用它,submit()返回一个Future对象。

休眠

影响任务行为的一种简单方法是调用sleep(),会让任务中止执行给定的时间

 TimeUnit.MILLISECONDS.sleep(100);  //毫秒
优先级/让步(p660)

优先级仅仅决定频率使用setPriority 设置优先级 (尽管jdk有十个优先级,但是唯一可移植的方法当调整优先级的时候,只使用MAX,NORM,MIN这三个级别)
任何重要的控制手段,都不能依赖于yield

后台线程(p662)

在程序运行时在后台提供一种通用服务的线程,而且这种线程并不是程序中不可或缺的一部分,因此,所有的非后台线程结束时,程序也中止了,同时会杀死进程中的所有后台线程。可以通过isDaemon()来确定一个线程是否为后台线程
必须在线程启动之前调用.setDaemon(true),才能启动后台线程

线程工厂

ThreadFactory 通过实现TreadFactory或者new 一个ThreadFactory对象,可以定制由Executor创建线程的属性,每个静态的ExecutorService创建方法都被重载为接受一个ThreadFactory对象,使用这个对象来创建新的线程。

加入一个线程(p669)

一个线程可以在其他线程之上调用join()方法,其效果是等待一段时间直到第二个线程结束才继续执行,如果某个线程在一个线程t上调用t.join,此线程将被挂起,直到目标线程t结束才恢复(即为t.isAlive()返回为假)。也可以在调用join的时候带上一个超时参数,这样如果目标线程在这段时间内还没有结束的话,join()方法总能返回。
对join方法的调用可以被中断,做法是在调用的线程上调用interrupt()方法,会使用到try-catch

捕获异常(p672)

由于线程的本质特性,使得你不能捕获从线程中逃逸的异常,一旦异常逃出任务的run()方法,它会向外传播到控制台。在se5中 可以使用Executor来解决这个问题。
Java se5之后出现了新接口Handler,它允许你在每一个Thread对象上都附着一个异常处理器Thread.UncaughtExceptionHandler.uncaughtException会在线程因未捕获的异常而临近死亡时被调用,为了使用它,我们可以创建一个新类型的线程工厂,它将在每个新创建的Thread对象附着一个Thread.UncaughtExceptionHandler ,再将这个线程工厂传给Executor

class ExceptionThread2 implements Runnable {
  public void run() {
    Thread t = Thread.currentThread();
    System.out.println("run() by " + t);
    System.out.println(
      "eh = " + t.getUncaughtExceptionHandler());
    throw new RuntimeException();
  }
}

class MyUncaughtExceptionHandler implements
Thread.UncaughtExceptionHandler {
  public void uncaughtException(Thread t, Throwable e) {
    System.out.println("caught " + e);
  }
}

class HandlerThreadFactory implements ThreadFactory {
  public Thread newThread(Runnable r) {
    System.out.println(this + " creating new Thread");
    Thread t = new Thread(r);
    System.out.println("created " + t);
    t.setUncaughtExceptionHandler(
      new MyUncaughtExceptionHandler());
    System.out.println(
      "eh = " + t.getUncaughtExceptionHandler());
    return t;
  }
}
解决共享资源竞争(p676)

关键字synchronized :当任务要执行到被synchronized保护的代码片段的时候,它将检查锁是否可用,然后获取锁,执行代码,释放锁。

共享资源一般是以对象的形式存在的内存片段,也可以是文件、输入/输出端口或者是打印机。要控制对共享资源的访问,得先把它包装进一个对象,然后把所有要访问的这个资源的方法标记为synchronized,如果某个任务处于一个对标记为synchronized的方法的调用中,那么在这个线程从该方法返回之前,其他所有要调用类中任何标记为synchronized方法的线程的都会被阻塞。

所有对象都自动的含有单一的锁(监视器)。当在对象上调用任意synchronized方法的时候,此对象都会被加锁,这时该对象上的其他synchronized方法只有等前一个方法调用完毕并释放锁之后才能被调用。所以,对于某一个特定的对象来说,其所有的synchronized方法共享一个锁,这可以被用来防止多个任务同时访问被编码为对象的内存。

使用并发时应当将域设置为private,否则synchronized关键字就不能防止其他 任务直接访问域,这样会产生冲突

一个任务多次获取锁(p677)

一个任务可以多次获得对象的锁,如果一个方法在同一个对象上调用的第二个方法,后者又调用了同一个对象上的另一个方法,就会出现这种情况 //????没懂。

针对类的锁

针对每一个类,也有一个锁(作为类的class对象的一部分),所以synchronized static方法可以在类的范围内防止对static数据的并发访问。

使用显示的Lock对象(p678)

Lock对象必须被显示的创建,锁定和释放,因此他和内建锁形式相比,代码缺乏优雅性,但是, 对于解决某些类型的问题来说,它更加灵活。

原子性与易变性(p680)

原子操作是不能被线程调度机制中断的操作,一旦操作开始,他一定可以在可能发生的切换到其他线程之前执行完毕

原子性可以应用于除long和double之外的所有基本类型之上的简单操作,对于读写除long和double之外的基本变量这样的操作,可以保证他们会被当做不可分的操作开操作内存,但是jvm可以将64位(long和double)的读取和写入当做两个分离的32位操作来执行,这就产生了一个读取和写入操作中间的上下文切换,从而导致不同的任务会看见不正确的结果的可能性(称为字撕裂),但是如果你定义long和double的时候使用volatile关键字,就会获得(简单赋值与返回操作的原子性)

volatile

volatile关键字还确保了应用中的可视性,如果你将一个域声明为volatile,只要对这个域产生了写操作,所有的读操作就都可以看见这个修改

原子类

AtomicInteger,AtomicLong, AtomicReference

线程本地存储(p691)

防止任务在共享资源上产生冲突的第二种方式是根除对变量的共享,线程本地存储是一种自动化机制,可以为使用相同变量的每一个不同的线程都创建不同的存储,因此,如果你有5个线程都要使用变量x所表示的对象,那线程本地存储就会生成5个用于x的不同存储块,主要是,他们可以将状态与线程关联起来。

在android的消息处理机制就使用了ThreadLocal

可以总结为一句话:ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。举个例子,我出门需要先坐公交再做地铁,这里的坐公交和坐地铁就好比是同一个线程内的两个函数,我就是一个线程,我要完成这两个函数都需要同一个东西:公交卡(北京公交和地铁都使用公交卡),那么我为了不向这两个函数都传递公交卡这个变量(相当于不是一直带着公交卡上路),我可以这么做:将公交卡事先交给一个机构,当我需要刷卡的时候再向这个机构要公交卡(当然每次拿的都是同一张公交卡)。这样就能达到只要是我(同一个线程)需要公交卡,何时何地都能向这个机构要的目的。
有人要说了:你可以将公交卡设置为全局变量啊,这样不是也能何时何地都能取公交卡吗?但是如果有很多个人(很多个线程)呢?大家可不能都使用同一张公交卡吧(我们假设公交卡是实名认证的),这样不就乱套了嘛。现在明白了吧?这就是ThreadLocal设计的初衷:提供线程内部的局部变量,在本线程内随时随地可取,隔离其他线程。

是编程思想的读书笔记,持续更新。

上一篇下一篇

猜你喜欢

热点阅读