《java编程思想》第二十一章 并发

Thread.yield():对线程调度器(Java线程机制的一部分,可以将CPU从一个线程转移给另一个线程)的一种建议,它是在说“我已经执行完生命期中最重要的部分了,此刻正是切换给其他任务执行一段时间的大好时机”,即对线程的一种让步,暂停当前正在执行的线程,并执行其他线程。

 

>>>使用Executor<<<

Java.util.concurrent包中的执行器(Executor)将为你管理Thread对象,从而简化了并发编程。

 

Executors:Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable类的工厂和实用方法。

 

Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。调用 execute将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。注意,可以使用 ThreadPoolExecutor构造方法创建具有类似属性但细节不同(例如超时参数)的线程池。

 

public class LiftOff implements Runnable {

  protected int countDown = 10; // Default

  private static int taskCount = 0;

  private final int id = taskCount++;

  public LiftOff() {}

  public LiftOff(int countDown) {

    this.countDown = countDown;

  }

  public String status() {

    return "#" + id + "(" + (countDown > 0 ? countDown : "Liftoff!") + "), ";

  }

  public void run() {

    while(countDown-- > 0) {

      System.out.print(status());

      Thread.yield();

    }

  }

}

 

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

public class CachedThreadPool {

  public static void main(String[] args) {

    ExecutorService exec = Executors.newCachedThreadPool();

    for(int i = 0; i < 5; i++)

      exec.execute(new LiftOff());

    exec.shutdown();

  }

}

/*

#2(9), #0(9), #4(9), #1(9), #3(9), #2(8), #0(8), #4(8), #1(8), #3(8), #2(7), #0(7), #4(7), #1(7), #3(7), #2(6), #0(6), #4(6), #1(6), #3(6), #2(5), #0(5), #4(5), #1(5), #3(5), #2(4), #0(4), #4(4), #1(4), #3(4), #2(3), #0(3), #4(3), #1(3), #3(3), #2(2), #4(2), #1(2), #3(2), #2(1), #4(1), #1(1), #3(1), #2(Liftoff!), #4(Liftoff!), #1(Liftoff!), #3(Liftoff!), #0(2), #0(1), #0(Liftoff!),

*/

 

 

Executors. newFixedThreadPool (int nThreads):可一次性预先执行代价高昂的线程分配,因而也就可以限制线程数量。不用为每个任务都固定地付出创建线程的开销。

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

public class FixedThreadPool {

  public static void main(String[] args) {

    // Constructor argument is number of threads:

    ExecutorService exec = Executors.newFixedThreadPool(5);

    for(int i = 0; i < 5; i++)

      exec.execute(new LiftOff());

    exec.shutdown();

  }

}

/*

#3(9), #4(9), #0(9), #2(9), #1(9), #3(8), #4(8), #0(8), #2(8), #1(8), #3(7), #4(7), #0(7), #2(7), #3(6), #4(6), #0(6), #2(6), #3(5), #4(5), #0(5), #2(5), #3(4), #4(4), #0(4), #2(4), #3(3), #4(3), #0(3), #2(3), #3(2), #4(2), #0(2), #2(2), #3(1), #4(1), #0(1), #2(1), #3(Liftoff!), #4(Liftoff!), #0(Liftoff!), #2(Liftoff!), #1(7), #1(6), #1(5), #1(4), #1(3), #1(2), #1(1), #1(Liftoff!),

*/

 

Executors. newSingleThreadExecutor (int nThreads):确保任意时刻在任何都只有唯一的任务在运行,你不需要在共享资源上处理同步,可以让你省去只是为了维持某些事物的原型而进行的各种协调努力。

public class SingleThreadExecutor {

  public static void main(String[] args) {

    ExecutorService exec =

      Executors.newSingleThreadExecutor();

    for(int i = 0; i < 5; i++)

      exec.execute(new LiftOff());

    exec.shutdown();

  }

}

/*

#0(9), #0(8), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #0(Liftoff!), #1(9), #1(8), #1(7), #1(6), #1(5), #1(4), #1(3), #1(2), #1(1), #1(Liftoff!), #2(9), #2(8), #2(7), #2(6), #2(5), #2(4), #2(3), #2(2), #2(1), #2(Liftoff!), #3(9), #3(8), #3(7), #3(6), #3(5), #3(4), #3(3), #3(2), #3(1), #3(Liftoff!), #4(9), #4(8), #4(7), #4(6), #4(5), #4(4), #4(3), #4(2), #4(1), #4(Liftoff!),

*/

 

 

 

 

Thread.sleep(100);等同于TimeUnit.MILLISECONDS.sleep(100);

 

Daemom线程:后面线程不属于程序中不可缺少的部分,因此,当所有非后台线程结束时,程序也就终止了,同时会杀死进程中的所有后台线程。返过来说,只要有任何非后台线程还在运行,程序就不会终止。

 

必须在线程启动之前调用setDaemon()方法,才能把它设置为后台线程。

 

如果一个线程是后台线程,那么它创建的任何线程将被自动设置成后台线程。

 

后台线程在不执行finally子句的情况下就会终止其run()方法,即后台线程的finally子句不一定执行。

 

在构造器中启动线程可能会有问题,因为线程可能会在构造器结束之前开始执行,这意味着该线程能够访问处于不稳定状态的对象。

 

异常不能跨线程传播给main(),所以你必须在了本地处理所有在线程内部产生的异常。

public class ExceptionThread implements Runnable {

  public void run() {

    throw new RuntimeException();

  }

  public static void main(String[] args) {

        try {

          ExecutorService exec =

            Executors.newCachedThreadPool();

          exec.execute(new ExceptionThread());

        } catch(RuntimeException ue) {

          // 这句将不会被执行,因为线程的异常是不会传递到调用它的线程的

          System.out.println("Exception has been handled!");

        }

      }

}

 

Thread.UncaughtExceptionHandler是Java SE5中的新接口,它允许你在每个Thread对象上都附着一个异常处理器。Thread.UncaughtExceptionHandler.uncaughtException()会在因未捕获的异常而临近死亡时被调用。为了使用它,我们创建了一个新类型的ThreadFactory,它将在每个新创建的Thread对象上附着一个Thread.UncaughtExceptionHandler,并将这个工厂传递给Exceutors创建新的ExcecutorService方法:

// 线程

class ExceptionThread2 implements Runnable {

  public void run() {

    Thread t = Thread.currentThread();

    System.out.println("run() by " + t);

    System.out.println("1.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("2.eh = " + t.getUncaughtExceptionHandler());

    return t;

  }

}

 

public class CaptureUncaughtException {

  public static void main(String[] args) {

    ExecutorService exec = Executors.newCachedThreadPool(

      new HandlerThreadFactory());

    exec.execute(new ExceptionThread2());

  }

} /*

HandlerThreadFactory@1a758cb creating new Thread

created Thread[Thread-0,5,main]

2.eh = MyUncaughtExceptionHandler@69b332

run() by Thread[Thread-0,5,main]

1.eh = MyUncaughtExceptionHandler@69b332

caught java.lang.RuntimeException

*/

 

 

如果你知道将要在代码中处处使用相同的异常处理器,那么更简单的方式是在Thread类中设置一个静态域,并将这个处理器设置为默认的未捕获异常处理器:Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler());

 

>>>使用Lock对象<<<

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

  private Lock lock = new ReentrantLock();

  public int next() {

    lock.lock();

    try {

      //…

    } finally {

      lock.unlock();

    }

  }

使用lock()和unlock()方法在next()内部创建了临界资源。还可以尝试获取锁:

  private ReentrantLock lock = new ReentrantLock();

  public void untimed() {

    boolean captured = lock.tryLock();

    try {

      //…

    } finally {

      if(captured)

        lock.unlock();

    }

  }

 

>>>使用volatile对象<<<

原子操作是不能被线程调试机制中断的操作,一旦开始操作,那么它一定会在切换到其他线程前执行完毕。

 

原子操作可以应用于除long和double之外的所有基本类型之上的“简单操作”,对于读取和写入除long和double之外的基本类型变量这种的操作,可以保证它们会被当作原子操作来操作内存。但是JVM可以将64位(long和double变量)的读取和写入当作两个分离的32位操作来执行,这就可能会产生了在一个读取和写入操作中间切换线程,从而导致不同的线程看到不正确结果的可能性。但是,当你定义long或double变量时,如果使用volatile关键字,就会获得(简单的赋值与返回操作)原子性,注:在Java SE5之前,volatile一直未能正确的工作。

 

volatile关键字还确保了应用中的可视性,如果你将一个域声明为volatile的,那么只要对这个域产生了写操作,那么所有的读操作就都可以看到这个修改,即便使用了本地缓存,情况确实如此,volatile域会立即被写入到主存中。

 

在非volatile域上的操作没有刷新到主存中去,因此其他读取该域的线程将不能必看到这个新值。因此,如果多个线程同时访问了某个域,那么这个域就应该是volatile的,否则,这个域应该只能由同步来访问,同步也会导致向主存中刷新,因此如果一个域完全由synchronized方法或语句块来保护,那就不必将其设置为volatile了。

 

什么才属于原子操作呢?对域中的值做赋值和返回操作都是原子性的。但i++; i+=2; 这样的操作肯定不是原子性的,即线程有可能从语句的中间切换。下面来证明i++在java里不是原子性操作的:

class SerialNumberGenerator {

       private static volatile int serialNumber = 0;

 

       public static /* synchronized */int nextSerialNumber() {

              // 不是线程安全,因为i++在Java里不是原子操作,

              // 即使將serialNumber设置成了volatile

              return serialNumber++;

       }

}

 

class CircularSet {

       private int[] array;

       private int len;

       private int index = 0;

 

       public CircularSet(int size) {

              array = new int[size];

              len = size;

              // 初始化为-1

              for (int i = 0; i < size; i++) {

                     array[i] = -1;

              }

       }

 

       public synchronized void add(int i) {

              array[index] = i;

              // 如果数组满后从头开始填充,好比循环数组:

              index = ++index % len;

       }

 

       public synchronized boolean contains(int val) {

              for (int i = 0; i < len; i++) {

                     if (array[i] == val) {

                            return true;

                     }

              }

              return false;

       }

}

 

public class SerialNumberChecker {

       private static final int SIZE = 10;

       private static CircularSet serials = new CircularSet(1000);

       private static ExecutorService exec = Executors.newCachedThreadPool();

 

       static class SerialChecker implements Runnable {

              public void run() {

                     while (true) {

                            int serial = SerialNumberGenerator.nextSerialNumber();

                            if (serials.contains(serial)) {// 如果数组中存在则退出

                                   System.out.println("Duplicate: " + serial);

                                   System.exit(0);

                            }

                            serials.add(serial);// 如果不存在,则放入

                     }

              }

       }

 

       public static void main(String[] args) throws Exception {

              SerialChecker sc = new SerialChecker();

              // 启动10线程

              for (int i = 0; i < SIZE; i++) {

                     exec.execute(sc);

              }

       }

}

 

 

 

 

public class Increament extends Thread {

       public static volatile int x = 0;

 

       public void run() {

//            synchronized (Increament.class) {

                     // x++与 x = x + 1都不是原子操作

                     x++;

                     // x = x + 1;

//            }

       }

 

       public static void main(String[] args) throws Exception {

             

              Thread threads[] = new Thread[10000];

              for (int i = 0; i < threads.length; i++) {

                     threads[i] = new Increament();

              }

 

              for (int i = 0; i < threads.length; i++) {

                     threads[i].start();

              }

              for (int i = 0; i < threads.length; i++) {

                     // 等待计算线程运行完

                     threads[i].join();

              }

              System.out.println("n=" + Increament.x);

       }

}

如果对x的操作是原子级别的,最后输出的结果应该为x=10000,而在执行上面积代码时,很多时侯输出的x都小于10000,这说明x++ 不是原子级别的操作。原因是声明为volatile的简单变量如果当前值由该变量以前的值相关,那么volatile关键字不起作用。

 

 

同一时刻只有一个线程能访问synchronized块,synchronized块并不是一下子要执行完毕,CPU调试可能从synchronized块中的某个语句切换到其它的线程,再其它线程执行完毕后再继续执行该同步块。切换到其他线程时是否释放synchronized块上的锁,这要看切换所采用的方式:如果是CPU自动或调用Thread.yeild切换,则不会释放;如果是调用wait,则会释放;如果是调用的Thread.sleep,则不会;如果是调用的thread.join,则要看synchronized块上的锁是否是thread线程对象,如果不是,则不会释放,如果是,则会释放。

 

只能在同步控制方法或同步控制块里调用wait()、notify()和notifyAll(),并且释放操作锁,但sleep()可以在非同步控制方法里调用,不会释放锁。

 

sleep、yield都是Thread的静态方法,join属于Thread的非静态方式,如果将它们放入在同步块中调用时都不会释放锁。但wait属于Object类的方法,在wait()期间对象锁是释放的。

 

在执行同步代码块的过程中,遇到异常而导致线程终止,锁会释放。

 

执行线程的suspend()方法会导致线程被暂停,并使用resume()可唤醒,但不会释放锁。

 

当线程在运行中执行了Thread类的yield()静态方法,如果此时具有相同优先级的其他线程处于就绪状态,那么yield()方法将把当前运行的线程放到可运行池中并使用中另一线程运行。如果没有相同优先级的可运行进程,则该方法什么也不做。

 

sleep方法与yield方法都是Thread类的静态方法,都会使当前处于运行的线程放弃CPU,把运行机会让给另的线程。两都的区别:

1.         sleep方法会给其他线程运行的机会以,而不考虑其他线程的优先级,因此会给较低优先级线程一个运行的机会;yield方法只会给相同或更高优先级的线程一个运行的机会。

2.         当线程执行了sleep方法后,将转到阻塞状态。当线程执行了yield方法后,将转入就绪状态。

3.         Sleep方法比yield方法具有更好的可移植性。不能依靠yield方法来提高程序的并发性能。对于大多数程序员来说,yield方法的唯一用途是在测试期间人为地提高程序的并发性能,以帮助发现一些隐藏的错误。

 

thread.join():当前线程调用另一线程thread.join()时,则当前运行的线程将转到阻塞状态,并且等待thread线程运行结束后,当前线程程才会恢复运行(从阻塞状态到就绪状态)。比如有3个线程在执行计算任务,必须等三个线程都执行完才能汇总,那么这时候在主线程里面让三个线程join,最后计算结果既可:

public class JoinTest {

       public static void main(String[] args) {

              Rt[] ct = new Rt[3];

              for (int i = 0; i < ct.length; i++) {

                     ct[i] = new Rt();

                     ct[i].start();

                     try {

                            //主线等待三个线程终止后再继续运行

                            ct[i].join();

                     } catch (InterruptedException e) {

                            e.printStackTrace();

                     }

              }

              int total = 0;

              for (int j = 0; j < ct.length; j++) {

                     total += ct[j].getResult();

              }

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

       }

}

 

class Rt extends Thread {

       private int result;

       public int getResult() {

              return result;

       }

       public void run() {

              try {

                     Thread.sleep(1000);

                     result = (int) (Math.random() * 100);

                     System.out.println(this.getName() + " result=" + result);

              } catch (InterruptedException e) {

                     e.printStackTrace();

              }

 

       }

}

 

join()只能由线程实例调用,如果thread.join()在同步块中调用,并且同步锁对象也是thread对象,由于thread.join()是调用thread.wait()来实现的,wait会释放thread对象锁,则thread.join()与在同步块的锁也会一并释放;如果thread.join()在同步块的锁对象不是thread对象,则thread线程阻塞时不会释放锁:

public class JoinTest {

       public static void main(String[] args) throws InterruptedException {

              JThread t = new JThread();

              start(t, t);

              System.out.println("--------");

              t = new JThread();

              start(t, JThread.class);

       }

 

       static void start(JThread t, Object lock) {

              t.setLock(lock);

              t.start();

              try {

                     Thread.sleep(100);

              } catch (InterruptedException e) {

                     e.printStackTrace();

              }

              //如果锁对象是JThread.class时,则主线程会一直阻塞

              t.f();

       }

}

 

class JThread extends Thread {

       private Object lock;

 

       void setLock(Object lock) {

              this.lock = lock;

       }

 

       public void run() {

              synchronized (lock) {

                     try {

                            System.out.println(Thread.currentThread().getName() + " - join before");

                            /*

                             * 当前线程阻塞,又要等待自己运行完,这是矛盾的,所以其实该线程永远不会恢复执

                             * 行,除非使用 join(long millis)方式。实际上我们看this.join()源码就会

                             * 看出,this.join()就是调用了this.wait()方法,因为了this.wait()会释放

                             * this对象上的锁,所以当lock对象是自身时,主线程不会被锁住,所以第一个线程

                             * 会打印 "main - f()"。第二个线程的锁对象是JThread的Class对象,由于join

                             * 时不会释放JThread.class对象上的锁, 第二个线程会一直阻塞,所以第二个线程

                             * 不会打印 "main - f()",

                             *

                             */

                            this.join();

                            /*

                             * 这样可以正常结束整个程序,因为this线程一直会阻塞直到对方(也是this的线程)运行完

                             * 或者是对方没有运行完等 1 毫秒后thsi线程继续运行,所以以这样的方式一定不会出现死锁

                             * 现象

                             */

                            //this.join(1);

                            System.out.println(Thread.currentThread().getName() + " - join after");

                     } catch (InterruptedException e) {

                            e.printStackTrace();

                     }

              }

       }

 

       public void f() {

              synchronized (lock) {

                     System.out.println(Thread.currentThread().getName() + " - f()");

              }

       }

}

 

sleep与join都会使当前线程处于阻塞状态,而yield则是进行就绪状态。

 

同步的静态方法的锁对象是方法所属类的Class对象,而同步的非静态方法的锁对象是所调用方法实例所对应的this对象。

 

继承Runnable与Thread的区别:Thread类本身也实现了Runnable接口。 因此除了构造Runnable对象并把它作为构造函数的参数传递给Thread类之外,你也可以生成Thread类的一个子类,通过覆盖这个run方法来执行相应的操作。不过,通常最好的策略是把Runnable接口当作一个单独的类来实现,并把它作为参数传递给个Thread的构造函数。通过将代码隔离在单独的类中可以使你不必担心Runnable类中使用的同步方法和同步块与在相应线程类中所使用的其他任何方法之间的潜在操行所带来的影响。更一般地说,这种分离允许独立控制相关的操作和运行这些操作的上下文,同一个Runnable对象既可以传递给多个使用不同方式初抬化的Thread对象,也可以传递给其他的轻量级执行者(executor)。同样需要注意的是,继承了Thread类的刘象不能再同时继承其他类了。

 

如果线程被启动并且没有终止,调用方法isAlive将返回true。如果线程仅仅是因为某个原因阻塞,该方法也会返回true。

 

通过调用线程t的join方法将调用者挂起,直到目标线程t结束运行:t.join方法会在当t.isAlive方法的结果为false时返回。

 

有一些Thread类的方法只能应用于当前正在运行的那个线程中(也就是,调用Thread静态方法的线程),为了强制实施,这些方法都被声明为static:Thread.currentThread、Thread.interrupted、Thread.sleep、Thread.yield。

 

Thread.yield:仅仅是一个建议——放弃当前线程去运行其他的线程,JVM可以使用自己的方式理解这个建议。尽管缺少保证,但yield方法仍旧可以在一些单CPU的JVM实现上起到相应的效果,只要这些实现不使用分时抢占式的调用机制,在这种机制下,只有当一个线程阻塞时,CPU才会切换到其他线程上执行。如果在系统中线程执行了耗时的非阻塞计算任务的会占有更多的CPU时间,因而降低了应用程序的响应,为了安全起见,当执行非阻塞的计算任务的方法时,则可以在执行过程中插入yield方法(甚至是sleep方法)。为了减少不必要的影响,可以只在偶尔的情况下调用yield方法,比如一个包含如下语句的循环:

if(Math.random() < 0.01) Thread.yield();

使用抢占式调度机制的JVM实现,特别是在多处理器的情况下,yield才可能显得没有什么意义。

猜你喜欢

转载自blog.csdn.net/fromatozhappy/article/details/52751913
今日推荐