还剩17页未读,继续阅读
本资源只提供10页预览,全部文档请下载后查看!喜欢就下载吧,查找使用更方便
文本内容:
6.阻塞队列当试图向满队列中添加元素或者向空队列中移除元素时,阻塞队列blocking queue会导致线程阻塞通过阻塞队列,可以按以下模式来工作工作者线程可以周期性的将中间结果放入阻塞队列中,其他线程可取出中间结果并进行进一步操作若前者工作的比较慢还没来得及向队列中插入元素,后者会等待它试图从空队列中取元素从而阻塞;若前者运行的快试图向满队列中插元素,它会等待其他线程阻塞队列提供了以下方法add方法添加一个元素若队列已满,会抛出HlegalStateException异常element方法返回队列的头元素若队列为空,会抛出NoSuchElementException异常offer方法添加一个元素,若成功则返回true若队列已满,则返回false peek方法返回队列的头元素若队列为空,则返回null poll方法删除并返回队列的头元素若队列为空,则返回null put方法添加一个元素若队列已满,则阻塞remove方法移除并返回头元素若队列为空,会抛出NoSuchElementExceptiono take方法移除并返回头元素若队列为空,则阻塞public classBlockingQueueTest{private intsize=20;private ArrayBlockingQueueIntegerblockingQueue=new ArrayBlockingQueueIntegersize;public staticvoid mainString[]args{BlockingQueueTest test二new BlockingQueueTest;Producer producer=test.new Producer;Consumer consumer=test.new Consumer;producer.start;consumer.start;}class Consumerextends Thread{@Override public void run{whiletrue{try//从阻塞队列中取出一个元素queue.take;System,out printin队列剩余+queue.size+”个元素}catch InterruptedExceptione{e.printStackTrace;class Producerextends Thread{@Override public void run{while true{try//向阻塞队列中插入一个元素queue.putl;System.out.printin队列剩余空间:+size-queue.size;}catch InterruptedExceptione{e.printStackTrace;
7.执行器在Java中,线程池通常指——个ThreadPoolExecutor对象,ThreadPoolExecutor类继承了AbstractExecutorService类,而AbstractExecutorService抽象类实现了ExecutorService接口,ExecutorService接口又扩展了Executor接口也就是说,Executor接口是Java中实现线程池的最基本接口在使用线程池时通常不直接调用ThreadPoolExecutor类的构造方法,二回使用Executors类提供给的静态工厂方法,这些静态工厂方法内部会调用ThreadPoolExecutor的构造方法,并为准备好相应的构造参数Executor是类中的以下三个方法会返回一个实现了ExecutorService接口的ThreadPoolExecutor类的对象ExecutorService newCachedThreadPool//返回一个带缓存的线程池,该池在必要的时候创建线程,在线程空闲60s后终止线程ExecutorService newFixedThreadPoolintthreads//返回一个线程池,线程数目由threads参数指明ExecutorService newSingleThreadExecutor//返回只含一个线程的线程池,它在一个单一的线程中依次执行各个任务对于newCachedThreadPool方法返回的线程池对每个任务,若有空闲线程可用,则立即让它执行任务;若没有可用的空闲线程,它就会创建一个新线程并加入线程池中;newFixedThreadPool方法返回的线程池里的线程数目由创建时指定,并一直保持不变若提交给它的任务多于线程池中的空闲线程数目,那么就会把任务放到队列中,当其他任务执行完毕后再来执行它们
8.Callable与Future之前提到了创建线程的两种方式,它们有一个共同的缺点,那就是异步方法run没有返回值,也就是说无法直接获取它的执行结果,只能通过共享变量或者线程间通信等方式来获取好消息是通过使用Callable和Future,可以方便的获得线程的执行结果Callable接口与Runnable接口类似,区别在于它定义的异步方法call有返回值Callable接口的定义如下public interfaceCallableV{V callthrows Exception;类型参数V即为异步方法call的返回值类型Future可以对具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成以及获取结果可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果Future接口的定义如下public interfaceFutureV{boolean cancelbooleanmaylnterruptlfRunning;boolean isCancelled;boolean isDoneQ;V getthrows InterruptedException,ExecutionException;V getlongtimeout,TimeUnit unitthrows InterruptedException,ExecutionException,TimeoutException;
9.同步容器与并发容器ava中的同步容器指的是线程安全的集合类,同步容器主要包含以下两类通过Collections类中的相应方法把普通容器类包装成线程安全的版本;Vector.HashTable等系统为封装好的线程安全的集合类相比与并发容器(下面会介绍),同步容器存在以下缺点对于并发读访问的支持不够好;由于内部多采用synchronized关键字实现,所以性能上不如并发容器;对同步容器进行迭代的同时修改它的ConcurrentModificationException
10.同步器CyclicBarrier:它允许线程集等待直至其中预定数目的线程到达某个状态(这个状态叫公共障栅(barrier)),然后可以选择执行一个处理障栅的动作适用场景当多个线程都完成某操作,这些线程才能继续执行时,或都完成了某操作后才能执行指定任务时对CyclicBarrier对象调用await方法即可让相应线程进入barrier状态,等到预定数目的线程都进入了barrier状态后,这些线程就可以继续往下执行了CountDownLatch:允许线程集等待直到计数器减为0适用场景当一个或多个线程需要等待直到指定数目的事件发生举例来说,假如主线程需要等待N个子线程执行完毕才继续执行,就可以使用CountDownLatch来实现,参考文献山张松柳晨光,张冬雯,et al.面向Java多线程机制的软件重构方法[J].北京理工大学学报,2018,3811:53-
59.
[2]刘聪.基于Java线程池的Kuznyechik算法应用研究[J].泰山学院学报,2018,4006:73-
79.网田春婷.基于Java多线程同步技术的简易模拟售票系统实现[J].电脑编程技巧与维护,2018,40212:74-75+
115.
[4]张光平.Java多线程的分析[J].科技风,20181:71-
71.
[5]姚凯,刘琳琳.基于Java多线程技术的网络编程研究[J].电子技术与软件工程,20178:12-
12.⑹武瑞婵,邓华丽.基于Java多线程的预处理迭代并行求解器[J].山西大同大学学报自然科学版,2017⑵.⑺孔德凤,应时.基于JAVA线程机制研究生产者-消费者问题[J].信息与电脑,
20172.
[8]Seo HS,Chung IS,Kim BM,et al.The Designand Implementationof Automata-based TestingEnvironment forJava Multi-thread Programs[J].
2001.
[9]Wang D,Zhang X,Men T,et al.An Implementationof SortingAlgorithm Basedon JavaMulti-thread Technology[C]//International Conferenceon ComputerScienceElectronics Engineering.IEEE,
2012.
[10]Hua Y.Program ofRace BetweenRabbit andTortoise Basedon JavaMulti-thread Mechanism[J].ComputerModernization,
2011.
一、为什么使用多线程
1.并发与并行现在,使用的计算机基本上都搭载了多核CPU,这时,能真正的实现多个进程并行执行,这种情况叫做并行,因为多个进程是真正“一并执行”的具体多少个进程可以并行执行取决于CPU核数综合以上,知道,并发是一个比并行更加宽泛的概念也就是说,在单核情况下,并发只是并发;而在多核的情况下,并发就变为了并行下文中将统一用并发来指代这一概念
2.阻塞与非阻塞UNIX系统内核提供了一个名为read的函数,用来读取文件的内容typedef ssize_t int;typedef size_t unsigned;ssize_t readintfd,void*buf,size_t n;这个函数从描述符为fd的当前文件位置复制至多n个字节到内存缓冲区buf若执行成功则返回读取到的字节数;若失败则返回-1read系统调用默认会阻塞,
3.多进程vs多线程然而,有时候,多线程共享数据的便捷容易可能会成为一个让头疼的问题,在后文中会具体提到常见的问题及相应的解决方案在上面的read函数的例子中,如果使用多线程,可以使用一个主线程去进行I/O的工作,再用一个或几个工作线程去执行一些轻量计算任务,这样当主线程阻塞时,线程调度程序会调度的工作线程来执行计算任务,从而更加充分的利用CPU时间片而且,在多核机器上,的多个线程可以并行执行在多个核上,进一步提升效率
二、如何使用多线程
1.线程执行模型每个进程刚被创建时都只含有一个线程,这个线程通常被称作主线程main threado而后随着进程的执行,若遇到创建新线程的代码,就会创建出新线程,而后随着新线程被启动,多个线程就会并发地运行某时刻,主线程阻塞在一个慢速系统调用中比如前面提到的read函数,这时线程调度程序会让主线程暂时休眠,调度另一个线程来作为当前运行的线程每个线程也有自己的一套变量,但相比于进程来说要少得多,因此线程切换的开销更小
2.创建一个新线程1通过实现Runnable接口在Java中,有两种方法可以创建一个新线程第一种方法是定义一个实现Runnable接口的类并实例化,然后符这个对象传入Thread的构造器来创建一个新线程,如以下代码所示class MyRunnableimplements Runnable{public voidrun{〃这里是新线程需要执行的任务}Runnable r=new MyRunnable;Thread t=new Threadr;2通过继承Thread类第二种创建一个新线程的方法是直接定义一个Thread的子类并实例化,从而创建一个新线程比如以下代码class MyThreadextends Thread{public voidrun{〃这里是线程要执行的任务}创建了一个线程对象后,直接对其调用start方法即可启动这个线程t.start;
5.如何保证线程安全所谓线程安全,指的是当多个线程并发访问数据对象时,不会造成对数据对象的“破坏”保证线程安全的一个基本思路就是让访问同一个数据对象的多个线程进行“排队”,一个接一个的来,这样就不会对数据造成破坏,但带来的代价是降低了并发性1race condition竞争条件当两个或两个以上的线程同时修改同一数据对象时,可能会产生不正确的结果,称这个时候存在一个竞争条件racecondition在多线程程序中,必须要充分考虑到多个线程同时访问一个数据时可能出现的各种情况,确保对数据进行同步存取,以防止错误结果的产生请考虑以下代码public classCounter{private longcount=0;public voidaddlong value{this.count=this.count+value;}注意一下改变count值的那一行,通常这个操作不是一步完成的,它大概分为以下三步第一步,把count的值加载到寄存器中;第二步,把相应寄存器的值加上value的值;第三步,把寄存器的值写回count变量可以编译以上代码然后用javap查看下编译器为生成的字节码可以看到,大致过程和以上描述的基本一样2锁对象Java类库中为提供了能够给临界区“上锁”的ReentrantLock类,它实现了Lock接口,在进一步介绍ReentrantLock类之前,Lock接口的定义public interfaceLock{void lock;void locklnterruptiblyOthrows InterruptedException;boolean tryLock;boolean tryLocklongtime,TimeUnit unitthrows InterruptedException;void unlock;Condition newCondition;lock方法用来获取锁,在锁被占用时它会一直阻塞,并且这个方法不能被中断;locklnterruptibly方法在获取不到锁时也会阻塞,它与lock方法的区别在于阻塞在该方法时可以被中断;tryLock方法也是用来获取锁的,它的无参版本在获取不到锁时会立刻返回false,它的计时等待版本会在等待指定时间还获取不到锁时返回false,计时等待的tryLock在阻塞期间也能够被中断使用tryLock方法的典型代码如下if myLock.tryLock{try•••}finally{myLock.unlock;}else{〃做其他的工作}unlock方法用来释放锁;newCondition方法用来获取当前锁对象相关的条件对象,这个在下文会具体介绍ReentrantLock类是唯---个Lock接口的实现类,它的意思是可重入锁,关于“可重入”的概念下面会进行介绍有了上面的介绍,理解它的使用方法就很简单了,比如下面的代码即完成了给add方法“上锁”Lock myLock=new ReentrantLock;public voidaddlong value{myLock.lock;try{this.count=this.count+value;}finally{myLock.unlock;}从以上代码可以看到,使用ReentrantLock对象来上锁时只需要先获取一个它的实例有了锁对象,尽管线程A在执行add方法的过程中被线程调度程序剥夺了运行权,其他的线程也进入不了临界区,因为线程A还在持有锁对象这样一来,就很好的保护了临界区ReentrantLock锁是可重入的,这意味着线程可以重复获得已经持有的锁,每个锁对象内部都持有一个计数,每当线程获取依次锁对象,这个计数就加1,释放一次就减1只有当计数值变为时,才意味着这个线程释放了锁对象,这时其他线程才可以来获取3条件对象有些时候,线程进入临界区后不能立即执行,它需要等某一条件满足后才开始执行比如,希望count值大于5的时候才增加它的值,最先想到的是加个条件判断public void addint value{if this.count5{this.count=this.count+value;}然而上面的代码存在一个问题假设线程A执行完了条件判断并的值count值大于5,而在此时该线程被线程调度程序中断执行,转而调度线程B,线程B对统一counter对象的count值进行了修改,使得它不再大于5,这时线程调度程序又来调度线程A,线程A刚才判定了条件为真,所以会执行add方法,尽管此时count值已不再大于5显然,这与所希望的情况的不符的对于这种问题,想到了可以在条件判断前后加锁与解锁publicvoid addint value{myLock.lock;trywhile counter.getCount=5{〃等待直到大于5}this.count=this.count+value;}finally{myLock.unlock;}在以上代码中,若线程A发现count值小于等于5,它会一直等到别的线程增加它的值直到它大于5然而线程A此时持有锁对象,其他线程无法进入临界区add方法内部来改变count的值,所以当线程A进入临界区时若count小于等于5,线程A会一直在循环中等待,其他的线程也无法进入临界区这种情况下,可以使用条件对象来管理那些已经获得了一个锁却不能开始干活的线程一个锁对象可以有一个或多个相关的条件对象,在锁对象上调用newCondition方法就可以获得一个条件对象比如可以为“count值大于5”获得一个条件对象Condition enoughCount=myLock.newCondition;然后,线程A发现count值不够时,调用“enoughCount.await”即可,这时它便会进入Waiting状态,放弃它持有的锁对象,以便其他线程能够进入临界区当线程B进入临界区修改了count值后,发现了count值大于5,线程B可通过enoughCount.signalAnO”来“唤醒所有等待这一条件满足的线程这里只有线程A此时线程A会从Waiting状态进入Runnable状态当线程A再次被调度时,它便会从await方法返回,重新获得锁并接着刚才继续执行注意,此时线程A会再次测试条件是否满足,若满足则执行相应操作也就是说signalAH方法仅仅是通知线程A一声count的值可能大于5了,应该再测试一下还有一个signal方法,会随机唤醒一个正在等待某条件的线程,这个方法的风险在于若随机唤醒的线程测试条件后发现仍然不满足,它还是会再次进入Waiting状态,若以后不再有线程唤醒它,它便不能再运行了4synchronized关键字Java中的每个对象都有一个内部锁,这个内部锁也被称为监视器monitor;每个类内部也有一个锁,用于控制多个线程对其静态成员的并发访问若一个实例方法用synchronized关键字修饰,那么这个对象的内部锁会“保护”此方法,称此方法为同步方法这意味着只有获取了该对象内部锁的线程才能够执行此方法也就是说,以下的代码public synchronizedvoidaddint value{•••}等价于publicvoidaddintvalue{this.innerLock.lock;try•••}finally{this.innerLock.unlock;}这意味着,通过给add方法加上synchronized关键字即可保护它,加锁解锁的工作不需要再手动完成对象的内部锁在同一时刻只能由一个线程持有,其他尝试获取的线程都会被阻塞直至该线程释放锁,这种情况下被阻塞的线程无法被中断内部锁对象只有一个相关条件wait方法添加一个线程到这个条件的等待集中;notify All/notify方法会唤醒等待集中的线程也就是说wait/notify等价于enoughCount.await/enoughCount.signAllo以上add方法可以这么实现public synchronizedvoidaddintvalue{while this.count=5{wait;}this.count+=value;notify All;若Counter中含有一个synchronized关键字修饰的静态方法,那么进入该方法的线程会获得Bank.class的内部锁这意味着其他任何线程不能执行Counter类的任何同步静态方法对象内部锁存在一些局限性不能中断一个正在试图获取锁的线程;试图获取锁时不能设定超时;每个锁仅有一个相关条件;synchronized方法或者synchronized代码块出现异常时,Java虚拟机会自动释放当前线程已获取的锁5同步阻塞上面提到了一个线程调用synchronized方法可以获得对象的内部锁前提是还未被其他线程获取,获得对象内部锁的另一种方法就是通过同步阻塞synchronized obj{〃临界区}经常会看到一种特殊的锁,如下所示public classCounter{private Objectlock=new Object;synchronized lock{〃临界区}。