网站详情页设计,石家庄最新数据消息,网页制作培训网站,简易html个人介绍网页File类
1. 概述 File#xff0c;是文件和目录路径的抽象表示 File只关注文件本身的信息#xff0c;而不能操作文件里的内容 。如果需要读取或写入文件内容#xff0c;必须使用IO流来完成。 在Java中#xff0c;java.io.File 类用于表示文件或目录的抽象路径名。它提供了一…File类
1. 概述 File是文件和目录路径的抽象表示 File只关注文件本身的信息而不能操作文件里的内容 。如果需要读取或写入文件内容必须使用IO流来完成。 在Java中java.io.File 类用于表示文件或目录的抽象路径名。它提供了一组方法可以用于创建、访问、重命名、删除文件或目录以及获取文件或目录的属性等操作。 2. File类的使用
创建文件对象
File file new File(d:/to/file.txt); // 使用文件路径创建文件对象创建目录
File dir new File(d:/to/directory); // 使用目录路径创建文件对象
boolean created dir.mkdir(); // 创建目录检查文件或目录是否存在
boolean exists file.exists(); // 检查文件是否存在
boolean isDirectory file.isDirectory(); // 检查是否为目录
boolean isFile file.isFile(); // 检查是否为文件获取文件或目录的属性
String name file.getName(); // 获取文件或目录的名称
String absolutePath file.getAbsolutePath();//获取文件或目录的绝对路径
String path file.getPath(); // 获取文件或目录的相对路径
long size file.length(); // 获取文件的大小字节数byte
long lastModified file.lastModified(); // 获取文件或目录的最后修改时间
String name file.getName();//获取文件的名字包含了文件的扩展名文件或目录的操作
boolean renamed file.renameTo(new File(f:/path/to/file.txt)); // 重命名文件或目录
boolean deleted file.delete(); // 删除文件或目录遍历目录下的文件和子目录
File[] files dir.listFiles(); // 获取目录下的文件和子目录列表
for (File f : files) {if (f.isFile()) {// 处理文件} else if (f.isDirectory()) {// 处理子目录}
}绝对路径和相对路径 绝对路径带有盘符就是绝对路径 相对路径相对路径是相对于工程目录进行定位 文件查找和定位 文件查找和定位一般我们都是先找到父级文件夹再找到具体的文件 这种定位方式我们一般都是通过两个参数来体现 //第一种第一个参数是父级文件夹路径第二个参数是文件名
File f1 new File(d:/io, test.txt);
System.out.println(f1.exists());
//第二种第一个参数是父级文件夹对象第二个参数是文件名
File parent new File(d:/io);
File f2 new File(parent, user.txt);
System.out.println(f2.exists());列出文件夹下的所有文件下一级 File folder new File(d:/io);//列出文件夹下的所有文件下一级File[] files folder.listFiles();if(files ! null){Arrays.stream(files).forEach(System.out::println);}结果 递归扫描文件夹
package com.wz.io;import java.io.File;public class ScanTest {public static void main(String[] args) {String folder d:/io; // 指定要扫描的文件夹路径scanFolder(new File(folder)); // 调用scanFolder方法开始扫描}public static void scanFolder(File folder) {if (folder.isFile()) { // 如果是文件直接打印文件路径System.out.println(folder);} else {// 如果是文件夹列出文件夹的下一级子文件File[] files folder.listFiles();if (files ! null) {for (File f : files) {if (f.isDirectory()) { // 如果是子文件夹递归调用scanFolder方法scanFolder(f);} else { // 如果是文件打印文件路径System.out.println(f);}}}}}
} 结果 递归删除文件夹
package com.wz.io;import java.io.File;public class DeleteTest {public static void main(String[] args) {String folder d:/test; // 指定要删除的文件夹路径deleteFolder(new File(folder)); // 调用deleteFolder方法开始删除}public static void deleteFolder(File folder) {if (folder.isDirectory()) { // 如果是文件夹File[] files folder.listFiles(); // 列出文件夹的下一级子文件if (files ! null) {for (File f : files) {if (f.isDirectory()) { // 如果是子文件夹递归调用deleteFolder方法deleteFolder(f);} else { // 如果是文件直接删除f.delete();}}}}folder.delete(); // 删除文件夹本身}
}
IO流 流是一抽象概念是对数据传输的总称。也就是说数据在设备间的传输称为流。更具体一点是内存与存储设备之间传输数据的通道。 IO的概念IO input Output也就是输入和输出参照物就是内存 针对内存来说把数据读入内存称为输入将内存中的数据写出去就是输出。 IO按照读的方式分为字节流和字符流。字节流每次读取的基本单位是字节字符流每次读取的单位是一个字符2个字节因此字节流每次读取8位字符流每次读取16位。 1. 字节流
1. OutputStream输出流写数据
package com.wz.io01_class;import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;public class OutputStreamTest {public static void main(String[] args) {String path d:/io/output.txt;File file new File(path);//判断父级目录是否存在File parentFile file.getParentFile();if (!parentFile.exists()){parentFile.mkdirs();}//try-with-resourcesJDK1.7提供的新特性 IO流在使用了之后会自动关闭//try-with-resources try后面的()中可以写多行代码但是必须以分号分割开最后一行代码的//分号可以省略。能够写入括号中的内容必须是实现了AutoClosable接口的流的构建try (OutputStream os new FileOutputStream(file,true)) {String content hello world!!;final byte[] data content.getBytes();//获取字符串的byte数据os.write(data);//写入数据os.flush();//强制将通道中的数据刷出写入文件}catch (IOException e){e.printStackTrace();}}
}
2. InputStream输入流读数据
package com.wz.io01_class;import java.io.*;public class InputStreamTest {public static void main(java.lang.String[] args) {File file new File(d:/io/output.txt);try (InputStream in new FileInputStream(file);){//如果文件比较大我们需要构建一个容器来反复读取文件内容byte[] buffer new byte[5];int len;while ((len in.read(buffer)) ! -1){System.out.println(new java.lang.String(buffer,0, len));}} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {throw new RuntimeException(e);}}
}
2. 字符流 字符流Character Stream是Java中用于以字符为单位进行读写操作的输入输出流。与字节流不同字符流以字符作为数据处理的基本单位而不是字节。 Java提供了两个主要的字符流类Reader和Writer。这些类通常用于处理字符数据例如文本文件或网络连接中的文本数据。 Reader类是抽象类它的子类用于从字符源读取字符数据。常用的Reader子类包括FileReader从文件中读取字符、InputStreamReader从字节流中读取字符等。 package com.wz.char_class;import java.io.*;public class ReaderTest {public static void main(String[] args) {//字符流的顶层父类Reader Writertry(Reader reader new FileReader(d:/io/output.txt);Writer writer new FileWriter(d:/io/output03.txt)){char[] buffer new char[2048];int len;while ((lenreader.read(buffer))!-1){System.out.println(new String(buffer,0,len));writer.write(buffer,0,len);}}catch (IOException e){e.printStackTrace();}}
} 这段代码首先通过FileReader创建一个字符输入流reader用于读取文件d:/io/output.txt的内容。然后通过FileWriter创建一个字符输出流writer用于写入内容到文件d:/io/output03.txt。 在代码的主体部分我们使用一个字符数组buffer作为缓冲区大小设置为2048个字符。reader.read(buffer)方法会将文件的内容读取到buffer中并返回实际读取的字符数或者返回-1表示已到达文件末尾。 然后通过new String(buffer, 0, len)将buffer中的字符转换为字符串并在控制台打印该字符串。 最后使用writer.write(buffer, 0, len)将buffer中的字符写入到输出文件中。 try-with-resources语句用于自动关闭字符流。这样可以确保在代码块结束时无论是否发生异常都会正确关闭字符流。 3. 字节缓冲流 字节缓冲流BufferedInputStream和BufferedOutputStream是Java IO流中的一种类型它们提供了缓冲功能可以提高读写操作的效率。 字节缓冲流继承自字节流InputStream和OutputStream它们通过在内存中创建一个缓冲区byte数组来存储数据。当使用字节缓冲流进行读取或写入操作时数据会先被读取到缓冲区中然后从缓冲区中读取或写入到目标位置。这样可以减少实际的IO操作次数提高读写效率。 BufferedInputStream类提供了以下常用方法
int read()//从输入流中读取一个字节数据并返回其整数表示0-255如果已经读取到流的末尾则返回-1。
int read(byte[] buffer)//从输入流中读取一定数量的字节数据并将其存储到指定的字节数组buffer中返回实际读取的字节数如果已经读取到流的末尾则返回-1。
void close()//关闭输入流。
BufferedOutputStream类提供了以下常用方法
void write(int byteValue)// 向输出流中写入一个字节数据。
void write(byte[] buffer)// 将指定的字节数组buffer中的数据写入到输出流中。
void flush()// 刷新输出流将缓冲区中的数据立即写入到目标位置。
void close()//关闭输出流。 在使用字节缓冲流进行写入操作时数据并不会立即写入到目标位置而是先存储在缓冲区中。如果需要立即将数据写入到目标位置可以调用flush方法刷新输出流。 字节缓冲流在处理大量数据时能够提供较高的读写效率特别适用于频繁读写小块数据的场景。在进行文件复制、网络传输等操作时使用字节缓冲流可以显著提升性能。 利用字节缓冲流进行文件拷贝
package com.wz.charBufferStream;import java.io.*;public class Test01 {public static void main(String[] args) {String sourceFile d:/io/IO流理解图.png;String destFile d:/io/copy.png;copyFile(sourceFile,destFile);}public static void copyFile(String sourceFile,String destFile){//创建一个File对象表示目标文件。File file new File(destFile);//获取目标文件的父目录File parentFile file.getParentFile();//判断父目录是否存在if (!parentFile.exists()) parentFile.mkdirs();//创建一个BufferedInputStream对象并将其初始化为一个FileInputStream对象的包装器用于读取源文件的数据。//创建一个BufferedOutputStream对象并将其初始化为一个FileOutputStream对象的包装器用于写入目标文件的数据。try (BufferedInputStream bis new BufferedInputStream(new FileInputStream(sourceFile));BufferedOutputStream bos new BufferedOutputStream(new FileOutputStream(destFile))) {//创建一个字节数组作为缓冲区用于存储从源文件读取的数据。byte[] buffer new byte[2048];while(true){int len bis.read(buffer);if (len-1)break;bos.write(buffer,0,len);bos.flush();}} catch (IOException e) {e.printStackTrace();}}
}
4. 字符缓冲流 字符缓冲流是Java IO提供的一种高效的字符流用于处理字符数据。它是基于字符流的装饰器模式实现的通过在字符流的基础上添加缓冲功能来提高读写性能。 在Java中字符缓冲流有两个主要的类BufferedReader和BufferedWriter。 BufferedReader BufferedReader是字符缓冲输入流它提供了一些额外的方法来增强字符输入流的功能。它可以缓冲字符允许高效的读取字符数据。它的构造方法可以接受一个字符输入流作为参数然后创建一个带有缓冲功能的字符输入流。 常用方法 readLine()//读取一行字符数据并返回一个字符串如果到达文件末尾则返回null。
read()//读取一个字符。
close()//关闭流同时会关闭基础的字符输入流。 try (BufferedReader reader new BufferedReader(new FileReader(file.txt))) {String line;while ((line reader.readLine()) ! null) {System.out.println(line);}} catch (IOException e) {e.printStackTrace();}
BufferedWriter BufferedWriter是字符缓冲输出流它提供了一些额外的方法来增强字符输出流的功能。它可以缓冲字符并提供高效的写入操作。它的构造方法可以接受一个字符输出流作为参数然后创建一个带有缓冲功能的字符输出流。 常用方法 write(String str)//将字符串写入流中。
newLine()//写入一个行分隔符。
flush()//刷新缓冲区将数据写入基础的字符输出流。
close()//关闭流同时会关闭基础的字符输出流。 try (BufferedWriter writer new BufferedWriter(new FileWriter(file.txt))) {writer.write(Hello, world!);writer.newLine();writer.write(This is a test.);writer.flush();} catch (IOException e) {e.printStackTrace();} 通过使用字符缓冲流可以提高字符输入输出操作的性能尤其是在处理大量字符数据时它能减少实际IO操作的次数从而提高程序的效率。 5. 数据流 在Java的I/O流中数据流Data Stream是一种流类别用于处理基本数据类型和字符串的输入和输出。数据流提供了一种方便的方式来读取和写入原始数据类型如intdoubleboolean等以及字符串而无需手动进行数据类型转换。 Java的数据流操作由两个主要类组成 数据流的部分方法
void writeBoolean(boolean v) throws IOException;//将布尔值作为1个字节写入底层输出通道
void writeByte(int v) throws IOException;//将字节写入底层输出通道
void writeShort(int v) throws IOException;//将短整数作为2个字节(高位在前)写入底层输出通道
void writeChar(int v) throws IOException;//将字符作为2个字节写(高位在前)入底层输出通道
void writeInt(int v) throws IOException;//将整数作为4个字节写(高位在前)入底层输出通道
void writeLong(long v) throws IOException;//将长整数作为8个字节写(高位在前)入底层输出通道
void writeFloat(float v) throws IOException;//将单精度浮点数作为4个字节写(高位在前)入底层输出通道
void writeDouble(double v) throws IOException;//将双精度浮点数作为8个字节写(高位在前)入底层输出通道
void writeUTF(String s) throws IOException;//将UTF-8编码格式的字符串以与机器无关的方式写入底层输出通道。
package com.wz.io01;import java.io.*;public class Test01 {public static void main(String[] args) {dataStream();}private static void dataStream(){String path d:/io/test.txt;try (DataOutputStream dos new DataOutputStream(new FileOutputStream(path));){dos.writeBoolean(false);dos.writeInt(1);dos.writeByte(2);dos.writeShort(3);dos.writeLong(4);dos.writeFloat(5.0f);dos.writeDouble(6.0);dos.writeChar(a);dos.writeUTF(Hello World);dos.flush();} catch (IOException e) {e.printStackTrace();}try (DataInputStream dis new DataInputStream(new FileInputStream(path))){boolean b dis.readBoolean();System.out.println(b);int i dis.readInt();System.out.println(i);byte b1 dis.readByte();System.out.println(b1);short s dis.readShort();System.out.println(s);long l dis.readLong();System.out.println(l);float v dis.readFloat();System.out.println(v);double v1 dis.readDouble();System.out.println(v1);char c dis.readChar();System.out.println(c);String s1 dis.readUTF();System.out.println(s1);} catch (IOException e) {e.printStackTrace();}}
} 结果 注意 在数据流中读取顺序必须与写入顺序保持一致。这是因为数据流中的数据是按照特定的格式写入的如果读取顺序与写入顺序不一致就会导致数据读取错误或解析错误。 当使用数据流进行读取时数据流会按照先后顺序解析数据并将其转换为相应的数据类型。如果读取顺序与写入顺序不一致例如尝试先读取一个整数然后读取一个字符串这样会导致读取出的数据类型不匹配造成解析错误。 6. 序列化 将一个对象从内存中写入磁盘文件中的过程称之为序列化反之就是反序列化。序列化必须要求该对象所有类型实现序列化的接口Serializable 注意 序列化和反序列化的对象版本一致性即序列化期间的Java类版本与反序列化期间的Java类版本应保持一致。如果版本不一致可能会导致对象反序列化失败或数据丢失。 Serializable接口仅仅只用于标识序列化 实现了Serializable接口的类可以通过ObjectOutputStream类进行序列化通过ObjectInputStream类进行反序列化。 package com.wz.io02;import java.io.*;public class Test {public static void main(String[] args) {serialize();}public static void serialize(){Student student new Student(ZhangSan, 18, 男);try(ObjectOutputStream oos new ObjectOutputStream(new FileOutputStream(d:/io/test.txt))){oos.writeObject(student);oos.flush();}catch (IOException e){e.printStackTrace();}try(ObjectInputStream ois new ObjectInputStream(new FileInputStream(d:/io/test.txt))) {Student s (Student) ois.readObject();System.out.println(s);} catch (IOException e) {throw new RuntimeException(e);} catch (ClassNotFoundException e) {throw new RuntimeException(e);}}
}
进程和线程
1. 概述
什么是进程 进程是操作系统进行资源分配和调度的基本单位。每个进程都是独立运行的相互之间互不干扰。进程可以包含一个或多个线程。 什么是线程 是进程内的执行单元。一个进程可以包含多个线程它们共享进程的内存空间和系统资源。线程是程序执行的最小单位可以独立执行特定的任务。线程共享进程的上下文包括内存、文件句柄和其他系统资源。多个线程可以在同一时间内并发执行提高程序的并发性和效率。 并发和并行的区别 并发Concurrency 是指多个任务在同一时间段内交替执行的能力。在并发中多个任务可以在同一时间段内启动、执行和完成但并不一定是同时进行。并发的目标是提高系统的吞吐量和响应性通过合理地调度和利用资源使得多个任务能够共享系统资源并以合理的方式并发执行。在并发中任务之间可能会相互交互和依赖需要进行同步和协调。 并行Parallelism 是指多个任务在同一时间点同时执行的能力。在并行中多个任务可以同时启动、执行和完成每个任务都在独立的处理单元如多个CPU核心上并行执行。并行的目标是通过同时执行多个任务来提高系统的计算能力和处理速度。在并行中任务之间通常是独立的彼此之间没有交互和依赖。 并发是指多个任务在同一时间段内交替执行而并行是指多个任务在同一时间点同时执行。 注意并发和并行并不是互斥的概念。在某些情况下可以同时使用并发和并行来提高系统的性能和效率。例如可以使用并发来处理多个用户请求而在每个请求内部使用并行来加速计算或处理密集型任务。 2. 线程的创建
继承Thread类
package com.wz.Thread01;
public class Mythread extends Thread{Overridepublic void run() {System.out.println(线程执行);}
}public class Test01 {public static void main(String[] args) {Mythread mythread new Mythread();mythread.start();}
} 创建一个继承自Thread类的子类重写run()方法来定义线程的执行逻辑。然后通过创建子类的实例并调用start()方法来启动线程。 实现Runnable接口
package com.wz.Thread02;
public class MyRunnable implements Runnable{Overridepublic void run() {System.out.println(线程执行);}
}package com.wz.Thread02;
public class MyRunnableTest {public static void main(String[] args) {MyRunnable runnable new MyRunnable();Thread thread new Thread(runnable);thread.start();}
} 创建一个实现了Runnable接口的类实现run()方法来定义线程的执行逻辑。然后通过创建Runnable实例并将其作为参数传递给Thread类的构造函数来创建线程 使用匿名内部类
package com.wz.Thread03;public class ThreadTest {public static void main(String[] args) {
// Thread thread new Thread(new Runnable() {
// Override
// public void run() {
// System.out.println(线程runThread.currentThread().getName());
// }
// },线程A);Thread thread new Thread(() - System.out.println(线程runThread.currentThread().getName()),线程A);thread.start();}
}
3. 线程中的方法
public synchronized void start();//启动线程但不一定会执行
public final String getName();//获取线程名称
public final synchronized void setName(String name);//设置线程的名称
public final void setPriority(int newPriority);//设置线程的优先级
public final int getPriority();//获取线程的优先级
public final void join() throws InterruptedException;//等待线程执行完成
//等待线程执行给定的时间(单位毫秒)
public final synchronized void join(long millis) throws InterruptedException;
//等待线程执行给定的时间(单位毫秒、纳秒)
public final synchronized void join(long millis, int nanos) throws InterruptedException;
public long getId();//获取线程的ID
public State getState();//获取线程的状态
public boolean isInterrupted();//检测线程是否被打断
public void interrupt();//打断线程public static native Thread currentThread();//获取当前运行的线程
public static boolean interrupted();//检测当前运行的线程是否被打断
public static native void yield();//暂停当前运行的线程然后再与其他线程争抢资源称为线程礼让
//使当前线程睡眠给定的时间单位毫秒
public static native void sleep(long millis) throws InterruptedException;
//使当前线程睡眠给定的时间单位毫秒、纳秒
public static void sleep(long millis, int nanos) throws InterruptedException;
package com.wz.Thread04;public class Test {public static void main(String[] args) {Thread thread new Thread(() - {for (int i 0; i 10; i) {System.out.println(线程 Thread.currentThread().getName()正在执行i);try {//休眠Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}},线程一);//修改线程名称thread.setName(Thread01);//获取线程状态System.out.println(thread.getState());thread.start();//获取线程状态System.out.println(thread.getState());for (int i 0; i 10; i) {System.out.println(线程Thread.currentThread().getName()正在执行i);if (i4){try {thread.join();} catch (InterruptedException e) {throw new RuntimeException(e);}}}//获取线程状态System.out.println(thread.getState());}
} 结果 分析 首先在主函数main()中创建了一个新的线程对象thread使用Lambda表达式定义了线程的执行逻辑。线程的执行逻辑是打印线程名和循环计数器的值并在每次循环后休眠1秒。通过调用thread.setName(“Thread01”)方法将线程的名称设置为Thread01。调用thread.getState()方法获取线程的状态。由于线程还没有启动所以此时线程的状态为NEW新建。调用thread.start()方法启动线程的执行。线程开始执行后会自动调用线程对象中的run()方法。再次调用thread.getState()方法获取线程的状态。此时线程的状态为RUNNABLE可运行。接下来在主线程中使用一个循环打印主线程的名称和循环计数器的值。当循环计数器的值为4时主线程调用thread.join()方法等待线程thread执行完成。在线程thread执行完毕后主线程继续执行。此时再次调用thread.getState()方法获取线程的状态此时线程的状态为TERMINATED终止。 代码的执行流程如下 主线程创建并启动线程thread。 主线程执行自己的循环打印操作。 当循环计数器的值为4时主线程调用thread.join()方法等待线程thread执行完成。 线程thread执行自己的循环打印操作。 线程thread执行完毕后主线程继续执行自己的循环打印操作。 4. 线程同步 (synchronized) synchronized 是 Java 中用于实现线程同步的关键字。它可以用来修饰方法或代码块以确保在同一时刻只有一个线程可以访问被修饰的代码。 synchronized 的作用是获取对象的锁只有获取到锁的线程才能执行被 synchronized 修饰的代码其他线程则需要等待锁的释放。当一个线程执行完 synchronized 代码块或方法后会释放锁其他等待锁的线程将有机会获取锁并执行代码。 使用 synchronized 可以有效地保证多线程环境下的数据安全性避免多个线程同时访问共享资源导致的数据竞争和不一致性。然而过度使用 synchronized 也可能导致性能问题因为只有一个线程能够执行被锁定的代码其他线程需要等待可能会造成线程的阻塞和效率降低。 卖票案例某火车站有10张火车票在3个窗口售卖 同步方法
package com.wz.Thread05;public class Test {/*** 某火车站有10张火车票在3个窗口售卖*/public static void main(String[] args) {Task task new Task();Thread t1 new Thread(task,窗口1);Thread t2 new Thread(task,窗口2);Thread t3 new Thread(task,窗口3);t1.start();t2.start();t3.start();}static class Task implements Runnable{private int totalTickets 10;private synchronized void saleTicket(){if (totalTickets0){String name Thread.currentThread().getName();System.out.println(name正在售卖车票totalTickets);totalTickets--;}}Overridepublic void run() {while (true){saleTicket();if (totalTickets0) break;try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}}
}
同步代码快
package com.wz.Thread06;public class Test {/*** 某火车站有10张火车票在3个窗口售卖*/public static void main(String[] args) {Task task new Task();Thread t1 new Thread(task, 窗口1);Thread t2 new Thread(task, 窗口2);Thread t3 new Thread(task, 窗口3);t1.start();t2.start();t3.start();}static class Task implements Runnable {private int totalTickets 10;private Object obj new Object();Overridepublic void run() {while (true) {synchronized (obj) {if (totalTickets 0) {String name Thread.currentThread().getName();System.out.println(name 正在售卖车票 totalTickets);totalTickets--;}if (totalTickets 0) break;try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}}}}}
} 使用obj作为锁对象的原因 使用 obj 对象作为锁的好处是它是唯一的所有线程都可以通过该对象来实现同步。 锁对象可以是任意对象只要确保多个线程共享同一个对象即可。在这个例子中使用了一个简单的 Object 对象作为锁但也可以使用其他对象比如自定义的对象或类的实例对象。 使用 synchronized 关键字锁定一个对象可以确保在同一时刻只有一个线程可以进入被锁定的代码块从而保证了线程的安全性。 5. 线程同步 (Lock) Lock 是 Java 提供的一个更灵活和可扩展的锁机制相比于 synchronized 关键字它提供了更多的功能和灵活性。 Lock 接口定义了一组用于获取和释放锁的方法其中最常用的实现类是 ReentrantLock。 使用 Lock 锁的一般模式如下 创建一个 Lock 对象。
Lock lock new ReentrantLock();
在需要同步的代码块前调用 lock() 方法获取锁。
lock.lock();
try {// 同步代码块
} finally {// 保证在任何情况下都会释放锁放在 finally 块中lock.unlock();
} lock() 方法会尝试获取锁如果锁已经被其他线程持有则当前线程会被阻塞直到获取到锁为止。 在代码块的最后使用 unlock() 方法释放锁。
lock.unlock(); unlock() 方法用于释放锁让其他等待锁的线程有机会获取锁并执行代码。 相比于 synchronizedLock 提供了更多的功能
可以实现公平性ReentrantLock 的构造函数可以传入一个 boolean 值用于指定是否公平获取锁。当设置为公平锁时线程会按照申请锁的顺序获取锁避免线程饥饿现象。支持可中断的获取锁lock() 方法可以响应中断当一个线程在等待锁的过程中被中断它可以选择继续等待获取锁或者放弃锁。支持超时获取锁tryLock() 方法可以尝试获取锁如果在指定的时间内没有获取到锁可以根据返回结果做相应的处理。支持多个条件的等待和唤醒Lock 提供了 Condition 接口可以通过 newCondition() 方法创建多个条件对象线程可以在不同的条件上等待和唤醒。 注意使用 Lock 需要手动释放锁否则可能导致死锁的发生。因此在使用 Lock 时一般会将获取锁和释放锁的代码包裹在 try-finally 块中确保锁的释放。 卖票案例某火车站有10张火车票在3个窗口售卖 package com.wz.Thread07;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class Test {public static void main(String[] args) {Task task new Task();Thread t1 new Thread(task, 窗口1);Thread t2 new Thread(task, 窗口2);Thread t3 new Thread(task, 窗口3);t1.start();t2.start();t3.start();}static class Task implements Runnable{private int totalTickets 10;private Lock lock new ReentrantLock();//创建一个lock对象Overridepublic void run() {while (true){if (lock.tryLock()){try{if (totalTickets0){String name Thread.currentThread().getName();System.out.println(name售卖车票totalTickets);totalTickets--;}}finally {lock.unlock();}}if (totalTickets0)break;try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}}}
} 使用 tryLock() 方法来尝试获取锁而不是直接使用 lock() 方法阻塞等待获取锁的好处是如果锁被其他线程持有当前线程不会被阻塞而是可以继续执行其他操作。 需要注意的是使用 tryLock() 方法获取锁后需要在 finally 块中调用 unlock() 方法来确保锁的释放以防止死锁的发生。 6. 线程通信 小明每次没有生活费了就给他的爸爸打电话他的爸爸知道了后就去银行存钱钱存好了之后就通知小明去取。 分析 创建账户类属性包括姓名name余额balace方法包括存钱save和取钱draw 创建存钱任务创建取钱任务 package com.wz.Thread09;public class Account {String name;double balance;boolean flag false;public Account(String name) {this.name name;}//存钱public synchronized void save(int money){if (flag){//如果存了System.out.println(name的爸爸等待存钱通知);try {wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}else {balancemoney;System.out.println(name的爸爸存了money元);flagtrue;notifyAll();}}public synchronized void draw(int money){if (flag){if (balancemoney){System.out.println(name提醒爸爸存钱);flagfalse;notifyAll();}else {balance-money;System.out.println(name取了moneyyuan);}}else {try {System.out.println(name等待爸爸存钱);wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
package com.wz.Thread09;public class Test {public static void main(String[] args) {Account account new Account(小明);Thread t1 new Thread(() - {while (true){account.draw(500);try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t1.start();Thread t2 new Thread(() - {while (true){account.save(800);try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t2.start();}
} 结果