java多线程并发编程总结

1.继承Thread后通过star方法启动。
thread.getName()获取当前线程名称
tread.currentThread()获取当前线程。


2.实现runnable接口,然后实现的类作为thread的构造参数,使用star启动。


3.带返回值的线程创建:实现callable接口,返回线程执行后的返回值future接口(futureTask是future的实现类,带泛型)。
future提供了几个接口用于控制线程:
cancel(bool mayInterruptIfRunning):试图取消该Future里的线程,当参数mayInterruptIfRunning为真标识用中断,否则让任务继续执行
V get()返回callable任务的值,调用该方法将阻塞程序,必须等到子线程结束后再返回值
V get(long timeout,TimeUnit unit):和上一个方法类似但是加了阻塞时间,如果指定时间没有返回将抛出异常
bool isCancelled() 如果在任务完成前被取消返回true
bool isDone() 如果任务完成返回true


创建实例:创建的Future除了能接收返回值外还必须处理线程返回的异常,使用try..catch
直接主线程创建(Future可以使用lambda)
public class CallableAndFuture {
    public static void main(String[] args) {
        Callable callable = new Callable() {
            public Integer call() throws Exception {
                return new Random().nextInt(100);
            }
        };
        FutureTask future = new FutureTask(callable);
        new Thread(future).start();
        try {
            Thread.sleep(5000);// 可能做一些事情
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
线程池创建调度(Future可以使用lambda),如果有多任务执行可以先创建一个Future集合把生成的Future都放里面,最后挨着循环读取
public class CallableAndFuture {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newSingleThreadExecutor();
        Future future = threadPool.submit(new Callable() {
            public Integer call() throws Exception {
                return new Random().nextInt(100);
            }
        });
        try {
            Thread.sleep(5000);// 可能做一些事情
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
java还提供了一个类CompletionService接收线程返回的对象几个,当从集合中去结果时返回已经完成的线程。


4.thread中的star和run的区别,star标识已经准备好交给JVM去调度,而使用run则把thread当做一个普通的对象立刻执行run的代码,并且其他线程不能并行执行(单线程执行main)。


线程具有新建、就绪、运行、阻塞、停止5个状态。
sleep是让当前线程在指定时间放弃cpu但是不会释放自己的锁,让低级线程有运行的机会。
yield只是把当前线程重新设置为就绪状态让更高级的或者同级的线程运行,不会释放锁。
join等待别的线程执行完在执行自己的方法。
前三个方法都是Thread的后面接收objact的方法
wait、notify和notifyAll()必须在synchronized使用,调用wait停止当前线程并且释放synchronized锁,其他线程可以针对相同对象的锁获取到然后执行。调用notify则标识以前wait的线程可以执行,但是必须等到当前线程完synchronized块才行,因为它还拥有锁。




5.线程阻塞 
自己调用thread.sleep()放弃资源,执行了一个阻塞的IO方法,试图得到一个同步监视器(但是该监视器被别的线程使用中),线程等待某个通知notify,调用了suspend()方法挂起自己(容易死锁少用,对应的激活resume)。一般情况线程从运行状态先变成阻塞状态在转出就绪状态后再进入运行状态,但有一个方法可以是线程从运行状态直接转成就绪状态(yield方法)
当一个线程出现阻塞(不管是不是wait()方法引起),在外面线程调用该线程对象的interrupt方法将给该线程产生一个interrupt异常,这样线程可以根据自身情况处理该异常



6.线程死亡:执行完成,出现异常(在别的线程中使用中断手段抛出中断异常是常用手段),直接调用stop(容易死锁)。使用isalive()判断线程是否激活(就绪,运行,阻塞都是激活返回true),不能对已经死亡的线程继续使用star()调用。


7.控制线程
join:调用线程的join方法将于等待join的线程执行完后再继续执行。
join():继续等待完成
join(long millis):等待millis毫秒
join(long millis,int nanos)等待millis毫秒加上nanos微毫秒,基本不用


8.后台线程(或者说守护线程、精灵线程)在前提线程都执行完后它会自动死亡。(前提线程创建的线程默认是前台线程,后台线程创建的默认是后台线程)
可以使用thread的setDaemon设置是否是后台线程。isDaemon判断线程是否是后台线程。




9.线程的静态方法:Thread.sleep(long millis)让当前线程停止millis毫秒,yield()相当于让当前线程暂停如果有优先级比当前线程高或者相同则调用这些线程,如果没有则还是继续调用自己执行代码。


10.线程优先级
每个创建的线程都优先级都和创建它的线程优先级相同。
Thread提供setPriority()和getPriority()方法数字和获取优先级。范围(1-10)但是由于不同操作系统对优先级的设置不同所以尽量使用thread提供的优先级枚举(MIN_RRIORITY,MAX_RRIORITY,NORM_RRIORITY)


11.线程同步
同步监视器,代码块同步
synchronized(obj)
{
...//此处代码同步
}
方法同步:只需要用synchronized修饰方法即可。
注意:不需要把线程安全类的所有方法都设置为synchronized,这样会出现性能降低,因此在设计类的时候只应该在某些需要安全的方法上使用synchronized。如果有需要应该设计2中方案(线程安全和线程不安全的2种类,比如StringBuilder线程不安全的但是单线程用它性能更好,StringBuffer则用于线程安全的)
以下几种分情况会释放同步监听器:执行完成或者异常退出,调用了wait()方法。使用sleep或者yield、suspend不会释放。


volatile是指对象马上改变后通知别的线程


12.同步锁lock(主要使用reentrantLock:可重入锁)
java5提供了2个根接口:lock和readWriteLock,针对lock实现了reentrantLock,针对readWriteLock提供了ReentrantReadWriteLock
JAVA8中提供了StampedLock大多数情况可以代替传统的ReentrantReadWriteLock。针对lock采用try。。catch。。finally方式确保锁释放
Lock l=new ReentrantLock();
l.lockInterruptibly();释放中断锁
l.lock();
l.tryLock()
l.tryLock(time, unit)
重入锁可以实现在被保护的代码中继续调用同样被保护的方法,但是要显示的多次释放。
ReadWriteLock管理一组锁,一个是只读的锁,一个是写锁。读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。它也是可重入的。可以实现写锁降低为读锁,在读锁和写锁的获取过程中支持中断,支持Condition,提供确定锁是否被持有等辅助方法


StampedLock的思想是读写锁中读不仅不阻塞读,同时也不应该阻塞写。在读的时候如果发生了写,则应当重读而不是在读的时候直接阻塞写!要记住StampedLock并没有实现重入特性。
public  class  Point {
           //一个点的x,y坐标
           private   double   x,y;
           /**Stamped类似一个时间戳的作用,每次写的时候对其+1来改变被操作对象的Stamped值
            * 这样其它线程读的时候发现目标对象的Stamped改变,则执行重读*/
           private final   StampedLock  stampedLock   =  new    StampedLock();
  
           // an exclusively locked method
           void move(doubledeltaX,doubledeltaY) {
                   /**stampedLock调用writeLock和unlockWrite时候都会导致stampedLock的stamp值的变化
                  * 即每次+1,直到加到最大值,然后从0重新开始 */
                  long stamp =stampedLock.writeLock(); //写锁
                  try {
                         x +=deltaX;
                         y +=deltaY;
                  } finally {
                         stampedLock.unlockWrite(stamp);//释放写锁
                  }
           }
  
         double distanceFromOrigin() {    // A read-only method
                 /**tryOptimisticRead是一个乐观的读,使用这种锁的读不阻塞写
                 * 每次读的时候得到一个当前的stamp值(类似时间戳的作用)*/
                long stamp =stampedLock.tryOptimisticRead();//如果返回值是0表示有写锁当前没有拿到锁,
     
                //这里就是读操作,读取x和y,因为读取x时,y可能被写了新的值,所以下面需要判断
                double    currentX =x,   currentY =y;
     
                /**如果读取的时候发生了写,则stampedLock的stamp属性值会变化,此时需要重读,
                * 再重读的时候需要加读锁(并且重读时使用的应当是悲观的读锁,即阻塞写的读锁)
                 * 当然重读的时候还可以使用tryOptimisticRead,此时需要结合循环了,即类似CAS方式
                 * 读锁又重新返回一个stampe值*/
                if (!stampedLock.validate(stamp)) {
                        stamp =stampedLock.readLock(); //读锁
                        try {
                              currentX =x;
                              currentY =y;
                        }finally{
                              stampedLock.unlockRead(stamp);//释放读锁
                       }
                }
               //读锁验证成功后才执行计算,即读的时候没有发生写
               return Math.sqrt(currentX *currentX + currentY *currentY);
          }
}


它支持各种锁之间的转换,比如将读锁转换为写锁而不用再次解锁和加锁十分实用
stampedLock.tryConvertToOptimisticRead(stamp)
stampedLock.tryConvertToReadLock(stamp)
stampedLock.tryConvertToWriteLock(stamp)


13.Semaphore,CountDownLatch、CyclicBarrier
除了锁之外,并发API也支持计数的信号量。不过锁通常用于变量或资源的互斥访问,信号量可以维护整体的准入许可。这在一些不同场景下,例如你需要限制你程序某个部分的并发访问总数时非常实用
ExecutorService executor = Executors.newFixedThreadPool(10);
Semaphore semaphore = new Semaphore(5);
Runnable longRunningTask = () -> {
    boolean permit = false;
    try {
        permit = semaphore.tryAcquire(1, TimeUnit.SECONDS);
        if (permit) {
            System.out.println("Semaphore acquired");
            sleep(5);
        } else {
            System.out.println("Could not acquire semaphore");
        }
    } catch (InterruptedException e) {
        throw new IllegalStateException(e);
    } finally {
        if (permit) {
            semaphore.release();
        }
    }
}


IntStream.range(0, 10)
    .forEach(i -> executor.submit(longRunningTask));


stop(executor);


执行器可能同时运行10个任务,但是我们使用了大小为5的信号量,所以将并发访问限制为5。使用try-finally代码块在异常情况中合理释放信号量十分重要。信号量限制对通过sleep(5)模拟的长时间运行任务的访问,最大5个线程。每个随后的tryAcquire()调用在经过最大为一秒的等待超时之后,会向控制台打印不能获取信号量的结果。
CountDownLatch:等待所有的子线程完成后再执行。
CyclicBarrier:通过它可以实现让一组线程等待至某个状态之后再全部同时执行




14.线程通信 
object提供的wait(有3个重载方法)、notify、notifyall这个三个方法需要在同步监视器下才能调用,也就是synchronized。
condition针对lock对象实现线程之间的通信,它绑定了一个lock对象,可以使用await(有4个重载方法包括中断的方法)阻塞,使用signal和signalAll方法提醒其他阻塞的唤醒。(condition可以实现1个lock多个condition,在不同的需要时释放和阻塞不同的线程)
class BoundedBuffer {  
   final Lock lock = new ReentrantLock();//锁对象  
   final Condition notFull  = lock.newCondition();//写线程条件   
   final Condition notEmpty = lock.newCondition();//读线程条件  
   final Object[] items = new Object[100];//缓存队列  
   int putptr/*写索引*/, takeptr/*读索引*/, count/*队列中存在的数据个数*/;  
   public void put(Object x) throws InterruptedException {  
     lock.lock();  
     try {  
       while (count == items.length)//如果队列满了   
         notFull.await();//阻塞写线程  
       items[putptr] = x;//赋值   
       if (++putptr == items.length) putptr = 0;//如果写索引写到队列的最后一个位置了,那么置为0  
       ++count;//个数++  
       notEmpty.signal();//唤醒读线程  
     } finally {  
       lock.unlock();  
     }  
   }    
   public Object take() throws InterruptedException {  
     lock.lock();  
     try {  
       while (count == 0)//如果队列为空  
         notEmpty.await();//阻塞读线程  
       Object x = items[takeptr];//取值   
       if (++takeptr == items.length) takeptr = 0;//如果读索引读到队列的最后一个位置了,那么置为0  
       --count;//个数--  
       notFull.signal();//唤醒写线程  
       return x;  
     } finally {  
       lock.unlock();  
     }  
   }   
 }  
BlockingQueue阻塞队列(用于生产和消费模式):主要作用不是作为容器而是作为线程同步工具。当生产者向BlockingQueue放入元素,如果队列已满则被阻塞,如果消费者去BlockingQueue元素如果为空则阻塞。
主要提供put和take2个阻塞方法。因为继承Queue有它的方法。主要有下面几个具体的实现类:ArrayBlockingQueue、DelayQueue、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue


15.线程组:对线程分组管理,有方法
activeCont活动线程数
interrupt中断组线程
isDaemon()是否是后台线程
setDaemon()设置是否是后台线程
setMaxPriority设置最大优先级
uncaughtException()可以处理线程阻止任意线程抛出的异常


16.线程Thread提供了当线程出现异常时由JVM调用的处理方法,
static setDefaultUncaughtExceptionHandler设置默认的线程类异常处理
setUncaughtExceptionHandler设置某一个线程对象异常处理方法
线程组实现了第二种。
当一个线程抛出未处理异常首先调用自身的异常处理方法,如果没就调用线程组的异常处理方法。
线程组的异常处理方法(先调用父线程组异常处理方法,如果没有调用类默认出器)


17.线程池
Executors工厂创建线程池,有如下结果方法:
Executors.newCachedThreadPool();
Executors.newCachedThreadPool(threadFactory);
Executors.newFixedThreadPool(nThreads);
Executors.newFixedThreadPool(nThreads, threadFactory);
Executors.newSingleThreadExecutor();
Executors.newSingleThreadExecutor(threadFactory);


Executors.newScheduledThreadPool(corePoolSize);
Executors.newScheduledThreadPool(corePoolSize, threadFactory);
Executors.newSingleThreadScheduledExecutor();
Executors.newSingleThreadScheduledExecutor(threadFactory);
jdk8新增加的功能,合理利用多CPU(生成的线程是后台线程)
Executors.newWorkStealingPool();
Executors.newWorkStealingPool(parallelism);


上面方法返回的ExecutorServes接收callable、runable对象创建线程并返回future的线程执行对象(如果是调度线程池返回的是调度的future),通过它可以控制线程和掌握线程当前状态等。待Scheduled的返回的线程池可实现调度功能。线程池调用完使用shutdown或者shutdownNow方式关闭。


18.ForkJoinPool:实现大任务划分为小任务后再执行,最后合并小任务。他是ExecutorServes的实现类也是一个线程池。
以下创建ForkJoinPool
ForkJoinPool forkJoinPool=new ForkJoinPool();
ForkJoinPool forkJoinPool=new ForkJoinPool(arg0);
以下是java8增加的功能
ForkJoinPool.commonPool();//返回通用池,不会因为shutdown或者shutdownNow影响。
ForkJoinPool.getCommonPoolParallelism();//返回通用池的并行级别
同样它也是通过submit(ForkJoinTask)执行,参数是一个抽闲类有RecursiveAction和RecursiveTask2个子类,第一个代表没返回值第二个有返回值。都是可以拆分合并的任务。
ForkJoinTask:我们要使用ForkJoin框架,必须首先创建一个ForkJoin任务。它提供在任务中执行fork()和join()操作的机制,通常情况下我们不需要直接继承ForkJoinTask类,而只需要继承它的子类,Fork/Join框架提供了以下两个子类:
RecursiveAction:用于没有返回结果的任务。
RecursiveTask :用于有返回结果的任务。
ForkJoinPool :ForkJoinTask需要通过ForkJoinPool来执行,任务分割出的子任务会添加到当前工作线程所维护的双端队列中,进入队列的头部。当一个工作线程的队列里暂时没有任务时,它会随机从其他工作线程的队列的尾部获取一个任务。


Fork/Join框架的异常处理
ForkJoinTask在执行的时候可能会抛出异常,但是我们没办法在主线程里直接捕获异常,所以ForkJoinTask提供了isCompletedAbnormally()方法来检查任务是否已经抛出异常或已经被取消了,并且可以通过ForkJoinTask的getException方法获取异常。使用如下代码:
if(task.isCompletedAbnormally())
{
    System.out.println(task.getException());
}
getException方法返回Throwable对象,如果任务被取消了则返回CancellationException。如果任务没有完成或者没有抛出异常则返回null。
public class ForkJoinTest {
    private double[] d;
    private class ForkJoinTask extends RecursiveTask {
        private int first;
        private int last;
        public ForkJoinTask(int first, int last) {
            this.first = first;
            this.last = last;
        }
        protected Integer compute() {
            int subCount;
            if (last - first < 10) {
                subCount = 0;
                for (int i = first; i <= last; i++) {
                    if (d[i] < 0.5)
                        subCount++;
                    }
                }
            else {
                int mid = (first + last) >>> 1;
                ForkJoinTask left = new ForkJoinTask(first, mid);
                left.fork();
                ForkJoinTask right = new ForkJoinTask(mid + 1, last);
                right.fork();
                subCount = left.join();
                subCount += right.join();
            }
            return subCount;
        }
    }
    public static void main(String[] args) {
        d = createArrayOfRandomDoubles();
        int n = new ForkJoinPool().invoke(new ForkJoinTask(0, 9999999));
        System.out.println("Found " + n + " values");
    }
}




19.ThreadLocal创建线程变量副本,各个线程互不干扰。


20.包装不安全的集合使用Collections的几个静态方法包装list,map,set和collection
其实在java5之后提供了大量的并发访问集合实现类和接口。主要有以下2类:
concurrent开头:concurrentHashMap,concurrentSkipListMap,concurrentSkipListSet,concurrentListedQueue,concurrentListedDeque
copyonwrite开头:copyonwriteArryList、copyonwriteArraySet
以前还介绍过一个BlockingQueue的队列,此队列在多线程是实现阻塞式,而此处的是非阻塞式。











你可能感兴趣的:(java并发编程)