队列是一种先进先出的数据结构,元素在队列末尾添加,在队列头部删除。Queue接口扩展自Collection,并且提供了插入,删除,校验等功能。
方法: offer()表示向队列中添加一个元素,
poll()和remove()是移出队列头部的元素,如果队列为空,那么poll()方法会返回null,remove()方法会抛出异常。
element()和peek()是获取队列头部的元素,不会删除该元素。
接口Deque是扩展自Queue的双向队列,它仅支持在两端插入和删除元素,因为LinkedList类实现了Deque接口,那么可以通过new LinkedList对象来创建一个队列。
PriorityQueue类实现了一个优先队列,优先队列中的元素被赋予优先级,拥有最高优先级的元素优先被删除。
Properties类是HashTable类的子类,所以也间接的实现了Map接口。
在实际应用中,常使用properties类对属性文件进行处理,(在国际化支持中我们也会使用另外一个工具来通过国际化方式读取属性文件: ResourceBundle )
Properties方法:
load():加载文件
getProperty(key):通过key值来获取对应属性的value
setProperty(String key,String value):向properties文件中写入值
专门处理集合类的工具类,类似于Arrays
集合框架中的新工具大部分都是线程不安全的,如果在并发环境下使用这些工具,那么就会导致各种问题,而Vector和HashTable这些老工具留下来也是为了向下兼容。
Collections提供了一种将非线程安全的集合转换成线程安全的集合:
Collection和Collections有什么区别?Array和Arrays有什么区别?
Collection是一个集合接口,它提供了大量的各种集合的基础操作的通用方法。Collection在Java类库中有很多的实现类,Collection的意义是为各个集合提供一个最大化的统一操作方式。
Collections是集合的工具类,它提供了针对集合的一些搜索,优化,线程安全化等方法。
Array类提供了动态创建数组和Java数组的方法
Arrays类提供了对数组的方法,此类还包含一个允许数组作为列表来查看的静态工厂。
1.基本概念
在计算机发展初期,只有单任务的操作系统,非常浪费计算机资源。
操作系统的发展使得多个任务能够同时运行,程序在各自的进程中进行,相互分离,由操作系统来分配资源,时间片,内存等。
特点:
资源利用:
在一个程序执行到一半,需要等待一些资源时,这时候cpu处于空闲状态,那么这时候其他的程序可以开始执行,进行有价值的工作,不浪费资源。
公平:
多用户或程序需要由平等的优先级,让他们能够更加平等的获取时间片来使用cpu执行。
方便:
写一些程序,让他们各自执行一个单独任务,在必要时进行相互协调。要比写一个程序,让他执行所有任务更容易,更让人满意。
程序:
程序是计算机治指令的集合,程序是一组静态的指令集,不占用系统内存,也不会被系统调度,也不能作为独立运行的单位,只是以文件的形式存储在计算机磁盘中。
进程:
是一个程序在其自身的地址内一次运行活动。进程是申请资源,调度和独立运行的单位。一个程序可以有多个进程。程序就不可以申请资源。
相同的关注点(资源利用,公平,方便)不仅促进了进程的发展,也促进了线程的发展:
线程允许程序控制流的多重分支同时存在于一个分支。
线程共享进程的资源,内存和文件句柄等,但是每一个线程有自己的程序计算器,栈和本地变量,
线程也为多处理器系统中并行地使用硬件提供了一个自然而然的分解,同一个程序内的多个线程可以在多cpu情况下同时调度。
线程有些时候被称为轻量进程。在大多数操作系统中,程序的执行把线程作为时序调度的单元。在没有明确的协调情况下,线程间相互同时或者异步的执行,因为线程共享进程的内存地址,所以同一进程中的线程访问相同变量,并从同一个堆中分配对象,保证了良好的数据共享。但如果没有明确的同步来管理数据共享,那么就会导致数据不安全,一个线程可以修改变量,造成其他线程的访问假数据。
多线程的优点:
可以更好的实现并行
恰当的使用多线程,可以降低开发和维护的开销,并且能够提高复杂应用的性能。
cpu在线程之间开关时的开销要比进程小得多,因为线程的开关是在同一地址,不涉及地址空间和其他操作,而进程不是。
创建和撤销进程的开销要比进程少得多。
Java语言提供了对多线程程序设计的支持。 多个线程运行时,由于线程之间的切换速度极快,看似是在同时运行,但对于执行者cpu来说,一个时间片只是执行一个线程。
JVM规范中没有规定线程实现模型,具体的JVM实现用1:1(内核线程),N:1(用户态线程),M:N(混合)模型的任何一种都是允许的。Java不会暴露出各个模型之间的区别,所以用户是感觉不到每个模型间的差异。只是性能特性会不一样。
线程的生命周期
新线程:使用new关键字创建线程实例后,仅作为一个对象存在,因为JVM没有为其分配时间片和其他运行所需资源。
就绪状态:JVM为该线程分配了运行所需的资源,只等cpu分配时间片,JVM的线程调度器会按照特定的规则来对该对象设置优先级,优先级越高,该线程获得cpu时间片的机会越大。
运行状态:就绪状态的线程获得时间片就会转成运行状态。
等待/阻塞:线程运行过程中被剥夺了时间片或者因为一些其他情况而导致线程等待/阻塞。这时线程会被挂起,直到所需要的资源再次被分配,那么就会进入就绪队列。还有一种情况是永远无法得到该资源,那么就会进入死锁状态。
死亡状态:当线程运行完成或者调用了结束线程的方法后,线程就会进入死亡状态,JVM会来回收线程所占用的资源。
总结:
进程是一个在其自身的内存地址中的一次执行活动。例如打开一个记事本就是调用了一个进程。进程是申请资源,调度和独立运行的单位,因此,它使用系统中的运行资源;而程序不能申请资源,一个程序可以有多个进程,线程允许程序控制流的多重分支同时存在于一个进程。
多线程的优点:
更好的实现并行
恰当的使用多线程,能够降低维护和开发的开销,并且能够提高复杂应用的性能。因为CPU在线程间的转换开销是非常低的,线程是在一个进程的同一地址内,不需要有关于地址空间以及其他工作。
创建和撤销线程的开销低于进程。
Java中线程存在哪几种状态:新建状态,就绪状态,运行状态,等待/阻塞状态,死亡状态。
2.创建以及调度
由于直接继承Thread类存在局限性,因为Java是单继承机制,一旦继承了thread,那么就无法再继承其他类。
所以还可以使用另一种方式:通过实现java.lang.Runnable接口来实现线程:
Runnable满足函数是接口,可以使用lambda表达式来完成run方法的重写。
Runnable接口的子类不是线程类,只是通过这种形式来给线程提供一个run方法,最后还要依赖Runnable接口和Thread类的依赖关系,使用Thread的构造方法来构件线程对象。
示例代码:
lambda表达式运用:
任何一个Java程序中都含有一个主线程(main),主线程和其他线程的区别:
它是产生其他线程的线程;
通常它必须是最后结束,因为它要执行其他线程的关闭工作。
如果直接通过线程对象来调用run方法,那么不会起到线程的作用,只是会单纯的运行一次run方法,只有在使用start方法时,才会起到线程的作用。
注意:start()方法被调用后,并不是立即去执行多线程代码,而是需要去等待,由就绪状态到运行状态,等待时间片的获取。所以什么时候线程开始执行是由系统调度来决定的。不能人为控制。
Thread线程类中有些方法已经被弃用,例如stop()停止一个已启动的线程,因为这个方法会解锁这个线程中监视的程序。可能会造成程序的对象不一致,或者引发死锁。
最终只有start()方法得到了保留
那么如果想要结束一个线程,可以通过一个共享变量来解决,通过共享变量值的改变,来使得run方法执行结束。例如下列代码中的共享变量(flag):
还有一种情况,如果当线程发生了等待/阻塞,那又该怎么停止线程呢?
在调用Thread的sleep方法,Object的wait方法等方法时,可能会造成线程阻塞,使线程处于不可执行的状态,即使使用共享变量,也因为不会执行到检查循环标志而无法停止线程。
所以可以使用thread类中的interrupt()方法,因为该方法虽然不会中断一个正在运行的线程,但是会使一个被阻塞的线程抛出一个中断异常,从而使线程提前结束阻塞状态,退出阻塞代码。
因为在catch中捕获异常后,还是会继续向下执行,这时候使用共享变量,改变共享变量的值就会在检查循环标志时,终止循环,从而终止线程。
Thread类中有两个方法可以判断线程是否通过interrupt方法被终止:
一个是静态的interrupted():用来判断当前线程是否被中断。
一个是非静态的isInterrupted():是用来判断某个线程是否被中断。
而且,在捕获异常InterruptedException时,并不一定是interrupt中断而抛出的异常,也有可能来自其他原因,这时候就需要编写代码来区分。
inputrupt()方法并不能阻断I/O阻塞或线程同步引起的线程阻塞,当scanner发生异常时,InterruptedException异常并不会进行捕获。
如何处理I/O资源引起的线程阻塞而导致线程中断问题呢?
答案是关闭底层的I/O通道,人为引发异常,再使用共享变量使其跳出run方法,线程终止。