[Thinking in JAVA] JAVA多线程的学习

1.  Thread.yield方法声明把CPU让给其他具有相同优先级的线程去执行,不过这只是一个暗示,并没有保障机制

2.  Executor

     执行器,管理Thread对象。

     语法demo:   

     ExecutorService exec=Executors.newCachedThreadPool();

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

           exec.execute(new  XXX());      //XXX为实现Runnable接口的类

      exec.shutdown();

     三种类型及其区别:   

           CachedThreadPool:在程序执行过程中创建与所需数量相同的线程,在回收旧线程时停止创建新线程,是Executor首选。

           FixedThreadPool:    一次性预先执行代价高昂的线程分配,可以限制线程的数量。

           SingleThreadExecutor: 如果向其提交多个任务,那么这些任务排队,每个任务都在下个任务开始前结束,所有的任务使用相同的线程。

             

3.如何创建有返回值的线程

   实现Callable接口,重写call()方法。

  exec.submit(new XXX()) 会返回Future对象,用future.get()获取值,这个值是泛型的,  取决于实现接口时的声明,如 implements Callable<String> 则get到的是String类型


4.通过编写定制的ThreadFactory可以定制由Executor创建的线程的属性(是否是后台,优先级,名称),

          比如class MyThreadFactory implements ThreadFactory{    

                             public Thread newThread(Runnable r) {
// TODO 自动生成的方法存根

                Thread t=new Thread(r);

                t.setDaemon(true);   //设置为后台线程
return ..;
}

5. 守护线程中派生的子线程默认是守护线程,当所有的非守护线程结束时,后台线程终止,一旦main()退出,JVM会关闭所有的守护线程。


6.线程join方法:

   若在A线程中调用B.join,则A被挂起,直到B结束。在调用时也可以带上一个超时参数。       对join方法的调用可以被打断(通过在调用线程上调用interrupt()方法),这时被打断的B需要用到try-catch子句。(在run方法中,catch InterruptedException)


7.捕获线程抛出的异常

    线程抛出的异常不能被正常的try catch到,可以用Executor解决这个问题:

   如4中所示自定义一个myfactory类,在该factory的newThread方法中t.setUncaughtExceptionHandler(new XXX) ;     XXX是实现了Thread.UncaughtExceptionHandler接口的类。 然后ExecutorService exec=Executors.newCachedThreadPool(new myfactory);


8.  synchronized和Lock对象

    synchronized:如果某个任务处于一个对标记为synchronized的调用中,那么在这个线程从该方法返回之前,其他所有要调用类中任何标记为synchronized方法的线程都会被阻塞。不过如果是synchronized(obj)方法块,只要obj不是相同的对象,两个方法不会因为另一个方法的同步而被阻塞

     用synchronized时代码量更小,且不会出现忘了unlock这种情况,用Lock对象需要 lock();  try{  } finally{ unlock},避免在lock之后代码出现异常导致死锁。

    显式使用lock对象可以解决更特殊的问题,比如尝试获取锁一段时间然后放弃、实现自己的调度算法等等。且在加锁释放锁方面有更细粒度的控制力。


9. volatile

一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。

    long和double的读取写入可能不是原子的,因为long和double 64位,在32位机器上的读写操作会被当作两个分离的32位操作执行。如果使用volatile关键字,就会获得原子性。此关键字还确保了应用中的可视性。如果把一个域声明为volatile的,只要对这个域产生写操作,所有的读操作都可以看到这个修改。即便使用了缓存,volatile域会被立即写入到主存中,而读取操作发生在主存。如果域由synchronized方法或语句块防护,不必设置为volatile。使用volatile 而不是synchronized的唯一安全的情况是类中只有一个可变域。第一选择应该是synchronized。

你可能感兴趣的:(java,线程)