目录
2. Java进阶
2.6 Java常用类
2.6.1 IO
2.6.1.1 定义
2.6.1.2 IO的分类
2.6.2 File类
2.6.2.1 定义
2.6.2.2 File类的构造方法
2.6.2.3 文件属性
2.6.2.4 目录
2.6.2.5 列出指定类型的文件
2.6.3 Java Logger
2.6.3.1 创建Logger对象
2.6.3.2 Logger的级别
2.6.3.3 Logger的Handler
2.6.3.4 Logger的Formatter
2.6.4 容器
2.6.4.1 定义
2.6.4.2 ArrayList
2.6.4.3 HashMap
2.6.5 多线程
2.6.5.1 定义
2.6.5.2 多线程实现
2.6.5.3 线程同步
【写在前面】
前文链接:Java入门(三):进阶
Java核心库 java.io提供了全面的IO接口:文件读写,标准设备输出等。
Java 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));
File类的对象,主要用来获取文件本身的一些信息,比如文件所在目录,文件长度,文件读写权限等,不涉及对文件的读写操作。
File(String filename)
File(String directoryPath, String filename)
File(file f, String filename)
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年五月至文件最后修改时刻的毫秒数)
创建目录: public boolean mkdir()
如果File对象是一个目录,那么该对象可以调用下述方法列出该目录下的文件和子目录
public String[] list():用字符串形式返回
public File[] listFiles:用File对象形式返回
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方法指定的目录和文件名字要求。
static Logger getLogger(String name):为指定子系统查找或创建一个logger
static Logger getLogger(String name, String resourceBundleName): 为指定子系统查找或创建一个logger
注意:name是Logger的名称,当名称相同时,同一个名称的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");
}
}
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
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");
}
}
Java中的容器提供了完善的方法来保存对象,可以使用这些工具来解决大数据量的问题。
(1)数据容器主要分为两类:
Collection:存放独立元素的序列
Map:存放key-value型的元素对
(2)最常用的四个容器:
LinkedList:
其数据结构采用的是链表,优势是删除和添加的效率很高,但随机访问元素的效率较ArrayList低。
ArrayList:
其数据结构采用的是线性表,优势是访问和查询十分方便,但添加和删除的效率低。
HashSet:
Set类不允许其中存在重复的元素(集),无法添加一个重复的元素。HashSet利用Hash函数进行查询效率上的优化,其contain()方法经常被使用,用于判断相关元素是否被添加过。
HashMap:
提供了key-value的键值对数据存储机制,可以十分方便的通过键值查找相应的元素,而且通过Hash散列机制,查找十分方便。
(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 arrayList1 = new ArrayList();
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,
(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 hm = new HashMap();
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 map1 = new HashMap();
map1.put("k1", "v1");
map1.put("k2", "v2");
map1.put("k3", "v3");
Map> map2 = new HashMap>();
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
(1)简言之,一个程序至少有一个进程,一个进程至少有一个线程。
线程的划分尺度小于进程,多线程程序的并发性高。
进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而提高了程序的运行效率。
(2)线程的一定条件下,状态会发生变化。线程的五个状态:
新建状态 New:
新创建一个线程对象
就绪状态 Runnable:
线程对象创建后,其他线程调用了该对象的start方法。该状态的线程位于可运行线程池中,变得可运行,但需要等待获取CPU的使用权之后,才正式运行。
运行状态 Running:
就绪状态的线程获取了CPU,执行程序代码。
阻塞状态 Blocked:
是线程因为某种原因放弃CPU使用权,暂时停止运行,直到线程进入就绪状态,才有机会转到运行状态。
---等待阻塞:运行的线程执行wati()方法,JVM会把该线程放入等待池中。
---同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
---其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程设置为阻塞状态。当sleep()状态超时,join()等待线程终止或超时,或者I/O处理完毕,线程重新转入就绪状态。
死亡状态 Dead:
线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
(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();
}
}
(1)定义
多线程的同步机制对资源进行加锁,使得在同一个时间,只有一个线程可以进行操作。
同步用于解决多个线程同时访问可能出现的问题。
同步机制可以使用synchronized关键字实现。
(2)syschronized方法
当synchronized关键字修饰一个方法时,该方法叫做同步方法。
当synchronized方法执行完或者发生异常时,会自动释放锁。
(3)锁lock
Java中的每个对象都有一个锁lock, 或者叫做监视器monitor。
当一个线程访问某个对象的synchronized方法时,将该对象上锁(是将对象上锁,不是对其中的某个方法上锁而已),其他任何线程都无法再去访问该对象的synchronized方法了 (这里指所有的同步方法,而不仅仅是同一个方法)。
直到之前的那个线程执行方法完毕后(或者抛出异常),才将该对象的锁释放掉。
其他线程才有可能再去访问该对象的synchronized方法。
注意:上面是给对象上锁,如果是不同的对象,那么各个对象之间没有限制关系。
3)synchronized块
写法:
synchronized(object){
}
含义:
表示线程在执行时,会将object对象上锁。
注意这个对象可以是任意类的对象,也可以使用this关键字。
这样就可以自行规定上锁对象。