字符集:
字符集最初是美国人存储他们字符的方法,即Ascll
标准Ascll采用一个字节来存储一个字符,并且以0开头共可表示0-127共128个字符
其他国家使用计算机时也要有自己国家的字符,中国创建的比较知名的有GBK,称为汉字内码扩展规范,也有人叫国标
GBK兼容了Ascll字符集它们依然占用一个字节
包含了两万多个中文字符,因为要表示的字符更加多就使用了两个字节,以1开头
但如果每个国家都创建自己的字符集,那么世界信息传递就会变得困难,出问题
这时国际组织推出了Unicode这个世界通用的字符集叫做统一码也叫万国码
开始时使用UTF-32这个编码方案,将所有字符均按照四个字节表示
但是由于太过奢侈,四个字节会过多占用存储空间,降低通讯效率
所以后来推出了UTF-8这个编码方案
UTF-8采用可变长编码方案,分为四个长度区
英文字符和数字依然只占用一个字节
汉字占用三个字节
编码时使用的字符集和解码时使用的字符集必须一致,否则容易出现乱码
字母和数字一般不会乱码,因为很多字符集都包含了Ascll码
IO流:
IO流中的I指的是input,输入流,O指的是output,输出流
输入流负责把数据从磁盘/网络读到内存中去,输出流负责写数据出去
Java为了应对不同场景下读写IO流的需求写了很多的io流
IO流按照方向分为输入流和输出流
按照数据中最小的单位分为字节流和字符流
所以总体上IO流分为四大类,字节输入流,字节输出流,字符输入流,字符输出流
字节xx流是以一个个字节的形式读取或写出
字符xx流是以一个个字符的形式读取或写出
字节输入流 InputStream--->FileInputStream
字节输出流 OutputStream--->FileOutputStream
字符输入流 Reader--->FileReader
字符输出流 Writer--->FileWriter
以上四者都是抽象类,箭头后面的是他们各自的实现类
FileInputStream文件输入流:
创建文字字节输入管道与源文件接触
FileInputStream is = new FileInputStream(new File("D:\\code\\project\\IO\\iO_1\\src\\IO"));
以上写法可以简化
FileInputStream is = new FileInputStream(("D:\\code\\project\\IO\\iO_1\\src\\IO"));
read()
每次读取一个字节返回,若没有数据了返回-1(读取一个数据后自动指向下一个)
int b1=is.read();
System.out.println((char) b1);
int b2=is.read();
System.out.println((char) b2);
int b3=is.read();
System.out.println((char) b3);
int b4=is.read();
System.out.println((char) b4);
int b5=is.read();
System.out.println(b5);
注意!
读取数据性能很差,每次都要去硬盘读取
读取汉字等多字节字符时会乱码
每次使用流后必须关闭,释放系统性能
close()
关闭流的方法
一次性读取n个字节存入到数组中
byte[] buffer=new byte[3];
int len=fis.read(buffer);
String s = new String(buffer);
System.out.println(s);
System.out.println("当次读取的字节数量"+len);
读取多少,倒出多少
当剩余的数据已经不满足数组的容量是若仍然输出
会在输出完全部后输出null,所以这里采用输出部分数组的方法
int len2=fis.read(buffer);
String s1 = new String(buffer,0,len2);//包含了重写方法
System.out.println(s1);
System.out.println("当次读取的字节数量"+len2);
上式中,len2代表剩余的数据字节数
当数据量过多,可能会有些繁琐,可以采用循环
String s1;
int length;
while ((length=fis.read(buffer))!=-1){
s1=new String(buffer,0,length);
System.out.print(s1);
}
最后不能忘记close;
注意!
以上不可避免的出现汉字输出乱码的情况
如上式,一个汉字占有三个字节,上式中一个数组有三个字节,但数字或字母只有1个字节
若文件是:“kk我”那么我字的三个字节就会被拆散,从而输出乱码
避免字节输入汉字乱码:
使用文件字节输入流避免汉字出现乱码的方法:创建一个和文件一样大的数组,一次性读取全部字节。
File file=new File("D:\\code\\project\\IO\\iO_1\\src\\PBX01.txt");
FileInputStream fis=new FileInputStream("D:\\code\\project\\IO\\iO_1\\src\\PBX01.txt");
byte[] b=new byte[(int) file.length()];
int len = fis.read(b);
String s = new String(b, 0, len);
System.out.println(s);
System.out.println("提取的字节数为:"+len);
这样一次性读取全部字节就可以避免拆散汉字字节
java官方在JDK1.9也提供了一种方法去完成上述操作
fis.readAllBytes()
FileOutputStream文件输出流:
创建字符输出流管道
下面一个是覆盖管道,会把文件原本的内容覆盖掉
FileOutputStream fos=new FileOutputStream("D:\\code\\project\\IO\\iO_1\\src\\PBX02.txt");
下面是一个追加管道
FileOutputStream fos=new FileOutputStream("D:\\code\\project\\IO\\iO_1\\src\\PBX02.txt",true);
在路径后面加一个参数true就不会覆盖之前的内容
write(int b)
写一个字节出去
fos.write(97);
会在D:\\code\\project\\IO\\iO_1\\src\\PBX02.txt文件里输出a
fos.write('b');
会在D:\\code\\project\\IO\\iO_1\\src\\PBX02.txt文件里输出b
fos.write('我');
会输出乱码,因为这个一次只能输出一个字节,会把汉字的三个字节强行拆散取第一个字节
write(byte[] buffer)
写一个字节数组出去
byte[] b="nbnb你是真牛逼".getBytes();
fos.write(b);//添加一个字符数组
write(byte[] buffer,int pos,int len)
写一个字节数组的一部分出去
byte[] b="nbnb你是真牛逼".getBytes();
fos.write(b,0,10);
//添加一个字符串数组,从第0个字节到第10个字节
换行
fos.write("\r\n".getBytes());
在windows系统中,\n便代表着换行可是\r\n在除了Windows系统外的系统也都能用,兼容性更好
注意!
字符输出流写出数据后必须刷新流或者关闭流,写出的数据才能生效
close();关闭流
flush();刷新流
可以用try--catch包住这个方法在try后面写上小括号在小括号里面去写资源,这样就会自动去关闭流(关闭流自带刷新流)
这些资源必须实现AutoCloseable或Closeable接口,实现其中的close()方法。Closeable是AutoCloseable的子接口。
try (
InputStream is = new FileInputStream("D:\\code\\project\\IO\\iO_1\\src\\PBX04.txt");
InputStream bis = new BufferedInputStream(is);
OutputStream os = new FileOutputStream("D:\\code\\project\\IO\\iO_1\\src\\PBX05.txt");
OutputStream bos = new BufferedOutputStream(os,10);
){
//字节缓冲输入流与输出流都自带一个8KB的缓冲池,而在is或者os后面输入一个‘,’再输入数字就可以更改字节缓冲输入流与输出流的缓冲池大小
byte[] buffer=new byte[1024];
int len;
while ((len=bis.read(buffer))!=-1){
bos.write(buffer,0,len);
}
System.out.println("复制完成");
} catch (IOException e) {
throw new RuntimeException(e);
}
缓冲流:
被称作包装流或者是处理流
用来对原始数据进行包装,以提高处理数据的性能
是高级流,一般而言要比原始流高效,因为它自带8KB的缓冲池,相当于一次读取\输出8KB的内容
但是采用存入到数组中进行读取的原始流不一定比缓冲流慢,只要数组够大,还有可能比缓冲流要快
缓冲字节输入流:
InputStream-->FileInputStream-->BufferedInpuoStream
字节缓冲输入流
缓冲字节输出流:
OutputStream-->FileOutputStream-->BufferedOutpuoStream
字节缓冲输出流
字节缓冲输入流与输出流都自带一个8KB的缓冲池,而在is或者os后面输入一个‘,’再输入数字就可以更改字节缓冲输入流与输出流的缓冲池大小
InputStream bis = new BufferedInputStream(is);
OutputStream bos = new BufferedOutputStream(os,10);
try (
InputStream is = new FileInputStream("D:\\code\\project\\IO\\iO_1\\src\\PBX04.txt");
InputStream bis = new BufferedInputStream(is);
OutputStream os = new FileOutputStream("D:\\code\\project\\IO\\iO_1\\src\\PBX05.txt");
OutputStream bos = new BufferedOutputStream(os,10);
){
//字节缓冲输入流与输出流都自带一个8KB的缓冲池,而在is或者os后面输入一个‘,’再输入数字就可以更改字节缓冲输入流与输出流的缓冲池大小
byte[] buffer=new byte[1024];
int len;
while ((len=bis.read(buffer))!=-1){
bos.write(buffer,0,len);
}
System.out.println("复制完成");
} catch (IOException e) {
throw new RuntimeException(e);
}
缓冲字符输入流:
缓冲字符输入流可以像之前一样包装一个普通的字符输入流
Reader rd = new FileReader("D:\\code\\project\\IO\\iO_1\\src\\PBX05.txt");
BufferedReader br = new BufferedReader(rd);
它也新增了一个功能:按行读取字符:readline();
读取一行数据返回,如果没有数据可读了,会返回null
String line;
while ((line=br.readLine())!=null){
System.out.println(line);
}
缓冲字符输出流:
Writer wt = new FileWriter("D:\\code\\project\\IO\\iO_1\\src\\PBX05.txt", true);
BufferedWriter bw = new BufferedWriter(wt);
与上面相同,它也新增了一个功能:换行:newline();
打印流:
打印流能够实现打印什么就写什么出去,例如之前打印97可能写出了a而打印流会写出97
PrintStream ps=new PrintStream("D:\\code\\project\\IO\\iO_1\\src\\PBX07.txt","GBK");
PrintStream ps=new PrintStream("D:\\code\\project\\IO\\iO_1\\src\\PBX07.txt");
PrintWriter pw=new PrintWriter("D:\\code\\project\\IO\\iO_1\\src\\PBX07.txt");
正常情况下上方代码块的三种打印流 几乎没有区别,可以随意选用
PrintWriter pw=new PrintWriter(new FileOutputStream("D:\\code\\project\\IO\\iO_1\\src\\PBX07.txt",true));
还可以直接在打印流中new出原始流,实现将覆盖管道改变为追加管道
PrintStream:
PrintWriter:
PrintStream和PrintWriter的区别 打印数据的功能上是一模一样的:都是使用方便,性能高效(核心优势)
PrintStream继承自字节输出流OutputStream,因此支持写字节数据的方法。
PrintWriter继承自字符输出流Writer,因此支持写字符数据出去。
输出语句的重定向:
把打印流打印的位置改到某个文件里面去
System.out.println("神龟虽寿");
System.out.println("犹有竟时");
System.out.println("腾蛇乘雾");
System.out.println("终为土灰");
以上会把四句话全部输出到控制台窗口中去
System.out.println("神龟虽寿");
System.out.println("犹有竟时");
//只能用Stream否则报错
try (PrintStream ps=new PrintStream("D:\\code\\project\\IO\\iO_1\\src\\PBX08.txt");){
System.setOut(ps);
System.out.println("腾蛇乘雾");
System.out.println("终为土灰");
} catch (Exception e) {
throw new Exception(e);
}
PrintStream ps=new PrintStream("D:\\code\\project\\IO\\iO_1\\src\\PBX08.txt");
System.setOut(ps);
这两步操作便可以将输出目标改为文件地址为D:\\code\\project\\IO\\iO_1\\src\\PBX08.txt的文件中去最终在控制台中输出前两句,在文件输出后两句
转换流:
字符输入转换流:
try (
//InputStream is=new FileInputStream("D:\\code\\project\\IO\\iO_1\\src\\PBX06.txt");
Reader rd=new InputStreamReader(new FileInputStream("D:\\code\\project\\IO\\iO_1\\src\\PBX06.txt"),"GBK");//把原始的字节输入流按照字符集编码转换为对应的字符输入流
BufferedReader br=new BufferedReader(rd);
){
String line;
while ((line=br.readLine())!=null){
System.out.println(line);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
Reader rd=new InputStreamReader(new FileInputStream("D:\\code\\project\\IO\\iO_1\\src\\PBX06.txt"),"GBK"); 其中GBK指的是所读取的文件是GBK形式的,把GBK读出为UTF-8
字符输出转换流:
控制写出去的字符使用某个字符集有两种方法
第一种是使用String类型的getBytes方法
String data=" ";
byte[] bytes=data.getBytes("GBK");
这样就可以把data中的数据转换为GBK类型写出
第二种便是使用字符输出转换流
try (
OutputStream os=new FileOutputStream("D:\\code\\project\\IO\\iO_1\\src\\PBX07.txt");
Writer wt=new OutputStreamWriter(os,"GBK");
BufferedWriter bw=new BufferedWriter(wt);
){
bw.newLine();
bw.write("老哥牛逼");
} catch (IOException e) {
throw new RuntimeException(e);
}
使用write方法把输入的字符串转化为指定的字符集写出
数据流:
允许把数据及其类型一并写出去
try (
InputStream is = new FileInputStream("D:\\code\\project\\IO\\iO_1\\src\\PBX04.txt");
InputStream bis = new BufferedInputStream(is);
OutputStream os = new FileOutputStream("D:\\code\\project\\IO\\iO_1\\src\\PBX05.txt");
OutputStream bos = new BufferedOutputStream(os,10);
){
//字节缓冲输入流与输出流都自带一个8KB的缓冲池,而在is或者os后面输入一个‘,’再输入数字就可以更改字节缓冲输入流与输出流的缓冲池大小
byte[] buffer=new byte[1024];
int len;
while ((len=bis.read(buffer))!=-1){
bos.write(buffer,0,len);
}
System.out.println("复制完成");
} catch (IOException e) {
throw new RuntimeException(e);
}
数据输入流:
DateInputStream
上面输出的顺序是什么读出就要是什么顺序
byte b = dis.readByte(); int i = dis.readInt(); double v = dis.readDouble(); String s = dis.readUTF();
数据输出流:
DateOutputStream
会在指定文件输出类似乱码的数据,但是那些可以被数据输入流读取
dos.writeByte(97);//将字节类型的数据写入基本的字节输出流
dos.writeInt(111);//将整数类型的数据写入基本的字节输出流
dos.writeDouble(111.1);//将double类型的数据写入基本的字节输出流
dos.writeUTF("龟虽寿");//将字符串数据以UTF-8编码成字节再写入基本的字节输出流
序列化流:
序列化:
ObjectOutputStream
可以把java对象序列化把java对象存到文件中去
try (
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("D:\\code\\project\\IO\\iO_1\\src\\PBX10.txt"));
){
User u=new User("老阴逼","张三",25,"666888xyz");
oos.writeObject(u);
System.out.println("序列化成功");
} catch (Exception e) {
throw new RuntimeException(e);
}
可以在用户类中在不想进行序列化的数据前面加上transient这样这个数据就不会参与序列化
想要一次性序列化多个对象可以用一个ArrayList集合包含这些对象,注意:!ArrayList集合已经实现了序列化接口,可以直接参与序列化
public class User implements Serializable {
private String lodingName;
private String UserName;
private int age;
private transient String password;
要序列化的对象类必须实现Serializable(没有特殊作用,只是一个标志)这样才能序列化
反序列化:
try (
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("D:\\code\\project\\IO\\iO_1\\src\\PBX10.txt"));
){
Object o = ois.readObject();
System.out.println(o);
} catch (Exception e) {
throw new RuntimeException(e);
}
使用readObject方法
扩展知识:框架:
框架是编写的类,接口等可以理解为一个半成品,大多是第三方研发的
好处:在框架上开发可以得到优秀的软件架构提高开发效率
框架的形式:一般都是把类接口等编译为class形式在压缩为一个.jar文件发行出去
特殊文件:
属性文件:
属性文件
每一行都是键值对
键不能重复
一般都是以.Properties结尾(并不强制)
//Properties是一个MAP集合但一般不当作集合来用
//Properties的对象代表一个属性文件
//那么就可以通过Properties的对象读取一个属性文件
方法:
//load(InputStream is)通过字节输入流读取键值对数据
//load(Reader reader)通过字符输入流读取键值对数据
//String getProperty(String Key)根据键获取值 其实就是get()
//String getPropertyNames()获取全部键 其实就是keySet方法
store() 将Properties对象中的键值对数据存入到属性文件中去
setProperty() 将一些键值对数据存入到Properties对象中
掌握吧键值对数据存入到属性文件中去
因为属性文件中键不能重复,所以也遵循着后加覆盖前加的原则所以当要改变一些数据时可以直接用setProperty()和store()添加
注意!
属性文件的字符集是ISO-8859-1
XML文件:
全称为:可扩展标记语言
本质上是一种特殊的数据格式,用来存储复杂的数据结构和数据关系
<?xml version="1.0" encoding="UTF-8" ?> <!--以上抬头声明必须放在第一行且必须存在,格式基本固定 --> <Users> <!-- 两个Users是根标签,只能有这两个且分别放在第二行和最后--> <!--XML中出现<、&等字符时可能会因为冲突导致报错此时可以用如下特殊字符替代--> <!--< < 小于--> <!--> > 大于--> <!--& & 和号--> <!--' ‘ 单引号--> <!--" ” 引号--> <Data1><![CDATA[ 特殊数据区,这里面可以写任何内容 包括:3<2&&5>4 ]]></Data1> </Users>
可以通过写出特定的格式把数据写到XML文件中去
StringBuffer sb=new StringBuffer();
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\r\n");
sb.append("\t<Person>\r\n");
sb.append("\t\t<Nmae>").append("帐务局").append("</Nmae>\r\n");
sb.append("\t</Person>\r\n");
try (
BufferedWriter bw=new BufferedWriter(new FileWriter("D:\\code\\project\\IO\\Special_File\\src\\Person.XML"));
){
bw.write(sb.toString());
} catch (IOException e) {
throw new RuntimeException(e);
}
XML文件的解析:
使用第三方公司创建的框架:DOM4J框架,它首先会通过DOM4J框架提供的SAXReader解析器来把要解析的XML文件加载到内存中去,同时将其加载为一个文档对象,接着可以用这个文档对象解析获取XML文件中的所有数据
xml文件的约束:
比如一个XML文件的标签内容本来是中文,一直以来都是按照中文去解析结果有个程序员将标签改为了英文,那么再去解析的话就会解析失败所以要约束XML文件
这里需要用到:约束文档
分为DTD约束文档和Schema约束文档
1.编写DTD约束文档:后缀必须是.DTD
2.在需要编写的XML文件中导入DTD约束文档
3.然后XML文件就必须用DTD规定的格式编写标签,否则报错
日志技术:
可以将系统执行的信息方便的纪录到指定的位置(控制台中,文件中,数据库中)
可以随时以开关形式开启或关闭日志,无需侵入到源代码进行修改
使用Logback的实现步骤
倒入Logback框架到项目中去
slf4j-api日志接口 Logback-core Logback-classic
将核心配置文件Logback.xml直接导入到src目录下去(必须是src)
创建Logback提供的Logger对象,再用Logger对象提供的方法记录日志信息
public static final Logger LOGGER=LoggerFactory.getLogger("Study1");
核心配置文件Logback.xml的作用:
对Logback框架进行控制
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
以上两个是控制输出位置的数据第一个是控制台,第二个指向文件
即输出到这个地址:
<file>D:/log/itheima-data.log</file>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] %c [%thread] : %msg%n</pattern>
规定了输出格式 [%thread]指日志的级别 %c指输出该日志的文件
<charset>utf-8</charset>
规定了输出字符集,防止出现乱码
<!-- 1、控制日志的输出情况:如,开启日志,取消日志"ALL"/"OFF" 里面是日志级别就意思是只有该日志级别及以上的才会被输出 --> <root level="debug"> <appender-ref ref="CONSOLE"/> <appender-ref ref="FILE" /> </root>
<!--指定日志文件拆分和压缩规则--> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <!--通过指定压缩文件名称,来确定分割文件方式--> <fileNamePattern>D:/log/itheima-data-%i-%d{yyyy-MM-dd}-.log.gz</fileNamePattern> <!--文件拆分大小--> <maxFileSize>1MB</maxFileSize> </rollingPolicy>
每有1MB大小的日志填充文件,就会重新生成文件并将原文件形成压缩包
用来防治文件过大导致后期无法打开
日志级别:
多线程:
创建线程方法1继承Thread类
优点:编码简单
缺点:已经继承了Thread类,无法继承其他类,不利于功能的拓展
步骤:
1.创建一个类(暂命名为MyThread),使之继承Thread方法
2.重写run方法
3.跑回main方法中创建MyThread类的对象
4.用该对象调用start方法
注意:
1.启动线程必须是调用start方法而不是调用run方法
如果调用run方法那么系统中只会有一条主线程,系统会把run当作一个对象去正常调用方法
2.不要把主线程任务放在启动主线程之前
把主线程任务放在启动主线程之前的话系统会先执行完主线程的任务才发现并执行副线程
线程自带的方法:
1.getName==获取当前线程的名字(线程名字默认为Thread+XX) 由Thread的对象调用
2.setName==>给线程起名字 由Thread的对象调用
3.currentThread()获取调用它的线程的对象 由Thread直接调用
4.sleep(XX) 由Thread直接调用 会让当前执行的线程暂停xx毫秒
5.join 由Thread的对象调用 使当前执行的线程先执行完
Thread m = Thread.currentThread();
System.out.println(m.getName());//main
m.setName("主线程");
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 5; i++) {
System.out.println(i);
if (i==3){
Thread m=Thread.currentThread();
Thread.sleep(5000);
}
}
Thread t1=new MyThread();
t1.start();
t1.join();
Thread t2=new MyThread();
t2.start();
t2.join();
对于线程的命名还可以调用有参构造器来进行
public MyThread(String name){
//setName(name);
super(name);
}
上方代码块注释的和未注释的都可以适配需求,但建议使用未注释的
MyThread t=new MyThread("第一线程");
创建线程方法2实现Runnable接口
优点:它只是实现了接口,没有继承类,可以继承其他类,拓展性更强
缺点:需要多创建一个任务对象
步骤:
1.创建一个类(暂命名为MyRunnable),使之实现Runnable接口
2.重写run方法
3.跑回main方法中创建MyRunnable类的对象
4.用Thread对象封装MyRunnable类的对象
5.用Thread对象对象调用start方法
注意!
如果使用MyRunnable类的对象直接调用start方法会报错,因为start只有线程对象可以调用而MyRunnable类的对象是任务对象
实现Runnable接口也可以通过创建内部类来完成多线程
匿名内部类写法
1.直接创建接口的匿名内部类对象
多个不同的子线程并列写可以同时启动
@Functionalinterface函数式接口,标志着可以运营Lambda
Runnable target=new Runnable() {
@Override
public void run() {
for (int i=1;i<6;i++) {
System.out.println("多线程1启动"+i);
}
}
};
new Thread(target).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i=1;i<6;i++) {
System.out.println("多线程2启动"+i);
}
}
}).start();
创建线程方法3Callable接口和FutureTask类来创建
前面的两种线程创建方法都会存在一些问题,假如线程执行结束后需要返回一些数据,它们执行的run方法均不能直接返回
JDK5.0提供了Callable接口和FutureTask类来实现(第三种线程创建方法)
这种方法的优点:只是实现了一个接口,扩展性强,可以通过FutureTask的get()方法来得到结果数据
缺点:编码较为复杂
步骤:
1.定义一个类实现Callable接口和call方法,封装要做的事情和要返回的数据
把Callable对象封装成FutureTask对象
2.把线程任务交给Thread对象
3.调用Thread对象的start方法启动线程
4.线程执行完毕后调用FutureTask对象的get方法得到结果
Callable对象的作用
Callable对象并不是线程的任务对象Thread不能接收
作用:
1.是线程对象,可以交给Thread对象
2.可以在线程执行完毕后用get方法得到结果
获取线程执行完毕后的结果
String rs = f1.get();
System.out.println(rs);
如果要同时计算两个式子,例如同时计算1-5的和和1-100的和,不需要再建立一个MyCallable类,只需要重新创建一个对象,再复写一份之前的代码就行了
安全问题,线程同步:
多个线程同时操作同一资源时,且发生更改,可能会出现业务安全问题
线程同步方法1//同步代码块//会锁柱一段代码,同一段时间只允许一段线程执行它
甚至以上四个线程,每次只有一个线程能够通过,与是否是同一个对象无关(在使用任意的唯一对象的情况下)所以应该用共享资源作为锁
官方建议使用类名.class作为锁
synchronized ("在这里面放入一个在系统中独一无二的量(例如双引号里的内容在字符常量池中,属于独一无二的量)") {}
线程同步方法2//同步方法:把访问共享资源的同步方法上锁
原理:每次进入一个线程,线程执行结束后自动解锁
区别:同步代码块所的范围更小,同步方法锁的范围更大所以,同步代码块的性能更好,同步方法可读性更好
线程同步方法3//lock锁//程序员自己创建出锁对象再手动运用锁对象进行加锁和解锁
ReentrantLock,Lock接口的实现类,一般都要用它创建多态
lock锁,上锁
unlock解锁
在空白地方创建锁对象(在定义变量那里)
这样做可以保证一个账户对象一把锁
但如果锁中间那段代码出现问题就会跳走,无法解锁,其它线程进不来
可以使用Try-Catch-Finally来解决