javaSE(5)——IO,线程,Lambda

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. 流操作步骤:
    1.创建源或者目标对象
    2.创建IO流对象
    3.具体的IO操作
    4.关闭资源

  2. InputStream 输入
    FileInputStream() 文件字节输入流
    FileInputStream(String name)
    FileInputStream(File file)
    方法
    int read() 从该输入流读取一个字节的数据。返回:下一个数据字节;如果到达流的末尾,则返回 -1
    int read(byte[] b) 一组数据放入 数组中。返回:读入缓冲区的总字节数;如果因为已经到达流末尾而不再有数据可用,则返回 -1。

  3. 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 线程的生命周期
  1. 实例化:通过new的方式产生一个线程对象。
  2. 就绪:strart一启动就进入就绪状态,之后线程每次执行在获得资源前都会进入就绪状态。
  3. 运行状态:处于就绪状态的线程得到系统资源(抢到cpu)后就进入运行状态。
  4. 阻塞:不在cpu队列中
    sleep():休眠,线程休眠一定的时间,自动苏醒,才进入cpu队列中;
    join():当前线程被阻塞,调用的线程有限运行,直到调用线程结束,当前线程才进入cpu队列中;
    等待:调用Object的wait()方法;
    yield():挂起,线程显示让出CPU控制权;
    等待IO事件输入,如JOptionPane输入框;
  5. 结束:线程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 异常计算一个结果,如果不能这样做,就会抛出一个异常。

  1. 实现 Callable 接口,重写 call 方法(call方法比run方法强大的多);
  2. 用 FutureTask 类包裹 Callable 对象,然后再用 Thread 类包裹 FutureTask 类,并调用 start 方法启动线程;
  3. 调用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 两者区别

区别:

  1. 实现类,对象
    sleep() 方法是线程类(Thread)的static静态方法;wait()是Object类的方法;
  2. 阻塞方式和唤醒方式不一致
    sleep():让调用线程进入睡眠状态,让出执行机会给其他线程,等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争cpu的执行时间。
    wait():当一个线程执行到wait方法时,它就进入到一个和该对象相关的等待池,同时释放对象的机锁,使得其他线程能够访问,可以通过notify,notifyAll方法来唤醒等待的线程。

  3. 因为sleep() 是静态的方法,他不能改变对象的机锁,当一个synchronized块中调用了sleep() 方法,线程虽然进入休眠,但是对象的锁没有被释放,其他线程依然无法访问这个对象。
    而调用wait() 方法会释放对象锁;
  4. 使用
    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,分号就必须一起省略

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值