day29_大数据第一阶段总结(二)

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.HashSetjava.util.LinkedHashSetjava.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命名。
 

  1. 虽然读取了一个字节,但是会自动提升为int类型。

  2. 流操作完毕后,必须释放系统资源,调用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. 一个对象要想序列化,必须满足两个条件:1、实现java.io.Serializable 接口2、属性不被使用transient 关键字修饰

  2. 写出对象方法:public final void writeObject (Object obj) : 将指定的对象写出。out.writeObject(e);

2、ObjectInputStream类

ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。

构造方法

public ObjectInputStream(InputStream in): 创建一个指定InputStream的ObjectInputStream。
反序列化操作

public final Object readObject () : 读取一个对象。

(十一)缓冲流

  • 字节缓冲流BufferedInputStreamBufferedOutputStream

  • 字符缓冲流BufferedReaderBufferedWriter

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. 同步代码块。

  2. 同步方法。

  3. 锁机制。

1、同步代码块

格式:

synchronized(同步锁){

    需要同步操作的代码

}

同步锁

  1. 锁对象 可以是任意类型。

  2. 多个线程对象 要使用同一把锁

注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着。

多个线程看到的必须是同一把锁才能实现线程互斥

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());;
    }
}

枚举类的特点

  • 枚举类很安全,不能被实例化对象,甚至使用反射也不能创建对象.

  • 枚举常量必须最先声明,并且常量之间是有逗号隔开,最后一个常量是有分号.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值