java中级
一、异常
异常的含义
异常是java处理错误的一种机制。
设计良好的程序应该在异常发生的时候提供处理这些错误的方法,使得程序不会因为异常的发生而阻断。
Java的异常处理是通过5个关键字来实现的:try、catch、 finally、throw、throws。
1、使用try catch 来捕获异常
把异常的发现和处理相分离
2、异常体系
Throwable中的常用方法:
public void printStackTrace():打印异常的详细信息。
包含了异常的类型,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace。
public String getMessage():获取发生异常的原因。
提示给用户的时候,就提示错误原因。
public String toString():获取异常的类型和异常描述信息(不用)。
3、异常分类
4、异常处理
-
自行处理
使用try{}catch(){}语句,保证程序可以正常运行。
-
抛出异常
需要使用throws和throw向上抛出交由调用者处理。
throw关键字的使用
手动抛出异常
声明异常throws
自动抛出异常
finally关键字
finally:有一些特定的代码无论异常是否发生,都需要执行。另外,因为异常会引发程序跳转,导致有些语句执行不到。而finally就是解决这个问题的,在finally代码块中存放的代码都是一定会被执行的。
finally的语法:
try...catch....finally:自身需要处理异常,最终还得关闭资源。
注意:finally不能单独使用。
5、常见异常举例
二、正则表达式
正则表达式是一个强大的字符串处理工具,可以对字符串进行查找、提取、分割、替换等工作。
正则表达式是一个用来匹配字符串的模板。
(一)判断字符串是否匹配正则表达式的方式
1、方式一
1 将一个字符串编译成Pattern对象。
Pattern p = Pattern.compile(正则表达式);
2 使用Pattern对象创建Matcher对象
Matcher m = p.matcher(要匹配的字符串);
3 调用Matcher类的matches()方法
m.matches() 返回true表示匹配,返回false表示不匹配。
2、方式二
Pattern.matches(正则,字符串)
3、方式三
String类下提供了直接匹配的方法。
str.matches(正则)
(二)正则表达式符号
1、预定义字符类
2、Greedy 数量词
X? | X,一次或一次也没有 |
---|---|
X* | X,零次到多次 |
X+ | X,一次或多次 |
X{n} | X,恰好n次 |
X{n,} | X,至少n次 |
X{n,m} | X,至少n次,但是不超过m次 |
3、范围表示
4、单词边界
\b 表示单词边界,除了\w中的字符外的其他字符都可以作为单词边界。
(三)分组
使用分组:在正则表达式中,使用()来表示分组。(ab){3}。
所谓分组就是使用小括号将一些项包括起来,使其成为独立的逻辑域,那么就可以像处理一个独立单元一样去处理小括号的内容。
这种分组叫做捕获组,可以做反向引用。
注意: 反向引用指的就是使用\\组号 来表示引用前面哪一组中的内容。
示例:String regex = "((a)(b(c)))\\3+";
在普通字符串组号用$数字表示
三、集合框架
(一)概述
-
数组的长度是固定的。集合的长度是可变的。
-
数组中存储的是同一类型的元素,可以存储基本数据类型值。集合存储的都是对象。而且对象的类型可以不一致。在开发中一般当对象多的时候,使用集合进行存储
(二)分类
(三)Collection相关Api
Collection是所有单列集合的父接口,因此在Collection中定义了单列集合(List和Set)通用的一些方法,这些方法可用于操作所有的单列集合。方法如下:
int size(); 获取集合的长度
boolean isEmpty(); 判断集合是否为空
void clear(); 清空集合
boolean contains(Object element); 判断当前元素是否存在于集合内
boolean add(Object element); 向集合中添加元素
boolean remove(Object element); 根据参数删除集合中的元素
boolean containsAll(Collelction c); 判断一个集合中的元素是否存在于当前的集合中
boolean addAll(Collelction c); 向一个集合内部添加另一个集合
boolean removeAll(Collelction c); 在一个集合中删除参数指定的集合
Object[] toArray 把一个集合转换为Object数组
四、泛型
JDK1.4之前类型不明确,装入集合的类型都被当作Object对待,从而失去自己的类型。从集合中取出时往往需要类型转换,效率低下,易出错。
解决办法: JDK1.5开始,增加了泛型的功能。
定义集合时,同时定义集合中元素的类型。可以在类或方法中预支地使用未知的类型。
好处
-
将运行时期的ClassCastException,转移到了编译时期变成了编译失败。
-
避免了类型强转的麻烦。
tips:没有指定泛型时,默认类型为Object类型。
(一)泛型通配符
不知道使用什么类型来接收的时候,此时可以使用?,?表示未知通配符。
public static void getElement(Collection<?> coll){}
泛型的上限:
-
格式:
类型名称 <? extends 类 > 对象名称
-
意义:
只能接收该类型及其子类
泛型的下限:
-
格式:
类型名称 <? super 类 > 对象名称
-
意义:
只能接收该类型及其父类型
五、Iterator迭代器
迭代:即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。
常用方法如下:
-
public boolean hasNext()
:如果仍有元素可以迭代,则返回 true。
-
public E next()
:返回迭代的下一个元素。
2、增强for
增强for循环(也称for each循环)是JDK1.5以后出来的一个高级for循环,专门用来遍历数组和集合的。它的内部原理其实是个Iterator迭代器,所以在遍历的过程中,不能对集合中的元素进行增删操作。
六、Set
Set
集合有多个子类,这里我们介绍其中的java.util.HashSet
、java.util.LinkedHashSet
、java.util.TreeSet
这三个集合。
(一)HashSet
用Hash技术实现的Set结构。
无序:和添加时的顺序可能不同。
由于Set集合是不能存入重复元素的集合。那么HashSet也是具备这一特性的。
总结:
元素的哈希值是通过元素的hashcode方法 来获取的, HashSet首先判断两个元素的哈希值,如果哈希值一样,接着会比较equals方法 如果 equls结果为true ,HashSet就视为同一个元素。如果equals 为false就不是同一个元素。
哈希值相同equals为false的元素是怎么存储呢,就是在同样的哈希值下顺延(可以认为哈希值相同的元素放在一个哈希桶中)。也就是哈希一样的存一列。
(二)TreeSet
TreeSet是一个可排序集合,基于红黑树(自平衡二叉树),默认按元素的自然顺序升序排列。
TreeSet集合排序的两种方式:
第一种方式让元素自身具备比较性。也就是元素需要实现Comparable接口,重写compareTo 方法。
这种方式也作为元素的自然排序,也可称为默认排序。
1、Comparable接口
compareTo 方法:this和参数的比较。
-
返回正数 this比参数大 (this排在参数后面)
-
返回负数 this比参数小 (this排在参数前面)
-
返回零 this和参数相等(认为是重复数据)
2、Comparator接口
当元素自身不具备比较性,或者元素自身具备的比较性不是所需的。
那么这时只能让容器自身具备。
定义一个类实现Comparator 接口,重写compare方法。并将该接口的子类对象作为参数传递给TreeSet集合的构造方法。
注意:当Comparable比较方式,及Comparator比较方式同时存在,以Comparator比较方式为主。
(三)LinkedHashSet
添加数据有序
在HashSet下面有一个子类java.util.LinkedHashSet
,它是链表和哈希表组合的一个数据存储结构。
七、list接口
List接口是Collection的子接口,实现List接口的容器类中的元素是有顺序的,而且可以重复。
主要实现类有 ArrayList 和 LinkedList。
List接口中的方法
相对Collection接口扩展的方法
Object get(int index); 通过索引获取元素
Object set(int index,Object element); 修改某出索引元素
void add(int index,Object element); 在某处索引添加元素
Object remove(int index); 根据索引删除元素
(一)ArrayList
元素增删慢,查找快
(二)LinkedList
ArrayList与LinkedList的区别?
-
他们都是线性表,但是ArrayList基于数组(顺序存储),LinkedList基于链表(链式存储)
-
由于实现的不同,ArrayList在随机访问元素时性能较高,插入和删除元素时效率较低,LinkedList则反之。
(三)Vector和Stack(栈)
1、Vector的使用
Vector: 描述的是一个线程安全的ArrayList。
-
Vector与ArrayList的区别
ArrayList: 多线程效率高。
Vector : 多线程安全的,所以效率低。
2、Stack(栈)
Stack类是Vector类的子类。特点是先进后出。
(四)可变参数
修饰符 返回值类型 方法名(参数类型... 形参名){ }
其实这个书写完全等价与
修饰符 返回值类型 方法名(参数类型[] 形参名){ }
只是后面这种定义,在调用时必须传递数组,而前者可以直接传递数据即可。
JDK1.5以后。出现了简化操作。... 用在参数上,称之为可变参数。
同样是代表数组,但是在调用这个带有可变参数的方法时,不用创建数组(这就是简单之处),直接将数组中的元素作为实际参数进行传递,其实编译成的class文件,将这些元素先封装到一个数组中,在进行传递。这些动作都在编译.class文件时,自动完成了。
八、操作集合的工具类 Collections
java.utils.Collections是集合工具类,用来对集合进行操作。部分方法如下:
sort(List<T> list); 对List集合进行默认排序
addAll(Collection<T>,T...e);向集合中添加可变参数元素
fill(List<E> list, E e):将list集合中的所有元素都填充为元素e
int frequency(Collection<E> c, E e):返回在集合c中的元素e的个数
max、min:获取集合的最大值或者最小值
replaceAll(List<E> list, E oldVal, E newVal):将集合list中的所有指定老元素oldVal都替换成新元素newVal
reverse(List<E> list):将参数集合list进行反转
shuffle(List<E> list):将list集合中的元素进行随机置换
swap(List<E> list, int a, int b):将a索引和b索引的元素进行交换
synchronizedXxx方法系列:将一个线程不安全的集合传入方法,返回一个线程安全的集合
九、Map
-
Collection
中的集合称为单列集合,Map<K,V>
中的集合称为双列集合。 -
需要注意的是,
Map
中的集合不能包含重复的键,值可以重复;每个键只能对应一个值。
常用子类
通过查看Map接口描述,看到Map有多个子类,这里我们主要讲解常用的HashMap集合、LinkedHashMap集合。
HashMap<K,V>:存储数据采用的哈希表结构,元素的存取顺序不能保证一致。由于要保证键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。
LinkedHashMap<K,V>:HashMap下有个子类LinkedHashMap,存储数据采用的哈希表结构+链表结构。通过链表结构可以保证元素的存取顺序一致;通过哈希表结构可以保证的键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。
(一)map中常用方法
put(K key, V value) : 向Map中添加数据,成功返回会被覆盖的value,key重复时,后添加会覆盖先添加的。
putAll(Map<? extends K,? extends V> m): 向Map中添加另一个Map集合。
isEmpty(): Map中是否包含数据 size() :Map中包含键值对的个数
get(key): 根据key获取value
clear() :清空Map
containsKey(key) :判断Map中是否包含key
containsValue(value) :判断Map中是否包含value
keySet() :返回Map中所有key 组成的Set集合
values(): 返回Map中所有value组成的集合 (返回值Collection类型)
entrySet(): 返回此映射中包含的映射关系的 Set 视图
HashMap是Map接口的最常用实现类,底层是哈希表形式存储的,非线程安全的,允许有null键值对。
(二) Entry键值对对象
-
public K getKey()
:获取Entry对象中的键。 -
public V getValue()
:获取Entry对象中的值。
在Map集合中也提供了获取所有Entry对象的方法:
-
public Set<Map.Entry<K,V>> entrySet()
: 获取到Map集合中所有的键值对对象的集合(Set集合)。
(三)HashTable
Hashtable是一个比较古老的类(从JDK1.2开始),特点是线程安全的,不允许有null键值对。用法和HashMap相同,基本被HashMap替代。
面试常见问题HashMap和Hashtable的区别?
HashMap: 线程不安全,允许null键值对
Hashtable: 线程安全,不允许null键值对
(四)TreeMap
特点:会根据key值进行升序排列。
(五)LinkedHashMap
是HashMap的一个子类。
和HashMap的不同之处在于,具有可预知的迭代顺序,存储键值对的顺序和遍历集合时取出键值对的顺序一致。
九、文件File
1、构造方法
public File(String pathname) :通过将给定的 路径名字符串 转换为抽象路径名来创建新的 File实例。
public File(String parent, String child) :从 父路径名字符串 和 子路径名字符串 创建新的 File实例。
public File(File parent, String child) :从 父抽象路径名 和 子路径名字符串 创建新的 File实例。
小贴士:无论该路径下是否存在文件或者目录,都不影响File对象的创建。
2、绝对路径和相对路径
-
绝对路径:从盘符开始的路径,这是一个完整的路径。linux从根开始。
-
相对路径:相对于项目目录的路径,这是一个便捷的路径,开发中经常使用。
3、常用方法
获取功能的方法
public String getAbsolutePath() :返回此File的绝对路径名字符串。
public String getPath() :将此File转换为路径名字符串。
public String getName() :返回由此File表示的文件或目录的名称。
public long length() :返回由此File表示的文件的长度
判断功能的方法
public boolean exists() :此File表示的文件或目录是否实际存在。
public boolean isDirectory() :此File表示的是否为目录。
public boolean isFile() :此File表示的是否为文件。
创建删除功能的方法
public boolean createNewFile() :当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。
public boolean delete() :删除由此File表示的文件或目录。
public boolean mkdir() :创建由此File表示的目录。 可以创建单级目录
public boolean mkdirs() :创建由此File表示的目录,包括任何必需但不存在的父目录。 可以创建多级目录
API中说明:delete方法,如果此File表示目录,则目录必须为空才能删除。
目录的遍历方法
-
public String[] list()
:返回一个String数组,表示该File目录中的所有子文件或目录。
-
public File[] listFiles()
:返回一个File数组,表示该File目录中的所有子文件或目录。
十、IO流
顶级父类
输入流 | 输出流 | |
---|---|---|
字节流 | 字节输入流 InputStream | 字节输出流 OutputStream |
字符流 | 字符输入流 Reader | 字符输出流 Writer |
(一)OutputStream
public void close() :关闭此输出流并释放与此流相关联的任何系统资源。
public void write(byte[] b):将 b.length字节从指定的字节数组写入此输出流。 [97,98,99,100]
public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。
public abstract void write(int b) :将指定的字节输出。
(二)FileOutputStream类
构造方法
-
public FileOutputStream(File file)
:创建文件输出流以写入由指定的 File对象表示的文件。 -
public FileOutputStream(String name)
: 创建文件输出流以指定的名称写入文件。
写出字节数据
1、写出字节:write(int b)
2、写出字节数组:write(byte[] b)
3、写出指定长度字节数组:write(byte[] b, int off, int len)
(三)InputStream
public void close() :关闭此输入流并释放与此流相关联的任何系统资源。
public abstract int read(): 从输入流读取数据的下一个字节。
public int read(byte[] b): 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。
(四)FileInputStream
FileInputStream(File file): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。
FileInputStream(String name): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。
-
虽然读取了一个字节,但是会自动提升为int类型。
-
流操作完毕后,必须释放系统资源,调用close方法,千万记得。
使用字节数组读取:read(byte[] b)
,每次读取b的长度个字节到数组中,返回读取到的有效字节个数,读取到末尾时,返回-1
,代码使用演示:
(五)FileReader类
构造方法
FileReader(File file): 创建一个新的 FileReader ,给定要读取的File对象。
FileReader(String fileName): 创建一个新的 FileReader ,给定要读取的文件的名称。
当你创建一个流对象时,必须传入一个文件路径。类似于FileInputStream 。
读取字符数据
1、读取字符:read
方法,每次可以读取一个字符的数据,提升为int类型,读取到文件末尾,返回-1
,循环读取,代码使用演示:
2、使用字符数组读取:read(char[] cbuf)
,每次读取b的长度个字符到数组中,返回读取到的有效字符个数,读取到末尾时,返回-1
,代码使用演示:
(六)字符输出流【Writer】
public abstract void close() :关闭此输出流并释放与此流相关联的任何系统资源。
public abstract void flush() :刷新此输出流并强制任何缓冲的输出字符被写出。
public void write(int b) :写出一个字符。
public void write(char[] cbuf):将 b.length字符从指定的字符数组写出此输出流。
public abstract void write(char[] b, int off, int len) :从指定的字符数组写出 len字符,从偏移量 off开始输出到此输出流。
public void write(String str) :写出一个字符串。
(七)FileWriter类
构造方法
-
FileWriter(File file)
: 创建一个新的 FileWriter,给定要读取的File对象。 -
FileWriter(String fileName)
: 创建一个新的 FileWriter,给定要读取的文件的名称。
基本写出数据
写出字符:write(int b)
方法
关闭和刷新
-
flush
:刷新缓冲区,流对象可以继续使用。 -
close
:关闭流,释放系统资源。关闭前会刷新缓冲区。
小贴士:即便是flush方法写出了数据,操作的最后还是要调用close方法,释放系统资源。
写出其他数据
1、写出字符数组 :write(char[] cbuf)
和 write(char[] cbuf, int off, int len)
,每次可以写出字符数组中的数据,用法类似FileOutputStream,代码使用演示:
2.写出字符串:write(String str)
和 write(String str, int off, int len)
,每次可以写出字符串中的数据,更为方便,代码使用演示:
小贴士:字符流,只能操作文本文件,不能操作图片,视频等非文本文件。
(八)序列流(也称为合并流)
SequenceInputStream(InputStream s1, InputStream s2)
把流s1和流s2合并成一个输入流对象
(九)数据流
1、DataOutputStream
writeInt(int):一次写入四个字节。注意和write(int)不同。write(int)只将该整数的最低一个8位写入。剩余三个8位丢弃。
writeBoolean(boolean);
writeShort(short);
writeLong(long);
剩下是数据类型也也一样。
writeUTF(String):按照utf-8将字符数据进行输出。只能通过readUTF读取。
2、DataInputStream
int readInt():一次读取四个字节,并将其转成int值。
boolean readBoolean():一次读取一个字节。
short readShort();
long readLong();
String readUTF():按照utf-8修改版读取字符。注意,它只能读writeUTF()写入的字符数据。
(十)序列化流
Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据
、对象的类型
和对象中存储的数据
等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据
、对象的类型
和对象中存储的数据
信息,都可以用来在内存中创建对象。看图理解序列化:
1、ObjectOutputStream类
构造方法
-
public ObjectOutputStream(OutputStream out)
: 创建一个指定OutputStream的ObjectOutputStream。
序列化操作
-
一个对象要想序列化,必须满足两个条件:1、实现
java.io.Serializable
接口2、属性不被使用transient
关键字修饰 -
写出对象方法:
public final void writeObject (Object obj)
: 将指定的对象写出。out.writeObject(e);
2、ObjectInputStream类
ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。
构造方法
public ObjectInputStream(InputStream in): 创建一个指定InputStream的ObjectInputStream。
反序列化操作
public final Object readObject ()
: 读取一个对象。
(十一)缓冲流
-
字节缓冲流:
BufferedInputStream
,BufferedOutputStream
-
字符缓冲流:
BufferedReader
,BufferedWriter
1、字节缓冲流
构造方法
-
public BufferedInputStream(InputStream in)
:创建一个 新的缓冲输入流。 -
public BufferedOutputStream(OutputStream out)
: 创建一个新的缓冲输出流。
2、字符缓冲流
构造方法
-
public BufferedReader(Reader in)
:创建一个 新的缓冲输入流。 -
public BufferedWriter(Writer out)
: 创建一个新的缓冲输出流。
特有方法
-
BufferedReader:
public String readLine()
: 读一行文字。 -
BufferedWriter:
public void newLine()
: 写一行行分隔符
bw.write(str);
(十二)打印流(自带缓冲区)
构造方法
PrintStream(File f) 通过file对象构建字节打印流对象
PrintStream(String path) 通过String路径构建字节打印流对象
PrintStream(OutputStream os):将一个普通的字节输出流,包装成打印流,就可以使用print和println等方法了
该类中除了有write方法外,用提供了print方法和println方法,用来写数据。
(十三)转换流(自带缓冲区)
字节流转换为字符流。
有时,我们需要将字节流转换为字符流,使用InputStreamReader和OutputStreamWriter。
OutputStream os = new FileOutputStream("c:/bbb.txt");
Writer writer = new OutputStreamWriter(os);
InputStream is = new FileInputStream("c:/aaa.txt");
Reader reader = new InputStreamReader(is);
十一、属性集
java.util.Properties
继承于Hashtable
,来表示一个持久的属性集。它使用键值结构存储数据,每个键及其对应值都是一个字符串。该类也被许多Java类使用。
1、Properties类
构造方法
public Properties() :创建一个空的属性列表。
基本的存储方法
public Object setProperty(String key, String value) : 保存一对属性。
public String getProperty(String key) :使用此属性列表中指定的键搜索属性值。
public Set<String> stringPropertyNames() :所有键的名称的集合。
2、与流相关的方法
public void load(InputStream inStream)
: 从字节输入流中读取键值对。
小贴士:文本中的数据,必须是键值对形式,可以使用空格、等号、冒号等符号分隔。
public void store(outputtStream outStream,String comment)
:使用输出流与properties进行交互
示例代码
FileInputStream fis = new FileInputStream("file.properties");
Properties pp = new Properties();
pp.load(fis);
pp.setProperty("address", "beiji1ng");
pp.setProperty("db", "mysql");
FileOutputStream fos = new FileOutputStream("file.properties");
pp.store(fos, "update 更新说明");
十二、多线程
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
线程:进程内部的一个独立执行单元;一个进程可以同时并发的运行多个线程,可以理解为一个进程便相当于一个单 CPU 操作系统,而线程便是这个系统中运行的多个任务。
进程与线程的区别
-
进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。
-
线程:堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小的多。
线程调度:抢占式调度
(一)继承Thread类
常用方法:
public String getName():获取当前线程名称。
public void start():导致此线程开始执行; Java虚拟机调用此线程的run方法。
public void run():此线程要执行的任务在此处定义代码。
public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
public static Thread currentThread():返回对当前正在执行的线程对象的引用。
(二)实现Runnable接口
步骤如下:
1、定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
2、创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
public Thread(Runnable target):分配一个带有指定目标新的线程对象。
public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字。
3、调用线程对象的start()方法来启动线程。
Thread和Runnable的区别
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
总结:
实现Runnable接口比继承Thread类所具有的优势:
适合多个相同的程序代码的线程去共享同一个资源。
可以避免java中的单继承的局限性。
线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类。
扩充:在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实在就是在操作系统中启动了一个进程。
(三)线程的生命周期
1、生命周期概述
线程是一个动态的概念,有创建的时候,也有运行和变化的时候,必须也就有消亡的时候,所以从生到死就是一个生命周期。在生命周期中,有各种各样的状态,这些状态可以相互转换。
新建态:刚创建好对象的时候,刚new出来的时候
就绪态:线程准备好了所有运行的资源,只差cpu来临
运行态:cpu正在执行的线程的状态
阻塞态:线程主动休息、或者缺少一些运行的资源,即使cpu来临,也无法运行
死亡态:线程运行完成、出现异常、调用方法结束
(四)线程的相关方法
1、线程的join方法
非静态方法,当前线程挂起(阻塞),等待加入(join)的线程执行完成。
当前线程调用t.join()意味着,当前线程挂起直到目标线程t结束。
// 主线程挂起(暂停), 当t1线程运行结束后,主线程继续
2、线程的yield方法
yield方法会短暂地让出cpu,当前线程并不是进入阻塞状态,而是进入就绪状态。
3、守护线程(后台线程)
这些“系统线程”被称为“守护线程”,java程序在所有非守护线程完成后退出
守护线程只有在别的线程运行的时候才有意义。
后台线程的特征: 当所有前台线程都运行完成后,无论后台线程是否运行完成都要结束。
(注意:是在一段时间内结束,并不会立即结束)
实现:
setDaemon(boolean on)
(五)线程同步
当我们使用多个线程访问统一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。
要解决上述多线程并发访问多一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制(synchronized)来解决。
为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。
那么怎么去使用呢?有三种方式完成同步操作:
-
同步代码块。
-
同步方法。
-
锁机制。
1、同步代码块
格式:
synchronized(同步锁){
需要同步操作的代码
}
同步锁
-
锁对象 可以是任意类型。
-
多个线程对象 要使用同一把锁
注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着。
多个线程看到的必须是同一把锁才能实现线程互斥
2、同步方法
-
同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。
格式:
public synchronized void method(){
可能会产生线程安全问题的代码
}
3、Lock锁
java.util.concurrent.locks.Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
Lock锁也称同步锁,加锁与释放锁方法化了,如下:
public void lock():加同步锁。
public void unlock():释放同步锁。
Lock lock = new ReentrantLock();
lock.lock();
lock.unlock();
(六)死锁
多个线程竞争多个排他锁的时候,可能出现死锁。
互相等待对方资源,称之为死锁
(七)线程协作
这种协作是通过线程之间的握手来实现的,这种握手可以通过Object的wait()和notify()来安全的实现。
1、生产者和消费者的问题
package com.offcn;
import java.util.ArrayList;
import java.util.List;
public class Demo2 {
// 生产者和消费者的问题
// 有一家汉堡店举办吃汉堡比赛,决赛时有3个顾客来吃,3个厨师来做,一个服务员负责协调汉堡的数量。
// 为了避免浪费,制作好的汉堡被放进一个能装有10个汉堡的长条状容器中,按照先进先出的原则取汉堡。
public static List<String> list =new ArrayList<>();
public static void main(String[] args) {
chushi c1=new chushi();c1.setName("张大厨");
chushi c2 =new chushi();c2.setName("张二厨");
chushi c3=new chushi();c3.setName("张三厨");
chike k1=new chike();chike k2=new chike();chike k3=new chike();
k1.setName("李大吃");k2.setName("李二吃");k3.setName("李三吃");
c1.start();c2.start();c3.start();
k1.start();k2.start();k3.start();
}
}
class chushi extends Thread
{
public static int i=0;
@Override
public void run() {
while (true) {
//做汉堡
synchronized ("chi") {
if (Demo2.list.size() < 10) {
System.out.println(getName() + "做第" + (++i) + "个汉堡");
Demo2.list.add("第" + (i) + "个汉堡");
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//做完之后服务员叫所有吃客来拿汉堡,抢着拿,先到先得
"chi".notifyAll();
} else {
//汉堡做满了,可以休息一下
try {
"chi".wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
class chike extends Thread {
@Override
public void run() {
while (true) {
synchronized ("chi") {
if (Demo2.list.size() > 0)//窗口有汉堡我就拿来吃
{
System.out.println(getName() + "吃" + Demo2.list.get(0));//只能取最早生产的汉堡
Demo2.list.remove(0);
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
"chi".notifyAll();//服务员叫醒所有师傅补仓
} else {
//没有汉堡,看会手机等下
try {
"chi".wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
wait和sleep区别:
1、sleep是Thread类的静态方法,wait是object类下的方法
2、sleep需要参数来指定当前线程挂起多少毫秒,时间到了自动回复就绪状态,等待cpu分配时间
;wait让线程挂起,没有时间限制,等到notify或notifyAll进行唤醒
3、wait在挂起后释放资源(锁),sleep不会释放资源(锁)
(八)线程池
合理利用线程池能够带来三个好处:
降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
1、线程池创建方式
public static ExecutorService newFixedThreadPool(int nThreads)
:返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)
获取到了一个线程池ExecutorService 对象,那么怎么使用呢,在这里定义了一个使用线程池对象的方法如下:
public Future<?> submit(Runnable task):获取线程池中的某一个线程对象,并执行
public Future<T> submit(Callable<T> task):将Callable对象传给submit,Future是 call()方法调用后的返回值。
shutdown():调用这个方法后,线程池将不会再接受新任务,但原有的任务会继续执行,直到执行完成
shutdownNow():调用这个方法关闭线程池,没有执行完成的任务会暂停。
2、线程池的使用
class A implements Runnable{
@Override
public void run() {
for(int i=1;i<=100;i++){
System.out.println(Thread.currentThread().getName()+"<<<"+i);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public class Test2 {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(6);
pool.submit(new A());
pool.submit(new A());
pool.shutdown();
}
}
(九)Callable接口
Callable接口是对Runnable接口的增强,提供了一个call()方法,作为线程的执行体。但call()方法比run()方法功能更强大。
-
call()方法可以有返回值
-
call()方法可以抛出异常
(十)垃圾回收GC
在Java虚拟机的生命周期中,Java程序会陆续地创建无数个对象,假如所有的对象都永久占有内存,那么内存有可能很快被消耗光,最后引发内存空间不足的错误。因此必须采取一种措施来及时回收那些无用对象的内存,以保证内存可以被重复利用。
-
System.gc()方法:
告诉JVM垃圾回收,但是至于JVM是否会回收是没办法控制的。 -
finalize()方法:
当这个类的对象被垃圾回收时,会执行finalize()方法。 在类里面重写
十三、枚举
声明一个枚举类型,使用的关键字是enum,声明出来的也是一个类,编译出来也是一个.class的文件,只不过是补充了一些内容。也可以称为多例模式。
格式
public enum 名字{ 枚举项1,枚举项2; }
从Java5开始推出枚举类型。
package com.offcn.dmeo2;
public enum WeekDay1 {
X1("星期一"),X2("星期二"),X3("星期三"),X4("星期四"),X5("星期五"),X6("星期六"),X7("星期日");
private String dayName;
WeekDay1(String dayName) {
this.dayName = dayName;
}
public String getDayName() {
return dayName;
}
public void setDayName(String dayName) {
this.dayName = dayName;
}
}
package com.offcn.dmeo2;
public class Employee {
private WeekDay1 w;
public WeekDay1 getW() {
return w;
}
public void setW(WeekDay1 w) {
this.w = w;
}
}
package com.offcn.dmeo2;
public class TestEmp {
public static void main(String[] args) {
Employee e = new Employee();
e.setW(WeekDay1.X3);
System.out.println(e.getW().getDayName());;
}
}