目录
【写在前面】
前文链接:Java入门(三):进阶
2. Java进阶
2.6 Java常用类
2.6.1 IO
2.6.1.1 定义
Java核心库 java.io提供了全面的IO接口:文件读写,标准设备输出等。
Java IO是以流为基础进行输入输出的,所有数据被串行化写入输出流,或者从输入流读入。
2.6.1.2 IO的分类
字节流 VS 字符流
输入流 VS 输出流
(1)根据处理的数据类型不同:字节流和字符流
字符流的由来:因为文件编码的不同,而有了对字符进行高效操作的字符流对象。
字符流的原理:基于字节流读取字节时,去查了指定的码表。
字符流
Reader
BufferedReader
InputStreamReader
FileReader
Writer
BufferedWriter
OutputStreamWriter
FileWriter
字节流
InputStream
FileInputStream
FilterInputStream
BufferedInputStream
OutputStream
FileOutputStream
FilterOutputStream
BufferedOutputStream
(2)根据流向不同:输入流和输出流
(3)字节流和字符流的区别
1)字节流读取时,读到一个字节就返回一个字节。
字符流使用字节流读到一个或多个字节时,先去查指定的编码表,将查到的字符返回。
(中文对应的字节数是两个,在UTF-8码表中是3个字节)
2)字节流可以处理所有类型数据,如图片,MP3,AVI。
而字符流只能处理字符数据。
结论:只要是处理纯文本数据,就优先考虑字符流,其他就用字节流。
(4)转换流
具体的对象体现:
1)InputStreamReader: 字节到字符的桥梁
2)OutputStreamWriter: 字符到字节的桥梁
特点:
(1)是字节流和字符流之间的桥梁
(2)该流对象中可以对读取到字节数据进行指定编码表的编码转换
应用场景:
(1)当字节和字符之间有转换动作时
(2)流操作的数据需要进行编码表的指定时
(5)Reader和Writer常用方法
测试开发中涉及的IO操作,大多与日志相关,所以重点看关于Reader和Writer相关的常用API 。
Reader
---InputStreamReader
---FileReader:专门用于处理文件的字符读取流对象。
Writer
---OutputStreamWriter
---FileWriter:专门用于处理文件的字符写入流对象
Reader中的常用方法:
int read():读取一个字符,返回的是读到的那个字符。如果读到流的末尾,返回-1。
int read(char[]):将读到的字符存入指定的数组中,返回的是读到的字符个数,即往数组里装的元素个数。如果读到末尾,返回-1。
close():读取字符后,进行资源释放。
Writer中的常用方法:
writer(int c):将一个字符写入到流中。
writer(char []):将一个字符数组写入到流中。
writer(String):将一个字符串写入到流中。
flush(): 刷新流,将流中的数据刷新到新的目的地中,流还在。
close():关闭资源,在关闭前会先调用flush(),刷新流中的数据去目的地,然后关闭流。
FileWriter(String filename):
1)调用系统资源
2)在指定位置,创建一个文件。如果文件已经存在,将会被覆盖。
FileWriter(String filename, boolean append) //当boolean为true时,会在指定文件末尾处进行数据的续写
代码示例:
FileWriter fw = new FileWriter("d:\\demo.txt");
fw.write("abcdef");
fw.flush();
fw.write("hahahaha");
fw.close();
FileReader(String filename):
1)用于读取文本文件的流对象
2)用于关联文本文件
3)综合应用(高级)
代码示例:
FileReader fr = new FileReader("d:/demo.txt");
int ch = 0;
while((ch=fr.read())!=-1){
System.out.print((char)ch);
}
备注:上面例子中,“\\”和“/”都可以用来处理文件路径分隔符。
流操作的基本规律
明确数据源和数据汇(数据目的)。即为了确定是输入流还是输出流。
明确操作的数据是否是纯文本数据。即为了确定是字符流还是字节流。
【常见码表】
ASCII:美国标准信息交换码。使用的是1个字节的7位来表示该表中的字符,首位符号位。
ISO 8859-1:拉丁码表。使用一个字节来表示。
GB2312:简体中文码表。
GBK:简体中文码表,比GB2312融入更多的中文文件和符号。
Unicode:国际标准码表。都用两个字节表示一个字符。
UTF-8:对unicode进行优化,每一个字节都加入标识头。
【实例】!!!!!!】
实例需求:将键盘录入的数据存储到一个文件中
分析思路:
1)数据源
System.in 输入流,包括InputStream, Reader。
因为键盘录入一定是纯文本数据,所以选择Reader。
因为System.in对应的是字节读取流,因此要进行转换,将字节转换成字符,所以选择InputStreamReader。
如果要提高效率,则加入字符流的缓冲区BufferedReader。
BufferedReader bur=new BufferedReader(new InputStreamReader(System.in))
2)数据
一个文件,硬盘。输出流,包括outputStreamWriter。
因为要往文件存储的都是文本数据,所以选择Writer。
因为操作的是一个文件,所以选择FileWriter。
如果要提高效率,则使用BufferedWriter。
Buffered bufr=new BufferedWriter(new FileWriter("a.txt"))
3)附加需求:"将文本数据按照执行的编码表存入文件中"
因为往文件存储的都是文本数据,所以选writer.
因为要指定编码表,所以要用writer中的转换流outputStreamWriter(OutputStreamWriter: 字符到字节的桥梁)
如果要提高效率,选择BufferedWriter(最终是文件但不能选择FileWriter, 因为其默认编码)
输出转换流要接收一个字节输出流进来,所以要使用OutputStream体系,最终输出到一个文件中,那么就需要使用OutputStream体系中可以操作文件的字节流对象。
代码片段:
//FileOutputStream
String charSet = System.getProperty("file.encoding");
String charSet = "utf-8";
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("a.txt"), charSet));
2.6.2 File类
2.6.2.1 定义
File类的对象,主要用来获取文件本身的一些信息,比如文件所在目录,文件长度,文件读写权限等,不涉及对文件的读写操作。
2.6.2.2 File类的构造方法
File(String filename)
File(String directoryPath, String filename)
File(file f, String filename)
2.6.2.3 文件属性
public String getName():获取文件的名字
public boolean canRead():判断文件是否可读
public boolean canWrite():判断文件是否可写
public boolean exits():判断文件是否存在
public long length():获取文件长度
public String getAbsolutePath():获取文件的绝对路径
public String getParent():获取文件的父目录
public boolean isFile():判断文件是否是一个正常文件而不是目录
public boolean isDirectory():判断文件是否一个目录
public boolean isHidden():判断文件是否是隐藏文件
public long lastModified():文件最后修改的时间(1990年五月至文件最后修改时刻的毫秒数)
2.6.2.4 目录
创建目录: public boolean mkdir()
如果File对象是一个目录,那么该对象可以调用下述方法列出该目录下的文件和子目录
public String[] list():用字符串形式返回
public File[] listFiles:用File对象形式返回
2.6.2.5 列出指定类型的文件
public String[] list(FilenameFilter obj):字符串形式目录下指定类型的所有数据。
public File[] listFiles(FilenameFilter obj):用File对象形式返回目录下指定类型的所有文件。其中,FilenameFilter是一个接口,该接口有一个方法。
public boolean accept(File dir, String name):当向list方法传递一个实现该接口的对象时,dir调用list方法在列出文件时,将调用accept方法检查该文件Name是否符合accept方法指定的目录和文件名字要求。
2.6.3 Java Logger
2.6.3.1 创建Logger对象
static Logger getLogger(String name):为指定子系统查找或创建一个logger
static Logger getLogger(String name, String resourceBundleName): 为指定子系统查找或创建一个logger
注意:name是Logger的名称,当名称相同时,同一个名称的Logger只能创建一个。
2.6.3.2 Logger的级别
全部定义在java.util.logging.Level里面。
级别降序:
SEVERE(最高值)
WARNING
INFO
CONFIG
FINE
FINER
FINEST(最低值)还有一个级别OFF, 用来关闭日志记录。
使用级别ALL启用所有消息的日志记录。
logger 默认级别INFO, 比INFO低的日志不显示。
logger 默认级别定义在jre安装目录的lib下面的logging.propertities文件中。
logger的命名:一般使用圆点分隔的层次命名空间来命名Logger
简单代码实例:
public class TestLogger{
public static void main(String[] args){
Logger log = Logger.getLogger("MyLog"); //创建Logger实例
log.setLevel(Level.INFO); //设置logger级别
Logger log1 = Logger.getLogger("MyLog");
System.out.println(log == log1); //返回true
Logger log2 = Logger.getLogger("MyLog");
log2.setLevel(Level.WARNING);
log.info("aaa");
log2.info("bbb");
log2.fine("fine");
}
}
2.6.3.3 Logger的Handler
Handler对象从Logger中获取日志信息,并将这些信息导出,比如导出到文件file中等。
可通过执行setLevel(Level.OFF)来禁用Handler,并可通过执行适当级别的setLevel来重新启用。
Handler类通常使用LogManager属性来设置Handler的Filter, Formatter和Level的默认值
java.util.logging.Handler
java.util.logging.MemoryHandler
java.util.logging.StreamHandler
java.util.logging.ConsoleHandler
java.util.logging.FileHandler
java.util.logging.SocketHandler
注意:
默认的日志方式是XML格式。如果想要自定义logger的格式,需要用Formatter来定义。
补充:
在配置文件logging.properties中,同样存在对handler的一些默认设置(控制台的,文件中的...)
简单代码实例:
public class TestLogger{
public static void main(String[] args) throws IOException{
Logger log = Logger.getLogger("MyLog");
log.setLevel(Level.INFO);
Logger log1 = Logger.getLogger("MyLog");
System.out.println(log == log1); //返回true
Logger log2 = Logger.getLogger("MyLog");
//log2.setLevel(Level.WARNING);
ConsoleHandler consoleHandler = new ConsoleHandler();
consoleHandler.setLevel(Level.ALL);
log.addHandler(consoleHandler);
FileHandler fileHandler = new FileHandler("D:/testLog.log");
fileHandler.setLevel(Level.INFO);
log.addHandler(fileHandler);
log.info("aaa");
log2.info("bbb");
log2.fine("fine");
}
}
控制台输出 如下,此外还生成D盘的testlog文件:
true
xxxx-xx-xx xx:xx:xx TestLogger main
信息:aaa
xxxx-xx-xx xx:xx:xx TestLogger main
信息:aaa
xxxx-xx-xx xx:xx:xx TestLogger main
信息:bbb
xxxx-xx-xx xx:xx:xx TestLogger main
信息:bbb
2.6.3.4 Logger的Formatter
Formatter为格式化LogRecords提供支持 。
一般每个日志记录Handler都有关联的Formatter。
Formatter接受LogRecord, 并将它转换为一个字符串。
有些formatter(如XML Formatter)需要围绕一组格式化记录来包装头部和尾部字符串。可以使用getHeader和getTail方法来获得这些字符串。注意:这俩方法参数是 Handler
LogRecord对象用于在日志框架和单个日志Handler之间传递日志请求。
LogRecord(Level level, String msg)。用给定级别和消息值构造LogRecord:
java.util.logging.Formatter
java.util.logging.SimpleFormatter
java.util.logging.XMLFormatter
说明:
(1)filehandler 和 consolehandler 输出的数据形式是不同的,这种形式的展示就是formater.
record 是数据集合,format是展现的形式。
我们从record中拿到各种的数据值,在format里进行填空format(LogRecord record) 。
(2)jdk默认的logging.properties中默认设置的fomatter, 控制台是SimpleFormatter, 文件是XMLFormater。
简单代码实例:
public class TestLogger{
public static void main(String[] args) throws IOException{
Logger log = Logger.getLogger("MyLog");
log.setLevel(Level.INFO);
Logger log1 = Logger.getLogger("MyLog");
System.out.println(log == log1); //返回true
Logger log2 = Logger.getLogger("MyLog");
//log2.setLevel(Level.WARNING);
ConsoleHandler consoleHandler = new ConsoleHandler();
consoleHandler.setLevel(Level.ALL);
log.addHandler(consoleHandler);
FileHandler fileHandler = new FileHandler("D:/testLog.log");
fileHandler.setLevel(Level.INFO);
fileHandler.setFormatter(new MyLogHander());
log.addHandler(fileHandler);
log.info("aaa");
log2.info("bbb");
log2.fine("fine");
}
}
class MyLogHandler extends Formatter{
@Override
public String format(LogRecord record){
return record.getLevel + ":" + reord.getMessage() + "\n";
}
}
控制台输出 + D盘的testlog文件输出如下:
INFO: aaa
INFO: bbb
实例1:
public class MyFormater extends SimpleFormatter { //重写格式
@Override
public String getHead(Handler h){
return "start\r\n";
}
@Override
public String getTail(Handler h){
return "end\r\n";
}
}
public class TestLoggerFormater {
public static void main(String[] args) {
Logger log = Logger.getLogger("MyLog2");
log.setLevel(Level.INFO);
ConsoleHandler consoleHandler = new ConsoleHandler();
consoleHandler.setFormatter(new MyFormater());//默认是xmlformater,StreamHandler调用getHead和getTail, 这里使用上面重写的格式 MyFormater
log.addHandler(consoleHandler);
log.info("aaa");
log.info("bbb");
log.warning("bbb");
consoleHandler.close();
}
}
输出结果:
start
九月 13, 2021 3:52:05 下午 MyFormater2.TestLoggerFormater main
信息: aaa
九月 13, 2021 3:52:05 下午 MyFormater2.TestLoggerFormater main
信息: aaa
九月 13, 2021 3:52:05 下午 MyFormater2.TestLoggerFormater main
信息: bbb
九月 13, 2021 3:52:05 下午 MyFormater2.TestLoggerFormater main
信息: bbb
九月 13, 2021 3:52:05 下午 MyFormater2.TestLoggerFormater main
警告: bbb
九月 13, 2021 3:52:05 下午 MyFormater2.TestLoggerFormater main
警告: bbb
实例2:
public class MyFormater extends Formatter { //重写格式
@Override
public String format(LogRecord record){ //传参是 LogRecord
return "******" + record.getLevel() + ":" + record.getMessage() + "******" + "\r\n";
}
@Override
public String getHead(Handler h){ //传参是 Handler
return "this is header \r\n";
}
@Override
public String getTail(Handler h){ //传参是 Handler
return "this is footer\r\n";
}
}
public class TestLoggerFormater {
public static void main(String[] args) throws IOException {
Logger log = Logger.getLogger("MyLog1");
log.setLevel(Level.INFO);
FileHandler fileHandler = new FileHandler("C:/Users/pearl/Desktop/road/log/log1.log");
fileHandler.setFormatter(new MyFormater());
log.addHandler(fileHandler);
log.info("ddd");
log.info("eee");
log.warning("fff");
fileHandler.close();
}
}
控制台输出:
九月 13, 2021 4:10:25 下午 MyFormater1.TestLoggerFormater main
信息: ddd
九月 13, 2021 4:10:25 下午 MyFormater1.TestLoggerFormater main
信息: eee
九月 13, 2021 4:10:25 下午 MyFormater1.TestLoggerFormater main
警告: fff输出日志文件log1.log;
this is header
******INFO:ddd******
******INFO:eee******
******WARNING:fff******
this is footer
实例3:
//思路: 在不同类中,往同一个logger里写。即全局日志的概念。
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
class TestLoggerbase {
public static void main(String[] args) {
Logger log = Logger.getLogger("MyLog");
// log.setLevel(Level.CONFIG);
Logger log1 = Logger.getLogger("MyLog");
Logger log2 = Logger.getLogger("MyLog2");
System.out.println("判断logger:");
System.out.println(log == log1); // true, 因为都往同一个logger里写 MyLog
System.out.println(log == log2); // false
// log.setLevel(Level.WARNING);
// log.setLevel(Level.SEVERE);
// log.setLevel(Level.INFO);
// log.setLevel(Level.CONFIG);
// log.setLevel(Level.SEVERE);
log.setLevel(Level.ALL);
log.severe("S info");
// System.out.println("十月 30, 2016 1:44:32 下午 com.my.logger.TestLogger2
// main"+"严重: S");
log.warning("W");
// log.setLevel(Level.INFO);
log.info("inf0");
log.info("inf02");
log.info("inf03");
log.config("Config"); //设置无效. 因为C:\Program Files\Java\jdk1.8.0_151\jre\lib\logging.properties 是java logger的配置文件 ,配置文件设置了一些日志默认的内容,限制了一些行为。 .level= INFO, java.util.logging.ConsoleHandler.level = INFO ... 通过修改配置文件可以实现修改
log.fine("fine");
}
}
2.6.4 容器
2.6.4.1 定义
Java中的容器提供了完善的方法来保存对象,可以使用这些工具来解决大数据量的问题。
解释:
(1)数据容器主要分为两类:
Collection:存放独立元素的序列
Map:存放key-value型的元素对
(2)最常用的四个容器:
LinkedList:
其数据结构采用的是链表,优势是删除和添加的效率很高,但随机访问元素的效率较ArrayList低。
ArrayList:
其数据结构采用的是线性表,优势是访问和查询十分方便,但添加和删除的效率低。
HashSet:
Set类不允许其中存在重复的元素(集),无法添加一个重复的元素。HashSet利用Hash函数进行查询效率上的优化,其contain()方法经常被使用,用于判断相关元素是否被添加过。
HashMap:
提供了key-value的键值对数据存储机制,可以十分方便的通过键值查找相应的元素,而且通过Hash散列机制,查找十分方便。
2.6.4.2 ArrayList
(1)定义:
ArrayList就是动态数组,用MSDN中的说法就是Array的复杂版本,它提供了这些好处:
动态的增加和减少元素。
实现了ICollection和IList接口。
灵活的设置数组的大小。
(2)动态数组扩容:
ArrayList底层采用Object类型的数组实现,当使用不带参数的构造方法生成ArrayList对象时,会在底层生成一个长度为10的Object类型数组。
200个数据动态加到上面默认长度为10的数组,会进行5次扩容:10*2*2*2*2*2=320才会满足。
如果一开始就定义 ArrayList list= new ArrayList(210)创建ArrayList,则不用扩容和Copy,减少内存使用。
(3)常用方法:
声明:ArrayList list = new ArrayList();
给数组增加5个元素:
for(int i=0; i<5; i++)
list.add(i);ArrayList转换为String:
System.out.print(list.toString());
删除第3个值:
list.remove(2)
在第三个位置插入2:
list.add(2,2)
再增加10个元素:
for(int i=0; i<10; i++)
list.add(i+10);
ArrayList转换为Array:
Object[] obj = list.toArray();
for(Objuect i: obj){
System.out.print(i);
}
简单代码实例:
public class MyDemo {
public static void main(String[] args) {
//如果ArrayList 不限定存储类型,那么存储的是Object类型
ArrayList arrayList = new ArrayList();
arrayList.add("hi");
arrayList.add( 12334);
arrayList.add(88.99);
//限定ArrayList的存储类型
ArrayList<Integer> arrayList1 = new ArrayList<Integer>();
for(int i=0; i<5; i++){
arrayList1.add(i);
}
//ArrayList的一些常用方法
System.out.println(arrayList1.toString());
arrayList1.remove(1);
System.out.println(arrayList1.toString());
arrayList1.add(3,33);
System.out.println(arrayList1.toString());
for(int j=0; j<10; j++){
arrayList1.add(10+j);
}
System.out.println(arrayList1.toString());
Object[] objects = arrayList1.toArray();
for(Object o: objects){ //遍历
System.out.print(o + ", ");
}
System.out.println();
Iterator it = arrayList1.iterator(); //迭代器
while(it.hasNext()){
System.out.print(it.next() + ", ");
}
}
}
输出结果:
[0, 1, 2, 3, 4]
[0, 2, 3, 4]
[0, 2, 3, 33, 4]
[0, 2, 3, 33, 4, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
0, 2, 3, 33, 4, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
0, 2, 3, 33, 4, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
2.6.4.3 HashMap
(1)定义:
Map中通过对象来对对象进行索引,用来索引的对象叫做key, 其对应的对象叫做value。
HashMap是一个散列表,它存储的内容是键值对key-value映射。
(2)常用方法:
//HashMap存储
HashMap hm=new HshMap();
hm.put("a","test1");
hm.put("b", "test2");
hm.put("c", "test3");
//测试是否包含关键字"a"
System.out.print(hm.containsKey("a"));//测试是否包含关键字“b”
System.out.print(hm.containsKey("b"));
// 取得关键字“a”的value
System.out.print(hm.get("a"));
System.out.print(hm.entrySet());
//HashMap遍历
Iterator it=hm.entrySet().iterator();
while(it.hasNext()){
System.out.println(it.next());
}
//keySet()返回关键字的集合
Iterator it=hm.keySet().iterator();
while(it.hasNext()){
System.out.print(it.next());
}
//values()返回值的集合
Iterator it=hm.values().iterator();
while(it.hasNext()){
System.out.print(it.next());
}
// 删除key c 以及其对应的value
hm.remove("c")
//上面删除key c后进行hashmap遍历
Iterator it=hm.entrySet().iterator();
while(it.hasNext()){
System.out.print(it.next());
}
简单代码实例:
public class MyDemo {
public static void main(String[] args) {
HashMap<String, String> hm = new HashMap<String, String>();
hm.put("k1","v1");
hm.put("k2", "v2");
hm.put("k3", "v3");
System.out.println(hm.containsKey("k2"));
System.out.println(hm.containsKey("k5"));
System.out.println(hm.get("k1"));
System.out.println("**************************");
System.out.println(hm.entrySet()); //键值对的集合。所有键值对在一起,以数组的形式输出
System.out.println("**************************");
Iterator it = hm.entrySet().iterator(); //键值对的集合
while (it.hasNext()){
System.out.println(it.next());
}
System.out.println("**************************");
it = hm.keySet().iterator(); //关键字的集合
while(it.hasNext()){
System.out.println(it.next());
}
System.out.println("**************************");
it = hm.values().iterator(); //值的集合
while(it.hasNext()){
System.out.println(it.next());
}
System.out.println("**************************");
System.out.println("删除操作");
hm.remove("k2");
it = hm.entrySet().iterator();
while (it.hasNext()){
System.out.println(it.next());
}
}
}
输出结果:
true
false
v1
**************************
[k1=v1, k2=v2, k3=v3]
**************************
k1=v1
k2=v2
k3=v3
**************************
k1
k2
k3
**************************
v1
v2
v3
**************************
删除操作
k1=v1
k3=v3
延伸:Map的使用,Map的键值也可以是Map
简单代码实例:
public class MyMap {
public static void main(String[] args) {
Map<String, String> map1 = new HashMap<String, String>();
map1.put("k1", "v1");
map1.put("k2", "v2");
map1.put("k3", "v3");
Map<String, Map<String, String>> map2 = new HashMap<String, Map<String, String>>();
map2.put("1", map1);
map2.put("2", map1);
System.out.println(map2.entrySet()); //所有键值对在一起,以数组的形式输出
System.out.println("*******************************");
System.out.println(map2.get("1").get("k1"));
System.out.println(map2.get("2").get("k1"));
System.out.println(map2.get("1").get("k2"));
System.out.println(map2.get("2").get("k2"));
System.out.println(map2.get("1").get("k3"));
System.out.println(map2.get("2").get("k3"));
}
}
输出结果:
[1={k1=v1, k2=v2, k3=v3}, 2={k1=v1, k2=v2, k3=v3}]
*******************************
v1
v1
v2
v2
v3
v3
2.6.5 多线程
2.6.5.1 定义
(1)简言之,一个程序至少有一个进程,一个进程至少有一个线程。
线程的划分尺度小于进程,多线程程序的并发性高。
进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而提高了程序的运行效率。
(2)线程的一定条件下,状态会发生变化。线程的五个状态:
新建状态 New:
新创建一个线程对象
就绪状态 Runnable:
线程对象创建后,其他线程调用了该对象的start方法。该状态的线程位于可运行线程池中,变得可运行,但需要等待获取CPU的使用权之后,才正式运行。
运行状态 Running:
就绪状态的线程获取了CPU,执行程序代码。
阻塞状态 Blocked:
是线程因为某种原因放弃CPU使用权,暂时停止运行,直到线程进入就绪状态,才有机会转到运行状态。
---等待阻塞:运行的线程执行wati()方法,JVM会把该线程放入等待池中。
---同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
---其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程设置为阻塞状态。当sleep()状态超时,join()等待线程终止或超时,或者I/O处理完毕,线程重新转入就绪状态。
死亡状态 Dead:
线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
2.6.5.2 多线程实现
(1)Java要实现多线程,有两种手段:
继承Thread类
实现Runable接口
(2)详解:
1)直接继承Thread类
缺点:Java不支持多继承,不利于资源共享。
代码框架:
class MyThread1 extends Thread{
public void run(){
try{
Thread.sleep(200);
}catch(InterrupteException e){
e.printStackTrace();
}
for(int i=0; i<7; i++){
if(count>0){
System.out.print(Thread.currentThread().getName()+": count"+count--);
}
}
}
public static void main(String[] args){
MyThread1 h1 = new MyThread1();
MyThread1 h2 = new MyThread1();
MyThread1 h3 = new MyThread1();
h1.start();
h2.start();
h3.start();
}
private int count=5; //全局变量
}
2)实现Runable接口
代码框架:
class MyThread2 implements Runable {
private int count =5; //全局变量
public void run(){
try{
Thread.sleep(200);
}catch(InterrupteException e){
e.printStackTrace();
}
for(int i=0; i<7; i++){
if(count>0){
System.out.print(Thread.currentThread().getName()+": count"+count--);
}
}
}
public static void main(String[] args){
MyThread2 my = new MyThread2();
new Thread(my,"Thread 1").start();
new Thread(my,"Thread 2").start();
new Thread(my,"Thread 3").start();
}
}
2.6.5.3 线程同步
(1)定义
多线程的同步机制对资源进行加锁,使得在同一个时间,只有一个线程可以进行操作。
同步用于解决多个线程同时访问可能出现的问题。
同步机制可以使用synchronized关键字实现。
(2)syschronized方法
当synchronized关键字修饰一个方法时,该方法叫做同步方法。
当synchronized方法执行完或者发生异常时,会自动释放锁。
(3)锁lock
Java中的每个对象都有一个锁lock, 或者叫做监视器monitor。
当一个线程访问某个对象的synchronized方法时,将该对象上锁(是将对象上锁,不是对其中的某个方法上锁而已),其他任何线程都无法再去访问该对象的synchronized方法了 (这里指所有的同步方法,而不仅仅是同一个方法)。
直到之前的那个线程执行方法完毕后(或者抛出异常),才将该对象的锁释放掉。
其他线程才有可能再去访问该对象的synchronized方法。
注意:上面是给对象上锁,如果是不同的对象,那么各个对象之间没有限制关系。
3)synchronized块
写法:
synchronized(object){
}
含义:
表示线程在执行时,会将object对象上锁。
注意这个对象可以是任意类的对象,也可以使用this关键字。
这样就可以自行规定上锁对象。