1 IO
1.1 IO分类
java.io
1.方向:
输出out
输入in
2.类型
1)字节流——Input/OutputStream(声像)
public abstract class InputStream; extends Object
Stream:
public interface Stream
extends BaseStream<T,Stream>
具体实现:FileInputStream,FileOutputStream
2)字符流——Reader 或者 Writer(纯文本)
public abstract class Reader; extends Object
具体实现:FileReader,FileWriter
3)对象流
3.功能:
节点流——基础流
缓冲流——建立在节点流之上,含Buffer的流
1.1.1 字节流、字符流使用
-
流操作步骤:
1.创建源或者目标对象
2.创建IO流对象
3.具体的IO操作
4.关闭资源 -
InputStream 输入
FileInputStream() 文件字节输入流
FileInputStream(String name)
FileInputStream(File file)
方法
int read() 从该输入流读取一个字节的数据。返回:下一个数据字节;如果到达流的末尾,则返回 -1
int read(byte[] b) 一组数据放入 数组中。返回:读入缓冲区的总字节数;如果因为已经到达流末尾而不再有数据可用,则返回 -1。 -
OutPutStream 输出
FileOutputStream() 文件字节输出流
FileOutputStream(String name[,boolean tf]) 是否续写
FileOutputStream(File file[,boolean tf])
方法
void write() 从该输入流读取一个字节的数据。
void write(byte[] b) 一组数据放入 数组中
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class Homework {
public static void readImg() throws IOException {
// UTF8中,中文占3字符;GBK中中文占2字符
FileInputStream fis = new FileInputStream("E:\\AAA学习\\图片\\20160817.jpeg");
FileOutputStream fos = new FileOutputStream("E:\\AAA学习\\图片\\图片sss.jpeg");//注意文件名后缀
byte[] arr = new byte[2048];
int i ;//读到最后返回-1
while((i= fis.read(arr)) != -1) {
fos.write(arr, 0, i);
}
System.out.println("读入成功");
fos.close();
fis.close(); //必须释放资源
}
public static void readTxt() throws IOException {
FileReader fileReader = new FileReader("src/day06/练习.txt");
FileWriter fileWriter = new FileWriter("D:/test.txt");
int i;
while((i=fileReader.read()) != -1) {
fileWriter.write(i);
}
// 释放资源,先开后关,后开先关
fileWriter.close();
fileReader.close();
}
public static void main(String[] args) throws IOException {
readImg();
readTxt();
}
}
注意:1.当做流操作时,如果文件不存在,读取流会抛出java.io.FileNotFoundException(系统找不到指定的文件。),写入流会创建新文件。 2.操作流时,如果流不关闭,很容易出现输入输出流经超越了JVM的边界,所以有时可能无法回收资源;读文件时,忘记关闭流,在操作系统里对这个文件写入,删除等就会报错,告诉你这个文件被某个进程占用。
1.1.2 缓冲流
字节缓冲流:建立在节点流之上
new BufferedInputStream(new FileInputStream)
字符缓冲流:
BufferedReader
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class TestBuffer {
public static void main(String[] args) throws IOException {
// 字符流和缓冲流的效率比较
copyBufferIOStream(); // 运行结果:3969
copyInput(); // 运行结果:1044111
}
public static void copyInput() throws IOException{
long start=System.currentTimeMillis();
FileInputStream fis = new FileInputStream("E:\\AAA学习\\A国信安\\课程视频\\01第一阶段-javaSE基础\\任小华_java基础_day03_输入输出01.wmv");
FileOutputStream foStream = new FileOutputStream("D:/c.wmv");
int i;
while((i = fis.read()) != -1){
foStream.write(i);
}
foStream.close();
fis.close();
System.out.println(System.currentTimeMillis() - start);
}
public static void copyBufferIOStream() throws IOException {
long start=System.currentTimeMillis();
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("E:\\AAA学习\\A国信安\\课程视频\\01第一阶段-javaSE基础\\任小华_java基础_day03_输入输出01.wmv"));
FileOutputStream foStream=new FileOutputStream("D:/c1.wmv");
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(foStream);
int i;
while((i = bufferedInputStream.read()) != -1){
bufferedOutputStream.write(i);
}
bufferedOutputStream.close();
foStream.close();
bufferedInputStream.close();
System.out.println(System.currentTimeMillis() - start);
}
}
1.1.3 对象流
持久化,把对象持久化,对象流:把对象转化为二进制数据(对象必须序列化)
建立在节点流之上的。
public class Cat implements Serializable{
private static final long serialVersionUID = -7848891081419598064L;
private String color;
private String name;
private int age;
private char sex;
// 序列化
// getter,setter,无参构造,有参构造,重写toString()
}
public static void read() throws FileNotFoundException, IOException, ClassNotFoundException{
ObjectInputStream objectInputStream = new
ObjectInputStream(new FileInputStream("src/day7/cat.dat"));
Object obj;
while(true){
try{
obj = objectInputStream.readObject();
System.out.println(obj);
}catch(EOFException e){
break;
}
}
}
public static void write() throws IOException{//ObjectOutputStream 对象流不支持续写
Cat cat1 = new Cat("黄色","小花",2,'母');
Cat cat2 = new Cat("黑色","小黑",3,'公');
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("路径/cat.dat")); // 二进制文件
oos.writeobject(cat1);
oos.writeobject(cat2);
objectOutput.close();
}
1.1.4 转换流
桥梁流
OutputStreamWriter继承Writer,字符流 ===> 字节流
InputStreamReader继承Reader,字节流 ===> 字符流
构造器:
InputStreamReader(Inputstream in) :创建一个使用默认字符集的 InputStreamReader。
第二个参数可以是:Charset cs,CharsetDecoder dec,String charsetName
方法:
void close() :关闭该流并释放与之关联的所有资源。
String getEncoding() :返回此流使用的字符编码的名称。
int read() //读取单个字符。
int read(char[] cbuf, int offset, int length) :将字符读入数组中的某一部分。
boolean ready():判断此流是否已经准备好用于读取。
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
public class TransforTest {
public static void main(String[] args) throws Exception {
File file = new File("D:" + File.separator + "Document" + File.separator + "Document" + File.separator + "test.txt");
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
OutputStream output = new FileOutputStream(file); //字节流
//将OutputStream类对象传递给OutputStreamWriter类的构造方法,而后向上转型为Writer
Writer out = new OutputStreamWriter(output);
out.write("Hello world!");
out.flush();
out.close();
}
}
注意:
实现从字节流到字符流之间的转换,使得流的处理效率得到提升,但是如果我们想要达到最大的效率,我们应该考虑使用缓冲字符流包装转换流的思路来解决问题:
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
1.2 File
电脑上的指定文件或目录
1.2.1 构造器,常用方法
1.构造器
- File(File parent, String child)
从父抽象路径名和子路径名字符串创建新的 File实例。 - File(String pathname)
通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。 - File(String parent, String child)
从父路径名字符串和子路径名字符串创建新的 File实例。 - File(URI uri)
通过将给定的 file: URI转换为抽象路径名来创建新的 File实例。
2.方法
(查看API)
- boolean isFile()判断是否是一个文件
- boolean isDirectory() 判断是否是一个目录
- boolean exists()判断文件是否存在
- boolean createNewFile()创建新文件
- boolean mkdir()创建新目录;mkdirs()创建多级目录,注意区别
- long length()文件长度
- URI toURI()获取文件的URI字符串
- File getParentFile()获得父级目录
- File getAbsoluteFile()获得绝对路径表示的文件;String …Path()绝对路径字符串
- String[] list()获得下级目录/文件的名称
- File[] listFiles()获得下级目录/文件对象
- boolean delete()
什么目录都可以删除吗?
File.delete()用于删除“某个文件或者空目录”!所以要删除某个目录及其中的所有文件和子目录,要进行递归删除,注意:
1.路径上不能出现java认为的非法字符,如“(”,“)”等;
2.确保删除操作之前,文件不再被使用,即文件资源被释放!
3.删除文件与文件夹时,要删除的内容:文件;子文件夹(有文件的,无文件的文件夹)
1.2.2 绝对路径,相对路径
绝对路径:从盘符开始
相对路径:这项目来说的路径
- 什么目录都可以删除吗?
File.delete()用于删除“某个文件或者空目录”!所以要删除某个目录及其中的所有文件和子目录,要进行递归删除,注意:
1.路径上不能出现java认为的非法字符,如“(”,“)”等;
2.确保删除操作之前,文件不再被使用,即文件资源被释放!
3.删除文件与文件夹时,要删除的内容:文件;子文件夹(有文件的,无文件的文件夹)
1.2.3 File操作
import java.io.File;
public class FileTest {
public static void fileName(File file,String k) {
File[] files = file.listFiles();
System.out.println(k + file.getAbsolutePath());
if(files != null) {
for(File fil : files) {
fileName(fil, k + "\t");
}
}
}
public static void main(String[] args) {
String path = "E:\\AAA学习\\A国信安\\作业";
File file = new File(path);
fileName(file,"");
}
}
树状输出,运行结果:
1.3 对象序列化
序列化:把Java对象转换为字节序列的过程。
反序列化:把字节序列恢复为Java对象的过程。
用途:
1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
2) 在网络上传送对象的字节序列。
只能将支持 java.io.Serializable 接口的对象写入流中。每个 serializable 对象的类都被编码,编码内容包括类名和类签名、对象的字段值和数组值,以及从初始对象中引用的其他所有对象的闭包。
什么是对象序列化?如何实现对象序列化?
对象序列化可以将一个对象保存到一个文件,可以将通过流的方式在网络上传输,可以将文件的内容读取转化为一个对象。所谓对象流也就是将对象的内容流化,可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对象流进行读写操作时引发的问题。
序列化的实现:将需要被序列化的类实现serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着使用ObjectOutputStream对象writeObject(Object obj)方法就可以将参数obj的对象写出,要恢复的话则用输入流。
对象的默认序列化机制写入的内容是:对象的类,类签名,以及非瞬态和非静态字段的值
2 线程
2.1 相关概念
●程序:program是一个静态的概念
●进程:process是一个动态的概念
●进程是程序的一次动态执行过程,占用特定的地址空间
●每个进程都是独立的,由3部分组成cpu data coder
●缺点:内存的浪费,cpu的负担
●线程:Thread是进程中的一个"单一 的连续控制流程/执行路径”
●线程又被称为轻量级进程
●一个进程可拥有多个并行的线程
● 一个进程中的线程共享相同的内存单元/内存地址空间->可以访问相同的变量和对象,而且它们从同一堆中分配对象->通信、数据交换、同步操作
●由于线程的通信是同一地址空间上进行的,所以不需要额外的通信机制,这使得通信更简便而信息传递的速度也更快。
2.1.1 进程和线程,进程和程序
线程是进程的一部分,所以线程有的时候被称为是轻权进程或者轻量级进程。
多任务处理有两种类型:
-基于进程
-基于线程
●进程是指一种“自包容”的运行程序,有自己的地址空间;线程是进程内部单一的一个顺序控制流
●基于进程的特点是允许计算机同时运行两个或更多的程序。
●基于线程的多任务处理环境中,线程是最小的处理单位。
区别 | 进程 | 线程 |
---|---|---|
根本区别 | 资源分配单位 | 调度和执行单位 |
开销 | 每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销 | 线程可以看成时轻量级的进程,同类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换的开销小 |
所处环境 | 在操作系统中能同时运行多个任务(程序) | 在同一应用程序中有多个顺序流同时执行 |
分配内存 | 系统在运行的时候会为每个进程分配不同的内存区域 | 除了CPU之外,不会为线程分配内存(线程所使用的资源是它所属的进程的资源) ,线程组只能共享资源 |
包含关系 | 没有线程的进程是可以被看作单线程的,如果一个进程内拥有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的。 | 线程是进程的一部分,所以线程有的时候被称为是轻权进程或者轻量级进程。 |
进程与程序:
- 程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。而进程是程序在处理机上的一次执行过程,它是一个动态的概念。
- 程序可以作为一种软件资料长期存在,而 进程是有一定生命期的。程序是永久的,进程是暂时的。
- 进程更能真实地描述并发,而程序不能;
- 进程是由进程控制块、程序段、数据段三部分组成;
- 进程具有创建其他进程的功能,而程序没有。
- 同一程序同时运行于若干个数据集合上,它将属于若干个不同的进程。一个程序可以对应多个进程。
- 程序不能独立运行,进程是资源分配的基本单位
2.1.2 线程的生命周期
- 实例化:通过new的方式产生一个线程对象。
- 就绪:strart一启动就进入就绪状态,之后线程每次执行在获得资源前都会进入就绪状态。
- 运行状态:处于就绪状态的线程得到系统资源(抢到cpu)后就进入运行状态。
- 阻塞:不在cpu队列中
sleep():休眠,线程休眠一定的时间,自动苏醒,才进入cpu队列中;
join():当前线程被阻塞,调用的线程有限运行,直到调用线程结束,当前线程才进入cpu队列中;
等待:调用Object的wait()方法;
yield():挂起,线程显示让出CPU控制权;
等待IO事件输入,如JOptionPane输入框; - 结束:线程run()方法执行完毕。
2.1.3 主线程和子线程
单线程程序:Java程序中只有一个线程,执行从main方法开始,从上到下依次执行;
JVM执行main方法, main方法会进入到栈内存,JVM会找操作系统开辟一条main方法通 向cpu的执行路径;cpu就可以通过这个路径来执行main方法,而这个路径有一个名字,叫main(主)线程。
程序启动是自己就有一个线程执行自己本身的代码,这就是主线程。UI界面和Main函数均为主线程。
子线程就是创建之后用户自己创建的线程。被Thread包含的“方法体”或者“委托”均为子线程。
主线程:main方法一运行。就产生了主线程。
主线程的特点:
(1)最先开始
(2)最后结束
(3)产生其他的子线程
(4)回收资源
2.1.4 线程调度
分时调度:
所有线程轮流使用CPU使用权,平均分配每个线程占用CPU的时间。
抢占式调度:
优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
2.2 并发
多个线程实例化,准备就绪,去cpu队列中排队,cpu尽量选取优先级高的线程执行,执行一个时间片(0.05ms),然后该线程又进入cpu队列中重新排队,在一定的时间段内,达到多个线程一起执行的效果,称为并发。
并发:指两个或多个事件在同一时间段内发生。(交替执行)
并行:指两个或多个事件在同一时刻发生(同时发生)。
高并发
2.3 多线程的实现方法(介绍4种)
2.3.1 继承Thread
继承Thread,重写run方法,启动(start)线程
class ThreadA extends Thread {
@Override
public void run() { // run代码执行的时候才是运行状态
// 你想进行的代码
for(int i = 0; i < 20; i++){
System.out.println((char)((int)(Math.random()*10+48)));
}
}
}
public class Test{
public static void main(String[] args){
ThreadA a = new ThreadA(); // 实例化
a.start(); // 就绪,不一定在执行
}
}
2.3.2 实现Runnable
实现Runnable,重写run方法,创建使用Thread构造器
构造器:
Thread:
Thread()
Thread(String name)
Thread(Runnable run)
Thread(Runnable run,String name)
属性:
MAX_PRIORITY 10
MIN_PRIORITY 1
NORM_PRIORITY 5
在JAVA线程中,当一个或多个线程,同时处于就绪状态。优先级高的线程,会优先得到执行。通过一个int priority来控制优先级,范围为1-10,其中10最高,默认值为5,数字越大,优先级越高。通过setPriority(int a)改变线程的优先级。
方法:
String getName()
void setName(String name)
static Thread currentThread()
返回对当前正在执行的线程对象的引用。
// Thread.currentThread().sleep(1000); 休眠1s
public class TestThread {
public static void main(String[] args) {
// 实例化
ThreadRunnableA threadRunnableA = new ThreadRunnableA();
// 实例化线程
Thread tA = new Thread(threadRunnableA, "线程A"); // 设置线程名称:线程A
Thread tB = new Thread(new ThreadRunnableB());
threadRunnableA.b = tB;
tB.setName("线程B");
System.out.println(tB.MAX_PRIORITY);// 1-10,默认5
System.out.println(tB.MIN_PRIORITY);
tA.start();
tB.start();
for (int i = 0; i < 10; i++) {
System.out.println(
Thread.currentThread().getName() + (char) ((int) (Math.random() * 26 + 65)) + "---------" + i);
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class ThreadRunnableA implements Runnable {// 线程扩展
Thread b;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
// 获得线程的名字 .getName()
// 获得当前的运行的线程Thread.currentThread()
System.out.println(
Thread.currentThread().getName() + (char) ((int) (Math.random() * 26 + 97)) + "---------" + i);
try {
Thread.currentThread().sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (i == 5) {
try {
b.join();// A线程让B先运行,B结束后,A才会进入cpu队列中
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class ThreadRunnableB implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(
Thread.currentThread().getName() + (char) ((int) (Math.random() * 10 + 48)) + "---------" + i);
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
2.3.3 使用Callable和Future创建线程
方法:V call()
throws 异常计算一个结果,如果不能这样做,就会抛出一个异常。
- 实现 Callable 接口,重写 call 方法(call方法比run方法强大的多);
- 用 FutureTask 类包裹 Callable 对象,然后再用 Thread 类包裹 FutureTask 类,并调用 start 方法启动线程;
- 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
call() 方法可以有返回值,可以声明抛出异常。
class MyCallable implements Callable {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
public static void main(String[] args) throws Exception {
MyCallable mc = new MyCallable(); //实例化 callable
FutureTask oneTask = new FutureTask(mc); //用FutureTask包裹
//使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)
Thread oneThread = new Thread(oneTask); //用Thread包裹
oneThread.start();
System.out.print(oneTask.get()); //获取返回值
}
}
Callable 方法在 Java 8 后,支持拉姆达(Lambda)表达式的写法,可以创建一个 FutureTask 类,语句上不是太罗嗦。
Callable 方式有以下几个优点:
1.可以捕获线程上的异常。
2.可以通过 get 方法得到返回值。
3.get 方法阻塞当前线程,直到调用的线程运行结束。
4.可以取消线程的运行。
下面代码演示了使用 FutureTask 类运行线程,捕获异常的例子:
FutureTask<Integer> task = new FutureTask<Integer>(()->{
throw new Exception("自定义异常");
});
new Thread(task).start();
try {
System.out.println(task.get());
} catch (Exception e) {
System.out.println(e.getMessage());
}
2.3.4 使用线程池,Executor
Java 6 之后,还可以通过创建线程池来创建线程,使用 ExecutorService 的 execute 方法:
public class MyCallable implements Callable<String>{
@Override
public String call() throws Exception {
return "MyCallable...";
}
}
// 创建和调用
// 创建Callable实现类的实例,创建一个线程池,调用线程池的submit方法,如果需要返回值就调用Future的get方法获取
MyCallable callable = new MyCallable();
ExecutorService eService = Executors.newSingleThreadExecutor();
Future<String> future = eService.submit(callable);
try {
String result = future.get(); //获取返回结果
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
/**
ExecutorService es = Executors.newCachedThreadPool();
Runnable r = <your runnable here>;
es.execute(r);
*/
2.3.5 Runnable和Collable区别
优:实现某接口,可以继承其他类,操作相对灵活,并且能多个纯种共享一个对象Thread t = new Thread(ft);里面的ft对象能多个线程共享;
劣:编程相对复杂一种方式是继承Tread类,不能再继承其他类,编程相对简单。
区别:
(1)语法规则
(2)Callable的call方法有返回值、可以抛异常(可以获取异常信息)、支持泛型,而Runnable的run方法没有返回值、也没有抛异常(只能抛出运行时异常且无法捕获处理)。
(3)Callable运行后可以拿到一个Future对象,这个对象表示异步计算结果,可以从通过Future的get方法获取到call方法返回的结果。但要注意调用Future的get方法时,当前线程会阻塞,直到call方法返回结果。
(4)Runnable是作为线程的构造参数运行的,Callable是作为线程池的submit方法的参数运行的。
2.4 线程同步
当两个或两个以上的线程同时访问同一个资源时,为了保护资源的数据安全,只允许同一时间一个线程对该资源进行访问。
(例如,一个线程可能尝试从一个文件中读取数据,而另一个线程则尝试在同一文件中修改数据,在此情况下,数据可能会变得不一致),为了确保在任何时间点一个共享的资源只被一个线程使用,使用了“同步”。
synchronized同步关键字,当该关键字修饰方法时,该方法叫做同步方法。同步方法意味着,该方法同一时间只允许一个线程访问。
同步造成的后果:
1.数据安全
2.效率低下
2.4.1 同步代码块
同步代码块
synchronized(锁){
}
同步要放在锁里面
案例1:售票窗口
public class ThreadPiao {// 票 1 2 3
public static void main(String[] args) {
// 共享的资源 锁(监视器) 一定同一把
Piao piao = new Piao();
Windows windows1 = new Windows(piao, "窗口1");
windows1.obj = new Object();
Windows windows2 = new Windows(piao, "窗口2");
windows2.obj = new Object();
Windows windows3 = new Windows(piao, "窗口3");
windows3.obj = new Object();
windows1.start();
windows2.start();
windows3.start();
}
}
class Piao {
public int number = 100;
}
class Windows extends Thread {
Piao piao;
Object obj;
public Windows(Piao piao, String name) {
this.piao = piao;
setName(name);
}
@Override
public void run() {
while (true) {
System.out.println(getName() + "办理业务");
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (piao) {
if (piao.number > 0) {
System.out.println(getName() + "售票成功---售出" + piao.number + "号");
piao.number--;
} else {
break;
}
}
}
}
}
运行结果截图:(部分)
可以看出,利用线程同步,售出的票是一张一张按顺序减少的。
案例2:设计一个银行,给用户取款功能和存钱功能
public class BankLock {
public static void main(String[] args) {
// 共享的
User user = new User();
ThreadSave save = new ThreadSave(user);
ThreadDraw draw = new ThreadDraw(user);
save.start();
draw.start();
}
}
class User {
double money;
}
class ThreadSave extends Thread { // 存钱
User user;
public ThreadSave(User user) {
this.user = user;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
int saveManoey = ((int) (Math.random() * 10 + 1)) * 100; // [100,1000]
try {
sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (user) {
System.out.print("存了" + saveManoey);
user.money += saveManoey;
System.out.println(",余额是:" + (user.money ));
}
}
}
}
class ThreadDraw extends Thread { // 取钱
User user;
public ThreadDraw(User user) {
this.user = user;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
int drawManoey = ((int) (Math.random() * 10 + 1)) * 100;
try {
sleep(600);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (user) {
if (user.money >= drawManoey) {
System.out.print("取了" + drawManoey);
user.money -= drawManoey;
System.out.println(",余额是:" + (user.money ));
} else {
System.out.println("要取" + drawManoey + ",但是余额不足!!!");
}
}
}
}
}
运行结果截图:
结果可以看出,存取是有规律的,如果不用线程同步就会造成存取金额不正确。
2.4.2 同步方法
还是上面的银行案例,感受一下,同步方法,语法就是在方法前面添加关键字synchronized 。
public class Bank { // StringBuffer线程安全
public static void main(String[] args) {
User user = new User();
Bank bank = new Bank();
// Bank bank2=new Bank();
ThreadSave1 save1 = new ThreadSave1(bank);
save1.user = user;
ThreadDraw1 draw = new ThreadDraw1(bank);
draw.user = user;
save1.start();
draw.start();
}
synchronized public void save(User user, double money) {// this当前对象——锁
System.out.print("用户存入" + money);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
user.money += money;
System.out.println(",余额还剩" + user.money);
}
synchronized public void draw(User user, double money) {
System.out.print("用户取" + money);
if (user.money >= money) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
user.money -= money;
System.out.println(",余额还剩" + user.money);
} else {
System.out.println("余额不足,取不出来");
}
}
}
class ThreadSave1 extends Thread {
Bank bank;
User user;
public ThreadSave1(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
int saveManoey = ((int) (Math.random() * 10 + 1)) * 100;
bank.save(user, saveManoey);
}
}
}
class ThreadDraw1 extends Thread {
Bank bank;
User user;
public ThreadDraw1(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
int drawManoey = ((int) (Math.random() * 10 + 1)) * 100;
bank.draw(user, drawManoey);
}
}
}
运行结果截图:
2.4.3 sleep() 和 wait()
2.4.3.1 测试sleep()
Object obj = new Object();
Object obj;共享的,作为锁
public class TestSleep {
public static void main(String[] args) {
Object obj = new Object();
ThreadOne one = new ThreadOne();
one.obj = obj;
one.setName("ThreadOne");
ThreadTwo two = new ThreadTwo();
two.obj = obj;
two.setName("ThreadTwo");
one.start();
two.start();
}
}
class ThreadOne extends Thread {
Object obj;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("--ThreadOne未持有锁");
synchronized (obj) {
System.out.println("--ThreadOne ------持有锁");
if (i == 5) {
System.out.println("进入休眠状态");
try {
sleep(5000);// 拿资源在睡觉
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("休眠状态结束");
}
System.out.println(getName() + ":" + i);
}
System.out.println("--ThreadOne ------释放锁");
}
}
}
class ThreadTwo extends Thread {
Object obj;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("--ThreadTwo未持有锁");
synchronized (obj) {
System.out.println("--ThreadTwo ------持有锁");
System.out.println(getName() + ":" + i);
}
System.out.println("--ThreadTwo ------释放锁");
}
}
}
运行结果:
2.4.3.2 测试wait()
public class TestWait {
public static void main(String[] args) {
Object obj = new Object();
ThreadWait wait = new ThreadWait(obj);
ThreadNotiy notiy=new ThreadNotiy(obj);
wait.start();
notiy.start();
}
}
class ThreadWait extends Thread {
Object obj;
public ThreadWait(Object obj) {
this.obj = obj;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("--ThreadTwo未持有锁");
synchronized (obj) {
if (i == 5) {
System.out.println("进入等待");
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("--ThreadTwo ------持有锁");
System.out.println(getName() + ":" + i);
}
System.out.println("--ThreadTwo *******释放锁");
}
}
}
class ThreadNotiy extends Thread {
Object obj;
public ThreadNotiy(Object obj) {
this.obj = obj;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("--ThreadTwo未持有锁");
synchronized (obj) {
if (i == 8) {
System.out.println("唤醒当前被等待的线程");
notifyAll();
}
System.out.println("--ThreadTwo ------持有锁");
System.out.println(getName() + ":" + i);
}
System.out.println("--ThreadTwo *******释放锁");
}
}
}
运行结果:
…
2.4.3.3 两者区别
区别:
- 实现类,对象
sleep() 方法是线程类(Thread)的static静态方法;wait()是Object类的方法; - 阻塞方式和唤醒方式不一致
sleep():让调用线程进入睡眠状态,让出执行机会给其他线程,等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争cpu的执行时间。
wait():当一个线程执行到wait方法时,它就进入到一个和该对象相关的等待池,同时释放对象的机锁,使得其他线程能够访问,可以通过notify,notifyAll方法来唤醒等待的线程。 - 锁
因为sleep() 是静态的方法,他不能改变对象的机锁,当一个synchronized块中调用了sleep() 方法,线程虽然进入休眠,但是对象的锁没有被释放,其他线程依然无法访问这个对象。
而调用wait() 方法会释放对象锁; - 使用
sleep()任何地方使用
wait()同步中使用
sleep():持有锁,占着资源又不用
wait():释放锁,其他线程可访问
sleep() | wait() |
---|---|
Thread中 | Object类 |
静态Tread.sleep() | 对象.wait() |
哪个位置调用,哪个线程等待 | 访问对象的其他线程等待 |
不需要唤醒 | 需要唤醒notify()、notifyAll() |
休眠一段时间,暂时释放CPU,不会释放锁 | 等待后释放 |
notify和notifyAll区别:
notify和notifyAll都是在Object类中提供;
notify:唤醒在此对象监视器上处于阻塞状态的单个线程;
notifyAll:唤醒在此对象监视器上处于阻塞状态的所有线程;
start()和run()区别:
start()方法让线程进入就绪状态,等待CPU等资源进入运行状态执行run()方法中的代码;
run()方法:只会在原线程中执行代码逻辑,并不会重新启动一个新的线程去执行;
等唤醒机制:设计的时候一对,不能单独成立
案例:(生产者消费者问题)
写在另外的地方,一丢丢:
https://blog.csdn.net/weixin_45044097/article/details/98770314
2.5 锁
2.5.1 Lock
锁可以是任何引用类型;
同步锁:锁住的是一个对象。如果一个线程拿到了一个对象的机锁去执行一段同步代码块了,那么其他线程都不能执行这个对象的其他同步代码块。
2.5.2 死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
synchronized和Lock的区别:
synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中。
产生原因:
a. 竞争资源
系统中的资源可以分为两类:
可剥夺资源,是指某进程在获得这类资源后,该资源可以再被其他进程或系统剥夺,CPU和主存均属于可剥夺性资源;
另一类资源是不可剥夺资源,当系统把这类资源分配给某进程后,再不能强行收回,只能在进程用完后自行释放,如磁带机、打印机等。
产生死锁中的竞争资源之一指的是竞争不可剥夺资源(例如:系统中只有一台打印机,可供进程P1使用,假定P1已占用了打印机,若P2继续要求打印机打印将阻塞)
产生死锁中的竞争资源另外一种资源指的是竞争临时资源(临时资源包括硬件中断、信号、消息、缓冲区内的消息等),通常消息通信顺序进行不当,则会产生死锁
b. 进程间推进顺序非法
若P1保持了资源R1,P2保持了资源R2,系统处于不安全状态,因为这两个进程再向前推进,便可能发生死锁
例如,当P1运行到P1:Request(R2)时,将因R2已被P2占用而阻塞;当P2运行到P2:Request(R1)时,也将因R1已被P1占用而阻塞,于是发生进程死锁
死锁的发生必须具备以下四个必要条件:
1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
4)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
解决死锁基本方法:
1.预防
2.避免
3.检测
4.解除
死锁检测:
参考原文链接:https://blog.csdn.net/hd12370/article/details/82814348
2.5.3 Lock与synchronized 的区别
synchronized:
在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化synchronize,另外可读性非常好,不管用没用过5.0多线程包的程序员都能理解。
ReentrantLock:
ReentrantLock提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。在资源竞争不激烈的情形下,性能稍微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍。而ReentrantLock确还能维持常态。
Atomic:
和上面的类似,不激烈情况下,性能比synchronized略逊,而激烈的时候,也能维持常态。激烈的时候,Atomic的性能会优于ReentrantLock一倍左右。但是其有一个缺点,就是只能同步一个值,一段代码中只能出现一个Atomic的变量,多于一个同步无效。因为他不能在多个Atomic之间同步。
参考原文链接:https://www.cnblogs.com/nsw2018/p/5821738.html
2.6 线程池
2.6.1 概述
线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
线程池:容器---->集合(ArrayList,HashSet,LinkedList< Thread >,HashMap)
当程序第一次启动的时候,创建多个线程,保存到一个集合中,当我们想要使用线程的时候,就可以从集合中取出来线程使用。
Thread t = list.remove(0);返回的是被移除的元素(线程只能被一个任务使用)
Thread t = linked.removeList();当我们使用完毕线程,需要把线程归还给线程
list.add(t);
linked.addList(t);
// 在JDK 1.5之后,JDK内置线程池,直接使用
2.6.2 线程池使用
线程池:JDK1.5之后提供的
java.util.concurrent.Executors : 线程池的工厂类,用来生成线程池
Executor5类中的静态方法:
static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池
参数:
int nThreads :创建线程池中包含的线程数量
返回值:
ExecutorService接口,返回的是Executorservice接口的实现类对象,我们可以使用Executorservice接口接收(面向接口编程)
java.util.concurrent.Executorservice :线程池接口
用来从线程池中获取线程,调用start方法,执行线程任务
submit(Runnable task) 提交一个Runnable任务用于执行
关闭/销毁线程池的方法void shutdown()
线程池的使用步骤:
1.使用线程他的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
2.创建一个类,实现Runnable接口,重写run方法,设置线程任务
3.调用Executorservice中的方法submit,传递线程任务(实现类) ,开启线程,执行run方法
4.调用Executorservice中的方法shutdown销毁线程池(不建议执行)
3 Lambda表达式
3.1 函数式编程思想概述
在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿什么东西做什么事情"。相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽星忽略面向对象的复杂语法——强调做什么,而不是以什么形式做。
面向对象的思想:
做一件事情,找一个能解决这个事的对象调用对象的方法,完成事情。
函数式编程思想:
只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程。
冗余的Runnable代码
1.
RunnableImpl类只是为了实现Runnable接口而存在的,而且仅被使用了唯一次,所以使用匿名内部类的语法即可省去该实现类的单独定义,即匿名内部类:
3.2 使用
Runnable接口只有一个run方法的定义:public abstract void run();
无参;无返回值;代码块
语义体现在Lambda语法中:
() -> System.out.println("多线程执行");
●前面的一对小括号即run方法的参数(无) , 代表不需要任何条件;
●中间的一个箭头代表将前面的参数传递给后面的代码;
●后面的输出语句即业务逻辑代码。
Lambda表达式的标准格式:
1.一些参数
2.一个箭头
3.一段代码
有参有返回
Lambda表达式省略格式
凡是根据上下文推导出来的内容,都可以省略不写。
可以省略的内容:
1.(参数列表):括号中的参数列表的数据类型,可以省略不写。
2.(参数列表):括号中的参数如果只有一个,那么类型和()都可以省略。
3.{一些代码}:如果{}中的代码只有一行,无论是否有返回值,都可以省略({},return,分号)
注意:要省略{},return,分号就必须一起省略