一、概念理解
(1)多线程的两种实现形式:一种是实现Runnable接口,一种是继承Thread类并重写其run方法
(2)线程的执行的两种形式:一种是使用ExecutorService线程池,一种是使用Thread的start
(3)ExecutorService(具有服务生命周期的线程执行器)的三种实现:CachedThreadPool(为每个任务创建一个线程),FixedThreadPool(创建有限数量的线程),SingleThreadExector(只创建一个线程,多个线程顺序执行)
(4)Thread.yield()是对线程调度器的一种建议:我已经执行完了生命周期中的最重要的部分可,此刻是CPU切换给其他任务执行一段时间的大好时机。不过这只是一个暗示,没有任何机制能保证它会被采纳。所以,不可过度依赖yield。
(5)Thread.join()是先执行另一个线程,待另一个线程执行完后再继续执行下边的代码,强制线程按照先后顺序执行。
(6)线程的休眠Sleep,应该使用TimeUnit.SECOND.sleep(200)的写法而不是使用Thread.sleep(),前者允许指定sleep()的延迟的时间单元,具有更好的可读性。
(7)线程的优先级通过Thead.setPriority()来实现,因为JDK的十个优先级与多数操作系统不能映射的很好,所以当调整优先级的时候,只使用MAX_PRIORITY(最高)、NORM_PRIORITY(普通)、MIN_PRIORITY(最低)三种级别
(8)后台线程daemon,又叫守护线程,是服务于其他线程的一种后台服务线程,当他服务的线程中的所有非守护线程结束时,程序也就终止了,守护线程也会被全部杀死。也就是说,守护线程必须依附于别的线程存活。可以通过setDaemon()方法来设置是否为守护线程
(9)Executors.newCachedThreadPool()方法接收一个ThreadFactory参数,可以由开发者自己指定Thread的创建方式以及参数的设置
(10)要捕获线程的异常,可以通过在ThreadFactory中调用Thread.setUncaughtExceptionHandler()方法来处理异常,这个方法接收一个Thread.UncaughtExceptionHandler对象作为参数.
(11)Brian的同步规则:如果你正在写一个变量,它可能接下来将被另一个线程读取,或者正在读取一个上一次已经被另一个线程写过的变量,那么你必须使用同步,并且,读和写线程都必须使用相同的监视器锁同步。
(12)线程的四种状态:新建(new),就绪(Runnable),阻塞(Blocked),死亡(Dead)。
二、重要概念区分
(1)Runnable执行独立任务,不会返回任何值;Callable是实现有返回值的任务。Runnable接口使用ExectorService的execute方法执行,而Callable接口使用ExectorService的submit方法执行,该方法返回一个带参数类型的Future对象,调用Future的get方法可获取返回值。
(2)sleep()和wait()的区别:sleep是任务进入休眠状态,这时该线程并不会释放锁(yield方法也是如此),而wait的时候线程被挂起,同时也释放锁,共享的资源可以被其他线程使用。
(3)notify和notifyAll的区别:如果有多个线程在等待一个对象的锁,notify会唤醒其中一个线程(随机唤醒),其他线程继续等待,只有被唤醒的这个线程可以执行完;但是notifyAll会唤醒所有线程,虽然还是只有一个线程会获得锁继续执行,不一样的是,这个线程释放锁之后其他的线程会竞争获取这个锁继续执行,最终所有的线程都会执行完。
(4)ExecutorService的shutdown()方法和shutdownNow()方法的区别:shutdown调用后,不可以再submit新的task,已经submit的将继续执行;shutdownNow试图停止当前正执行的task,并返回尚未执行的task的list。
三、并发锁机制的几种实现:
(1)在方法上使用synchronized关键子。注意:对于某个特定对象来说,其所有synchronized方法共享同一个锁,这可以被用来防止多个任务同时访问该对象。其实意思就是说,锁是加在某个特定对象上的,而不是某个方法上。
(2)使用锁的另一个形式是使用Lock对象,Lock lock = new ReentrantLock(); try{lock.lock();}finally{lock.unlock();},这种方式更灵活,但是不好控制,一般情况是使用synchronize即可。
(3)第三种使用锁的形式是使用synchronize块,synchronize(object){},这种方式可以很好的解决一部分性能问题,除非你觉得自己可以搞定,否则还是用synchronize关键字吧。
(4)第四种解决同步的方式是使用volatile关键字,但是当一个域的值依赖于他之前的值时,或者是受到其他域的值的限制事,volatile就失效了,比如a++的操作或有a<b这样的边界限制,volatile都是无法工作的。使用volitile而不是synchronize的唯一安全的情况就是类中只有一个可变的域,所以,最安全的操作还是使用synchronize关键字。
(5)第五种解决同步的方式是使用线程的本地存储,即把需要同步的数据让ThreaLocal对象持有,ThreadLocal<Integer> value = new ThreadLoal<Integer>{protected synchronized Integer initaValue(){return new Integer(1);}}
四、常用多线程相关类
(1)java中的一些原生的原子类:AtomicInteger、AtomicLong、AtomicReference等
(2)jdk自带多线程解决方案:LinkedBlockingQueue(无界队列),ArrayBlockingQueue(固定尺寸的队列,满了后线程就会被阻塞),PipedWrite和PipedReader(通过输入/输出在线程间进行通信),CountDownLatch(一组任务必须执行完成后才能执行下一组,一次性的),CyclicBarrier(在进行下一个步骤之前,所有的前置任务必须都完成,像CountDownLatch,但是是可以多次重复的,典型的赛马),DelayQueue(一个无界的BlockingQueue,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走,可以用来实现session超时),PriorityBlockingQueue(一个优先级队列,可对其中的任务按照优先级排队取出,具有可阻塞的读取操作),ScheduleExecutor(定时执行任务或每隔规则的时间重复任务),Semaphore(允许N个任务同时访问一个资源),Exchanger(两个任务交换对象的栅栏)
(3)容器性能优化:使用CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentHashMap、ConcurrentLinkedQueue等免锁容器来实现高效的同步解决方案,在容器需要同步的时候尽量使用这些对象,可以很大的提升性能,减少锁带来的性能损耗
(4)ReadWriteLock对写入不频繁,但是读取特别频繁的数据同步进行了优化,只有在这种情况下才考虑使用它。在它执行写入操作的时候,所有线程都不可读取。
五、其他
(1)原子性:除了long和double之外的基本类型(int、char、byte、short、boolean、float)的读取和写入操作,可以保证其原子性,其他的操作都不是原子性的。因为long和double都是64位的,会被JVM当作两个分离的32位来进行操作。如果要实现long和double的原子性,可以使用AtomicLong类型和AtomicDobule类型,他们本身进行了原子性封装。但是64位的系统就可以保证long和double的原子性。
(2)错失的信号:要切记原子性规则,如果有第二个线程的wait方法在第一个线程的notify方法之后执行的可能性,就有可能会错失信号,导致死锁。一般synchronize块里边包含while语句,而不是用while包含synchronize块。
(3)正确理解notifyAll方法:当notifyAll因某个特定锁而被调用时,只有等待这个锁的任务才会被唤醒。就是说,notifyAll只会唤醒对特定某个对象上锁的线程。
(4)死锁,要形成死锁,必须同时满足下面四个条件:1)互斥条件,任务间必须共享至少一个资源;2)至少有一个任务必须持有一个资源且正在等待获取一个当前被别的任务持有的资源;3)资源不能被任务抢占,一个任务不能从另一个任务那里抢占资源;4)必须有循环等待,就会形成多任务见循环等待,都在等待别人,而没有人释放资源,就形成了死锁。
(5)中断,Thread.interrupted()方法或ExecutorService.shutdownNow()可以中断线程的sleep阻塞,但是不能中断I/O阻塞和互斥锁所阻塞,I/O中断可以通过清理资源的方式来中断。
(6)另一种线程调度方式是使用Condition类,Condition.await()挂起任务,Condition.signal()和signalAll()用来唤醒被其自身挂起的任务,一般跟Lock配合使用。Condition condition = new ReentrantLock().newCondition();