Summary of Java knowledge points "Effort Part II"

Small chat: This article mainly edits small details or knowledge supplements that Xiaobai feels are important and easy to forget Javawhen . Thinking that it will be very helpful for learning and using Javait , I will record it a little bit. The knowledge points with more content will be written in this column, so here are all small tipsones.

The entire Java language part, I will divide it into "Basic", "Efforts" and "Supplementary". This article is "Effort Part II", because the knowledge point module is relatively large, and the directory structure has been slightly changed from before. The basic content is roughly summarized here, and if there is any follow-up, it will be updated in the article "Supplement".

Currently other parts:

Summary of Java knowledge points "Basic"

Summary of Java knowledge points "Effort Part 1"


1. Abnormal

1.1. Exception Definition

  • Exception Architecture Diagram
    The source of the picture comes from the Internet

The root class of exception is java.lang.Throwable, under which there are two subclasses: java.lang.Errorandjava.lang.Exception

  • Error: serious error Error, an error that cannot be handled, can only be avoided in advance, like a terminal illness.
  • Exception: Indicates an exception. After an exception occurs, the programmer can correct it by means of code, so that the program can continue to run, which must be dealt with .
  • Common Error Examples and Descriptions

Java 虚拟机运行错误(Virtual MachineError)内存不足错误(OutOfMemoryError)类定义错误(NoClassDefFoundError)

These errors indicate that the failure occurred in the virtual machine itself, or when the virtual machine tried to execute the application. The Java virtual machine generally chooses the thread to terminate.

  • Common Exception Examples and Descriptions

RuntimeException (运行时异常)NullPointerException(空指针异常,要访问的变量没有引用任何对象)java.lang.ClassNotFoundException(类不存在异常)ArrayIndexOutOfBoundsException(数组下标越界异常)java.lang.ClassCastException(数据类型转换异常,比如强转失败)FileNotFoundException(文件找不到异常)SQLException(数据库操作异常)ArithmeticException(算术运算异常,比如一个整数除以0)

The difference between exceptions and errors: exceptions can be handled by the program itself, but errors cannot be handled.

  • Common methods for exceptions
method explain
public void printStackTrace() Print the details of the exception (including the type of exception, the cause of the exception, and the location where the exception occurred. In the development and debugging stages, you must use printStackTrace)
public String getMessage() Obtain the reason for the exception (when prompted to the user, it will prompt the cause of the error)
public String toString() Get the exception type and exception description information ( not very useful )
  • Cause of exception
    • After the JVM detects an exception, the JVM will create an exception object according to the cause of the exception. This exception object contains the content . In the getElement method, there is no exception handling logic ( try...catch ), then the JVM will throw the exception object to the main method of the caller of the method to handle the exception
    • After the main method receives the exception object, if there is no exception handling logic, it throws the object to the caller JVM of the main method for processing
    • After the JVM receives the exception, it will print the exception object (content, cause location) in red font on the console, and then the JVM will terminate the currently executing java program, that is, interrupt processing

1.2. Exception handling: try/catch/finally, throw, throws

Five keywords for Java exception handling: try, catch, finally, throw, throws

  • try...catch statement

Capture exceptions, capture targeted statements in Java, and handle exceptions in specified ways

  • grammatical format

    try {
           
           
         //编写可能会出现异常的代码
    } catch(异常类型  e) {
           
           
         //处理异常的代码
         //记录日志/打印异常信息/继续抛出异常
    }
    
  • finally code block

There is some specific code that needs to be executed whether or not an exception occurs. In addition, because the exception will cause the program to jump, some statements cannot be executed. And finally solves this problem. The code stored in the finally code block will be executed no matter whether it is caught or not.

  • Code example: For example, when does the code have to be executed eventually?

    /**
     * 当我们在try语句块中打开了一些物理资源(磁盘文件/网络连接/数据库连接等),我们都得在使用完之后,需要关闭打开的资源
     */
    import java.io.FileNotFoundException;
    
    public class tryCatchFinallyDemo {
           
           
        public static void main(String[] args) {
           
           
            try {
           
           
                read("b.txt");
            } catch (FileNotFoundException e) {
           
           
                //System.out.println("该类型的异常为:" + e.getMessage());
                e.printStackTrace();
            } finally {
           
           
                System.out.println("无论如何,finally的代码都会执行!");
            }
        }
    
        public static void read(String path) throws FileNotFoundException {
           
           
            if(!"a.txt".equals(path)) {
           
           
                //不存在此文件,是一个错误,则抛出异常
                throw new FileNotFoundException("文件不存在!");
            }
    
        }
    }
    
    输出
    java.io.FileNotFoundException: 文件不存在!
    	at study.异常.tryCatchFinallyDemo.read(tryCatchFinallyDemo.java:22)
    	at study.异常.tryCatchFinallyDemo.main(tryCatchFinallyDemo.java:9)
    无论如何,finally的代码都会执行!
    
  • throws statement exception

The keyword throws is used on the method declaration to indicate that the current method does not handle exceptions, but reminds the caller of the method to handle exceptions (throwing exceptions). If the caller still does not process it, it will be handed over to the JVM for processing in the end , and it will process it according to the default processing method

  • declaration format

    修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2{
           
            }
    
  • code example

    public class throwsDemo {
           
           
    	public static void main(String[] args)  throws Exception {
           
           
    		int c = divide(1, 0);
    		System.out.println(c);
    	}
        public static int divide(int a, int b) throws Exception {
           
           
            int c = a / b;	
            return c;		
    	}
    }
    
  • throw throws an exception

Create an exception object. Encapsulate some prompt information (information can be written by yourself)

throw is used in a method to throw an exception object, pass the exception object to the caller, and end the execution of the current method

After throwing an exception through the throw keyword, you also need to use the throws keyword or try...catch to handle the exception.

Note: If throw throws Error, RuntimeException or their subclass exception objects, there is no need to use the throws keyword or try....catch to handle the exception

  • declaration format

    throw new 异常类名(参数);
    
  • Example of use

    public class ThrowDemo {
           
           
    	public static void main(String[] args) /* throws Exception */ {
           
           
    		try {
           
           
    			printfAge(-1);
    		} catch (Exception e) {
           
           
    			System.out.println("该类型的异常为:" + e.getMessage());
    		}
    	}
    	
    	public static void printfAge(int age) throws Exception {
           
           
    		if(age < 0 || age > 200)
    			throw new Exception("年龄不在正确范围内");
    		else
    			System.out.println(age);
    	}
    }
    
    输出
    该类型的异常为:年龄不在正确范围内
    

1.3. Garbage collection mechanism

The Java virtual machine automatically performs garbage collection, and we do not need to actively write recycling instructions

The recovery method is: in addition to waiting for the automatic garbage collection of the Java virtual machine, you can also actively notify the system garbage collector to perform garbage collection

  • Code manual recycling operation

(1) Call the gc() static method of the System class: System.gc();

(2) Call the gc() instance method of the Runtime object: Runtime.getRuntime.gc();

Note: The above two methods can notify the garbage collector to start garbage collection, but it is still uncertain whether to perform garbage collection immediately. In most cases, there is always a certain effect after execution.

Notice:

  1. Although you can control when an object is no longer referenced by any reference variables through the program, you cannot precisely control the timing of Java garbage collection, that is, when you call a method for garbage collection, it does not immediately perform recycling operations
  2. When an object is released in memory, its finalize() method will be called automatically. The finalize() method is an instance method defined in the Object class.
  3. Any Java class can override the finalize () method of the Object class , in which the resources occupied by the object are cleaned up. If the garbage collection is still not performed before the program ends, the finalize() method of the lost reference object will not be called to clean up resources
  4. Only when the program thinks it needs more additional memory, the garbage collector will automatically perform garbage collection
  • Code usage example
class Person {
    
    
    //finalize()被自动调用,说明Person对象已被回收
	@Override
	public void finalize() throws Throwable {
    
    
		System.out.println("Person对象被释放了!");
	}
}

public class 垃圾回收机制Demo {
    
    
	public static void main(String[] args) {
    
    
		//method1();
		method2();
	}
	
	public static void method1() {
    
    
		//不手动介入情况
		Person p = new Person();
		p = null;
		
		for(int i = 1; i <= 10; i ++) {
    
    
			System.out.println("哈哈哈!");
		}
	}
	
	public static void method2() {
    
    
		//手动介入的情况
		Person p = new Person();
		p = null;
		System.gc();
		//Runtime.getRuntime().gc();
		
		for(int i = 1; i <= 10; i ++) {
    
    
			System.out.println("嘿嘿嘿!");
		}
	}
}

2. I流

2.1. IO flow classification system

Mainly refers to using the content under java.iothe package to perform input and output operations. Input is also called reading data, and output is also called writing data.

We can regard this kind of data transmission as a kind of data flow. According to the flow direction and based on the memory, it can be divided into and, 输入inputthat 输出outputis, the flow to the memory is the input flow, and the output flow that flows out of the memory.

  • Data flow: input stream and output stream

    • Input stream : A stream that reads data from 其他设备on to 内存the .

    • output stream : A stream that writes data 内存from to 其他设备on the .

  • Data type: byte stream and character stream

    • Byte stream : A stream of read and write data in units of bytes.

    • Character stream : A stream that reads and writes data in units of characters.

  • The role of data: node flow and process flow

    • Node stream : related to file, pipeline, and array operations.
    • Processing stream : related to buffering, serialization, conversion, and printing operations.

The source of the picture comes from the Internet

The source of the picture comes from the Internet


2.2. Byte stream && FileInputStream input stream

  • Construction method

    method explain
    FileInputStream(File file) Creates a FileInputStream by opening a connection to an actual file , named by a File object in the file system .
    FileInputStream(String name) Creates a FileInputStream by opening a connection to the actual file named by the pathname name in the file system
    • code demo

      public class FileInputStreamConstructor throws IOException {
              
              
          public static void main(String[] args) {
              
              
         	 	// 使用File对象创建流对象
              File file = new File("a.txt");
              FileInputStream fos = new FileInputStream(file);
            
              // 使用文件名称创建流对象
              FileInputStream fos = new FileInputStream("b.txt");
          }
      }
      
  • common method

    method explain
    public void close() closes this input stream and frees any system resources associated with this stream
    public abstract int read() reads the next byte of data from the input stream
    public int read(byte[] b) Read some number of bytes from the input stream and store them into byte array b
  • code demo

    public class FileInputStreamDemo {
    	public static void main(String[] args) throws IOException {
    		//1.创建字节对象
    		FileInputStream in = new FileInputStream("a.txt");	//a.txt的内容是abcde
    		
    		 int i = in.read();
    		 
    		 //将int型转成字节型
    		 byte q = (byte)i;
    		 byte[] a = {q};
    		 //再将字节型转成字符串型输出
    		System.out.println(i + "," + new String(a));	//97, a	第一次输出第一个字节(返回值为ASCII码)和其表示的字符
    		
    		//在循环中不断的调用read方法,并把读取到的数据给i赋值,只要没有读取到-1,说明没有达到文件末尾可以继续读取
    		while((i = in.read()) != -1) {
    			System.out.println((char)i);
    		}
    		//.关闭流释放资源
    		in.close();		
    	}
    }
    

2.3. Byte stream && FileOutputStream output stream

When you create a stream object, you must pass in a file path. Under this path, if there is no such file, it will be created. If there is such a file, the data of this file will be cleared. Write again if you don’t want to clear the original content, you can add the switch of the second keyword true in the position of the construction method

  • Construction method

    method explain
    FileOutputStream(File file) Creates a file output stream to write to the file represented by the specified File object
    FileOutputStream(String name) Create a file output stream to write to a file with the specified name
    public FileOutputStream(File file, boolean append) Creates a file output stream to write to the file represented by the specified File object
    public FileOutputStream(String name, boolean append) Create a file output stream to write to a file with the specified name
    • code demo

      public class FileOutputStreamConstructor throws IOException {
              
              
          public static void main(String[] args) {
              
              
         	 	// 使用File对象创建流对象
              File file = new File("a.txt");
              FileOutputStream fos1 = new FileOutputStream(file);
              FileOutputStream fos2 = new FileOutputStream(file, true);
              // 使用文件名称创建流对象
              FileOutputStream fos3 = new FileOutputStream("b.txt");
              ileOutputStream fos4 = new FileOutputStream("b.txt", true);
          }
      }
      
  • common method

    method explain
    public void close() closes this output stream and frees any system resources associated with this stream
    public void flush() Flushes this output stream and forces any buffered output bytes to be written out
    public int write(byte[] b) Writes b.length bytes from the specified byte array to this output stream
    public void write(byte[] b, int off, int len) Writes len bytes from the specified byte array, starting at offset off, to this output stream
    public abstract void write(int b) output the specified bytes to the stream
  • Code demo

    public class Part0202_字节输出流File0utputStream {
          
          
    	public static void main(String[] args) throws IOException {
          
          
    		//1/创建字节输出流对象
    		FileOutputStream out = new FileOutputStream("a.txt");	//如果文件不存在,则自动创建	
    		//2.写入数据
    		out.write(65);	//运行后文件中会出现“A”;
    		
    		//如果想输出字符串,需要用到String类的getByte()方法
    		byte[] i = "鸢一折纸".getBytes();
    		out.write(i);	//运行后文件中会出现“A鸢一折纸”;
    		System.out.println(Arrays.toString(i));	//输出“鸢一折纸”的字节码:[-16, -80, -46, -69, -43, -37, -42, -67]
    		//out.write("\r".getBytes());
    		//out.write("\n".getBytes());
    		out.write("\r\n".getBytes());
    		
            //数据追加演示,不会清空文件原有的数据
    		FileOutputStream out2 = new  FileOutputStream("a.txt", true);
    		out2.write("夜刀神十香".getBytes());		//A鸢一折纸
    											   //夜刀神十香
    		//3.关闭流释放资源
    		out.close();	
    		out2.close();	
     	}
    }
    

2.4. Character stream && FileReader character input (read) stream

When operating plain text files, it can solve the problem of Chinese garbled characters in byte streams

There may be a slight problem when reading text files using byte streams. That is, when a Chinese character is encountered, the complete character may not be displayed, that is because a Chinese character may occupy multiple bytes of storage. So Java provides some character stream classes to read and write data in units of characters, which are specially used to process text files.

Character stream, only text files can be operated, non-text files such as pictures and videos cannot be operated. Character streams are used when we simply read or write text files; byte streams are used otherwise.

  • Construction method
method explain
FileReader(File file) Creates a new FileReader , given a File object to read
FileReader(String name) Creates a new FileReader , given the name of the file to read
  • common method
common method explain
public void close() closes this stream and releases any system resources associated with this stream
public int read() read a character from the input stream
public int read(char[] cbuf) Read some characters from the input stream and store them into the character array cbuf
  • code example
import java.io.FileReader;
import java.io.IOException;

public class FileReaderDemo {
    
    
	public static void main(String[] args) throws IOException {
    
    
		//创建一个字符流输入对象
		FileReader fr = new FileReader("a.txt");
		
		int i;
		
		while((i = fr.read()) != -1) {
    
    
			System.out.print((char)i);	//虽然读取了一个字符,但是会自动提升为int类型,这里需要转型
		}
		
		fr.close();
	}
}

2.5. Character stream && FileWriter character output (write) stream

  • Construction method
method explain
FileWriter(File file) Creates a new FileWriter , given a File object to read
FileWriter(String name) Creates a new FileWriter , given the name of the file to read
  • common method
common method explain
void write(int c) write a single character
void write(char[] cbuf) write character array
abstract void write(char[] cbuf, int off, int len) Write a certain part of the character array, the start index of the off array, the number of characters written by len
void write(String str) write string
void write(String str, int off, int len) Write a certain part of the string, the start index of the off string, and the number of characters written by len . void flush() refreshes the buffer of the stream
void flush() Flush the stream's buffer
void close() Close this stream, but flush it first
  • Notice

If the character output stream does not call the close method or the flush method, the data will not be written to the file

flushclose 方法的区别
(1)flush() 方法是将数据刷出到文件中去,刷出之后可以继续调用 write() 方法写出
(2)close() 方法的主要功能是关闭流释放资源,同时也具有刷出数据的效果
(3)close() 方法调用结束后,不能再调用 write() 方法写出数据

  • 代码示例
import java.io.FileWriter;
import java.io.IOException;

public class FileWriterDemo {
    
    
	public static void main(String[] args) throws IOException {
    
    
		//创建一个字符流输出对象
		FileWriter fw = new FileWriter("a.txt");
		
		//写一个字符
		fw.write('A');	//A
		
		//写一个字符数组
		char[] arr = {
    
    'H', 'e', 'l', 'l', 'o'};	//AHello
		fw.write(arr);
		
		//写一个字符串
		fw.write("鸢一折纸");	//AHello鸢一折纸
		
		//写一个字符串的一部分
		String str = "你好,我的世界!"; 	//AHello鸢一折纸我的世界
		fw.write(str, 3, 4);	//(字符串名称, 起始索引, 字符串个数)
		
		fw.flush();
		fw.close();
	}
}

2.6. 处理流 && 节点流

  • 认识举例: IO流读取转换文件 utf8 编码操作(涉及以下IO类)

    • FileInputStream:文件操作——节点流
    • InputStreamReader:转换操作 —— 处理流
  • InputStreamReader 构造方法

方法 解释
InputStreamReader(InputStream in) 创建一个使用默认字符集的 InputStreamReader
InputStreamReader(InputStream in, String charsetName) 建使用指定默认字符集的 InputStreamReader
  • 代码演示
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class InputStreamRreaderDemo {
    
    
	public static void main(String[] args) throws IOException {
    
    
		//我这里演示按指定的编码表读写数据
		FileInputStream fin = new FileInputStream("a.txt");
		InputStreamReader isr = new InputStreamReader(fin, "utf-8");	//文件a.txt的编码表为UTF-8(默认的是GDK)
		BufferedReader br = new BufferedReader(isr);
		
		String i;
		while((i = br.readLine()) != null) {
    
    
			System.out.println(i);
		}
		
		br.close();
	}
}

2.7. print 打印流

平时我们在控制台打印输出,是调用 print 方法和 println 方法完成的,这两个方法都来自于 java.io.PrintStream 类,该类能够方便地打印各种数据类型的值,是一种便捷的输出方式。

  • 构造方法
构造方法 解释
public PrintStream(String fileName) 使用指定的文件名创建一个新的打印流
PrintStream ps = new PrintStream("ps.txt");
  • 改变打印流向

System.out 就是 PrintStream 类型的,只不过它的流向是系统规定的,打印在控制台上。不过,既然是流对象,我们就可以改变它的流向。

  • 代码演示
public class PrintDemo {
    
    
    public static void main(String[] args) throws IOException {
    
    
		// 调用系统的打印流,控制台直接输出aaa
        System.out.println("aaa");
      
		// 创建打印流,指定文件的名称
        PrintStream ps = new PrintStream("a.txt");
      	
      	// 设置系统的打印流流向,输出到a.txt
        System.setOut(ps);
      	// 调用系统的打印流
        System.out.println("bbb");	//控制台不再输出bbb,而是打印在了a.txt文件中
    }
}

a.txt文件中显示:
aaa

3. 序列化

3.1. 认识序列化

对象的序列化 (Serializable) 是指将一个 Java 对象转换成一个 I/O 流中字节序列的过程,即用一个字节序列可以表示一个对象,该字节序列包含该 对象的数据对象的类型对象中存储的属性 等信息。字节序列写出到文件之后,相当于文件中 持久保存 了一个对象的信息。

需要序列化的类一定要继承 Serializable 接口

  • 使用示例
class Fruit implements Serializable {
    
    
    // 加入序列版本号
    private static final long serialVersionUID = 1L;
    private final String name;
    private final String color;

    public Fruit(String name, String color) {
    
    
        this.name = name;
        this.color = color;
    }

    @Override
    public String toString() {
    
    
        return "Fruit{" +
                "name='" + name + '\'' +
                ", color='" + color + '\'' +
                '}';
    }
}

public class 序列化 {
    
    
    public static void main(String[] args) throws IOException {
    
    
        Fruit watermelon = new Fruit("西瓜", "red");
        Fruit banana = new Fruit("香蕉", "yellow");
        Fruit grape = new Fruit("葡萄", "purple");
        Fruit coco = new Fruit("椰子", "write");
        ArrayList<Fruit> fruits = new ArrayList<>();
        fruits.add(watermelon);
        fruits.add(banana);
        fruits.add(grape);
        fruits.add(coco);

        outPut(fruits);

        inPut();

    }

    private static <E> void outPut(E fruits)  {
    
    
        try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src\\study\\IO流\\字节流\\a.txt"))) {
    
    
            oos.writeObject(fruits);
        }catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }

    private static void inPut() {
    
    
        try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream("src\\study\\IO流\\字节流\\a.txt"))) {
    
    
            ArrayList<Fruit> list = (ArrayList<Fruit>)ois.readObject();
            list.forEach(System.out::println);
        } catch (IOException | ClassNotFoundException e) {
    
    
            e.printStackTrace();
        }
    }
}

输出
Fruit{
    
    name='西瓜', color='red'}
Fruit{
    
    name='香蕉', color='yellow'}
Fruit{
    
    name='葡萄', color='purple'}
Fruit{
    
    name='椰子', color='write'}

3.2. serialVersionUID(序列ID)

Serializable 接口给需要序列化的类,提供了一个序列版本号。serialVersionUID 该版本号的目的在于验证序列化的对象和对应类是否版本匹配

serialVersionUID 适用于 java 序列化机制。简单来说,JAVA 序列化的机制是通过判断类的 serialVersionUID 来验证的版本一致的。在进行反序列化时,JVM 会把传来的字节流中的 serialVersionUID 于本地相应实体类的 serialVersionUID 进行比较。如果相同说明是一致的,可以进行反序列化,否则会出现反序列化版本一致的异常,即是 InvalidCastException

  • 具体序列化的过程

序列化操作时会把系统当前类的 serialVersionUID 写入到序列化文件中,当反序列化时系统会自动检测文件中的 serialVersionUID,判断它是否与当前类中的 serialVersionUID 一致。如果一致说明序列化文件的版本与当前类的版本是一样的,可以反序列化成功,否则就失败;

  • serialVersionUID有两种显示的生成方式

(1)是默认的1L,比如:private static final long serialVersionUID = 1L;

(2)是根据包名,类名,继承关系,非私有的方法和属性,以及参数,返回值等诸多因子计算得出的,极度复杂生成的一个64位的哈希字段。基本上计算出来的这个值是唯一的。比如:private static final long serialVersionUID = xxxxL;

当实现 java.io.Serializable 接口中没有显示的定义 serialVersionUID 变量的时候,JAVA序列化机制会根据Class自动生成一个serialVersionUID 作序列化版本比较用,这种情况下,如果Class文件(类名,方法明等)没有发生变化(增加空格,换行,增加注释等等),就算再编译多次,serialVersionUID 也不会变化的。

如果我们不希望通过编译来强制划分软件版本,即实现序列化接口的实体能够兼容先前版本,就需要自己显示的定义一个 serialVersionUID,类型为 long 的变量。不修改这个变量值的序列化实体,都可以相互进行序列化和反序列化。


4. 多线程

4.1. 线程与进程

进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。

线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程

扩充:在java中,每次程序运行至少启动2个线程。一个是 main 线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个 JVM 其实在就是在操作系统中启动了一个进程。

  • 线程调度

分时调度:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

抢占式调度:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度

  • 抢占式调度详解

大部分操作系统都支持多进程并发运行,现在的操作系统几乎都支持同时运行多个程序。比如:现在我们上课一边使用编辑器,一边使用录屏软件,同时还开着画图板,dos窗口等软件。此时,这些程序是在同时运行,”感觉这些软件好像在同一时刻运行着“。

实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。

  • 线程的6种基本状态
状态名称 说明
NEW 初始状态,线程被构建,但是还没有调用start()方法
RUNNABLE 运行状态,Java线程将操作系统中的就绪和运行两种状态笼统地称作“运行中”
BLOCKED 阻塞状态,表示线程阻塞于锁
WAITING 等待状态,表示线程进入等待状态,进人该状态表示当前线程需要等待其他线程做出一些特定动作(通知或中断)
TIME_WAITING 超时等待状态,该状态不同于WAITING,它是可以在指定的时间自行返回的
TERMINATED 终止状态,表示当前线程已经执行完毕

4.2. Thread

  • 执行步骤
  1. 创建一个 Thread 线程类的子类(子线程),同时重写 Thread 类的 run() 方法;
  2. 创建该子类的实例对象,并通过调用 start() 方法启动线程
  • 构造方法
构造方法 解释
public Thread() 分配一个新的线程对象
public Thread(String name) 分配一个指定名字的新的线程对象
public Thread(Runnable target) 分配一个带有指定目标新的线程对象
public Thread(Runnable target,String name) 分配一个带有指定目标新的线程对象并指定名字
  • 常用方法
方法 解释
public String getName() 获取当前线程名称
public void start() 导致此线程开始执行; Java虚拟机调用此线程的run方法
public void run() 此线程要执行的任务在此处定义代码
public static void sleep(long millis) 线程休眠。使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)
public static native void yield() 线程让步。与sleep不同它不会阻塞线程,而是让系统的调度器重新调度一次。与当前线程优先级相同或者更高的线程可以获得执行的机会。
public final void join() 线程插队。调用的线程将被阻塞,直到被调用了 join() 方法加入的线程执行完成后它才会继续运行。
public static Thread currentThread() 返回对当前正在执行的线程对象的引用
  • 代码示例
//创建一个Thread线程类的子类
class Mythreads extends Thread {
    
    
	//创建子类线程有参构造方法
	public Mythreads(String name) {
    
    
		// TODO Auto-generated constructor stub
		super(name);
	}

	//重写Thread类的run()方法
	public void run() {
    
    
		int i = 0;
		while(i ++ < 100) {
    
    	
			//currentThread()方法得到当前线程的实例对象,然后调用getName()方法获取到线程名称。接下来,通过继承Thread类的方式来实现多线程
			System.out.println(Mythreads.currentThread().getName()
					 + "的run()方法在运行");
		}
	}
}

public class ThreadDemo {
    
    
	public static void main(String[] args) {
    
    
		//创建Mythreads 实例对象
		Mythreads thread1 = new Mythreads("一号");
		//调用start()方法启动线程
		thread1.start();
		
		//创建另一个线程并启动
		Mythreads thread2 = new Mythreads("二号");
		thread2.start();
		
	}
}

4.3. Runnable

  • 执行步骤
  1. 定义 Runnable 接口的实现类,并重写该接口的 run() 方法,该 run() 方法的方法体同样是该线程的线程执行体。
  2. 创建 Runnable 接口的实现类对象;
  3. 并以此实例作为 Thread 的参数来创建 Thread 对象,该 Thread 对象才是真正的线程对象。
  4. 调用线程对象的 start() 方来启动线程。

实际上所有的多线程代码都是通过运行 Thread 的 start() 方法来运行的。因此,不管是继承 Thread 类还是实现 Runnable 接口来实现多线程,最终还是通过 Thread 的对象的 API 来控制线程的,熟悉 Thread 类的 API 是进行多线程编程的基础。

注意:因为Runnable接口只有一个抽象run()方法,可以用 Lambda 表达式

  • 代码示例
public class RunnableDemo {
    
    
    public static void main(String[] args) {
    
    
        Thread thread1 = new Thread(() -> {
    
    
            int i = 50;
            while (i-- > 0) {
    
    
                System.out.println(Thread.currentThread().getName() + "正在运作!");
            }
        }, "1号");

        Thread thread2 = new Thread(() -> {
    
    
            int i = 50;
            while (i-- > 0) {
    
    
                System.out.println(Thread.currentThread().getName() + "正在运作!");
            }
        }, "2号");

        thread1.start();
        thread2.start();
    }
}
  • Thread和Runnable的区别

如果一个类继承 Thread,则不适合资源共享。但是如果实现了 Runable 接口的话,则很容易的实现资源共享。

  • 实现 Runnable 接口比继承 Thread 类所具有的优势
    • 适合多个相同的程序代码的线程去共享同一个资源
    • 可以避免 java 中的单继承的局限性
    • 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立
    • 线程池只能放入实现 RunableCallable 类线程,不能直接放入继承 Thread 的类

4.4. Callable

通过 Thread 类和 Runnable 接口实现多线程时,需要重写 run() 方法,但是由于该方法没有返回值,因此无法从多个线程中获取返回结果

为了解决这个问题,从 JDK 5 开始,Java 提供了一个 Callable 接口,来满足这种既能创建多线程又可以有返回值的需求

通过 Callable 接口实现多线程的方式与 Runnable 接口实现多线程的方式一样,都是通过 Thread 类的有参构造方法传人 Runnable 接口类型的参数来实现多线程,不同的是,这里传入的是 Runnable 接口的子类 FutureTask 对象作为参数,而 FutureTask 对象中则封装带有返回值的 Callable 接口实现类

  • 执行步骤
  1. 创建一个 Callable 接口的实现类,同时重写 Callable 接口的 call() 方法;
  2. 创建 Callable 接口的实现类对象;
  3. 通过 FutureTask 线程结果处理类的有参构造方法来封装 Callable 接口实现类对象;
  4. 使用参数为 FutureTask 类对象的 Thread 有参构造方法创建 Thread 线程实例;
  5. 调用线程实例的 start() 方法启动线程。
  • 代码示例
class MyCallable implements Callable {
    
    
    @Override
    public Object call() throws Exception {
    
    
        int i = 50;
        while (i-- > 0) {
    
    
            System.out.println(Thread.currentThread().getName()
                    + "的run()方法在运行");
        }
        return i;
    }
}

public class CallableDemo {
    
    
    public static void main(String[] args) {
    
    
        //创建Callable接口的实现类对象;
        MyCallable myCallable = new MyCallable();

        //使用FutureTask封装Callable接口
        FutureTask<Object> ft1 = new FutureTask<Object>(myCallable);
        //使用Thread (Runnable target, String name)构造方法创建线程对象
        Thread thread1 = new Thread(ft1, "1号");

        FutureTask<Object> ft2 = new FutureTask<Object>(myCallable);
        Thread thread2 = new Thread(ft2, "2号");

        thread1.start();
        thread2.start();
    }
}

5. 线程安全

5.1. synchronized

在 Java 中 synchronized 关键字一直都是元老级的角色,以前经常被称为“重量级锁 ”。但是,在JavaSE 1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的 偏向锁轻量级锁 以及其它各种优化之后变得在某些情况下并不是那么繁重了,当然效率也高了很多。synchronized的底层实现主要依靠 Lock-Free 的队列,基本思路是 自旋后阻塞竞争切换后继续竞争锁稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。

Java中每一个对象都可以作为锁,这是 synchronized 实现同步的基础:

  • 普通同步方法,锁是当前实例对象
  • 静态同步方法,锁是当前类的class对象
  • 同步方法块,锁是括号里面的对象
  • 同步代码块
class MyRunnable implements Runnable {
    
    
    private int tickets = 20;
    //定义一个任意对象,用作同步代码块的锁
    Object lock = new Object();
    @Override
    public void run() {
    
    
        while (true) {
    
    
            synchronized (lock) {
    
    
                if (tickets > 0) {
    
    
                    try {
    
    
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在发售第" + (tickets--) + "票");
                } else {
    
    
                    break;
                }
            }
        }
    }
}

public class Demo {
    
    
    public static void main(String[] args) {
    
    
        MyRunnable runnable = new MyRunnable();
        new Thread(runnable, "01号窗口").start();
        new Thread(runnable, "01号窗口").start();
        new Thread(runnable, "01号窗口").start();
    }
}
  • 同步方法

语法

[修饰符] synchronized 返回值类型 方法名([参数1, ……]){
    
    }

使用举例

class MyRunnable implements Runnable {
    
    
    private int tickets = 20;
    @Override
    public void run() {
    
    
        while (true) {
    
    
            locked();
            if(tickets <= 0) {
    
    
                break;
            }
        }
    }
    private synchronized void locked() {
    
    
        if (tickets > 0) {
    
    
            try {
    
    
                Thread.sleep(100);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在发售第" + tickets-- + "张票");
        }
    }
}

public class 同步代码块 {
    
    
    public static void main(String[] args) {
    
    
        MyRunnable runnable = new MyRunnable();

        new Thread(runnable, "01号窗口").start();
        new Thread(runnable, "01号窗口").start();
        new Thread(runnable, "01号窗口").start();
    }
}

5.2. ReentrantLock同步锁

synchronized 同步代码块和同步方法使用一种封闭式的锁机制,使用起来非常简单,也能够解决线程同步过程中出现的线程安全问题,但也有一些限制,例如它无法中断一个正在等候获得锁的线程,也无法通过轮询得到锁,如果不想等下去,也就没法得到锁。

JDK 5 开始, Java 增加了一个功能更强大的 ReentrantLock 锁。ReentrantLock 锁与 synchronized 隐式锁在功能上基本相同,其最大的优势在于 ReentrantLock 锁可以让某个线程在持续获取同步锁失败后返回,不再继续等待,另外 ReentrantLock 锁在使用时也更加灵活

锁【locked.lock】必须紧跟try代码块,且 unlock 要放到 finally代码块 第一行。

  • 方法
方法 解释
public void lock() 加同步锁
public void unlock() 释放同步锁
  • 使用示例
class SaleThread implements Runnable {
    
    
    private int tickets = 20;
    //定义一个Lock锁对象,用作同步代码块的锁
    private final Lock locked = new ReentrantLock();

    @Override
    public void run() {
    
    
        while (true) {
    
    
            locked.lock();
            if (tickets > 0) {
    
    
                try {
    
    
                    Thread.sleep(100);
                    System.out.println(Thread.currentThread().getName() + "正在发售第" + tickets-- + "张票");
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                } finally {
    
    
                    locked.unlock();
                }
            } else {
    
    
                //执行完代码块后进行释放锁
                locked.unlock();
                break;
            }
        }
    }
}

public class Demo {
    
    
    public static void main(String[] args) {
    
    
        SaleThread thread = new SaleThread();

        new Thread(thread, "窗口1").start();
        new Thread(thread, "窗口2").start();
        new Thread(thread, "窗口3").start();
        new Thread(thread, "窗口4").start();
    }
}

6. 网络编程

6.1. UDP通信协议

用户数据报协议(User Datagram Protocol)。UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。

由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输例如视频会议都使用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。

但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议。

特点:数据被限制在64kb以内,超出这个范围就不能发送了。

数据报(Datagram):网络传输的基本单位。

  • InteAddress获取主机地址

    在JDK中提供了一个与IP地址相关的InetAddress类,该类用于封装一个IP地址,并提供了一系列与IP地址相关的方法。

    • 常用方法
    方法 说明
    InetAddress getByName(String host) 获取给定主机名的的IP地址,host参数表示指定主机
    InetAddress getLocalHost() 获取本地主机地址
    String getHostName() 获取本地IP地址的主机名
    boolean isReachable(int timeout) 判断在限定时间内指定的IP地址是否可以访问
    String getHostAddress() 获取字符串格式的原始IP地址
  • DatagramPacket(集装箱)

    创建发送端和接收端的DatagramPacket对象时,使用的构造方法有所不同。接收端的构造方法只需要接收一个字节数组来存放接收到的数据。发送端的构造方法不但要接存放了发送数据的字节数组,还需要指定发送端IP地址和端口号。

    作用:DatagramPacket 用于封装UDP通信中的数据

    • 常用构造方法
    方法 解释
    DatagramPacket(byte[] buf, int length) 指定了封装数据的字节数组和数据的大小,没有指定IP地址和端口号,这样的对象只能用于接收端。
    DatagramPacket(byte[] buf, int offset, int length) 该构造方法.与第1个构造方法类似同样用于接收端,只不过在第1个构造方法的基础上,增加了一个数, 该参数用于指定一个数组中发送数据的偏移量为offset,即从offset位 置开始发送数据。
    DatagramPacket(byte[] buf,int length,InetAddress addr,int port) 该构造方法不仅指定了封装数据的字节数组和数据的大小,还指定了数据包的目标IP地址(addr)和端口号(port) 。该对象通常用于发送端。
    DatagramPacket(byte[] buf, int offset, int length, InetAddress addr, int port) 该构造方法与第3个构造方法类似同样用于发送端,只不过在第3个构造方法的基础上,增加了一个offset参数,该参数用于指定一个数组中发送数据的偏移量为offset,即从offset位置开始发送数据。
    • 常用方法
    方法 解释
    InetAddress getAddress() 该方法用于返回发送端或者接收端的IP地址,如果是发送端的DatagramPacket对象,就返回接收端的IP地址;反之,就返回发送端的IP地址
    int getPort() 该方法用于返回发送端或者接收端的端口号,如果是发送端的DatagramPacket对象,就返回接收端的端口号;反之,就返回发送端的端口号
    byte[] getData() 该方法用于返回将要接收或者将要发送的数据,如果是发送端的DatagramPacket对象,就返回将要发送的数据; 反之,就返回接收到的数据
    int getLength() 该方法用于返回接收或者将要发送数据的长度,如果是发送端的DatagramPacket对象,就返回将要发送的数据长度;反之,就返回接收到数据的长度
  • DatagramSocket(码头)

    • 常用构造方法
    方法 解释
    DatagramSocket() 该构造方法用于创建发送端的 DatagramSocket 对象,并没有指定端口号,此时,系统会分配一个没有被其它网络程序所使用的端口号。
    DatagramSocket(int port) 该构造方法既可用于创建接收端的 DatagramSocket 对象,也可以创建发送端的 DatagramSocket 对象,在创建接收端的 DatagramSocket 对象时,必须要指定一个端口号,这样就可以监听指定的端口。
    DatagramSocket(int port, InetAddress addr) (这个不怎么用)该构造方法在创建 DatagramSocket 时,不仅指定了端口号还指定了相关的 IP 地址,这种情况适用于计算机上有多块网卡的情况,可以明确规定数据通过哪,块网卡向外发送和接收哪块网卡的数据。
    • 常用方法
    方法 解释
    void receive(DatagramPacket p) 该方法用于接收 DatagramPacket 数据报,在接收到数据之前会直处于阻塞状态,如果发送消息的长度比数据报长,则消息将会被截取
    void send(DatagramPacket p) 该方法用于发送 DatagramPacket 数据报,发送的数据报中包含将要发送的数据、数据的长度、远程主机的 IP 地址和端口号
    void close() 关闭当前的 Socket ,通知驱动程序释放为这个 Socket 保留的资源
  • 使用示例

public class TestReceive {
    
    
    public static void main(String[] args) throws Exception {
    
    
        System.out.println("接收端开启!");
        System.out.println("接收端开启!");
        System.out.println("接收端开启!");

        DatagramSocket socket = new DatagramSocket(6666);

        DatagramPacket packet = new DatagramPacket(new byte[1024], 1024);

        socket.receive(packet);

        InetAddress address = packet.getAddress();

        byte[] data = packet.getData();
        int len = packet.getLength();
        int port = packet.getPort();

        System.out.println("ip为" + address + "端口号为" + port + "的对象正在向您发送信息");
        System.out.println("发送的信息为:");
        System.out.println(new String(data));

    }
}


public class TestSend {
    
    
    public static void main(String[] args) throws Exception {
    
    
        System.out.println("发送端开启!");
        System.out.println("发送端开启!");
        System.out.println("发送端开启!");

        DatagramSocket socket = new DatagramSocket(5555);

        byte[] bytes = "你好呀".getBytes();

        DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("127.0.0.1"), 6666);

        socket.send(packet);

        System.out.println("发送成功!");

        socket.close();
    }
}
//注意:先开启接收端,在开启发送端

6.2. TCP通信协议

传输控制协议 (Transmission Control Protocol)。TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。

在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”。

  • 三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。

    第一次握手,客户端向服务器端发出连接请求,等待服务器确认。
    第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求。
    第三次握手,客户端再次向服务器端发送确认信息,确认连接。

  • 完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛,例如下载文件、浏览网页等。

TCP通信能实现两台计算机之间的数据交互,通信的两端,要严格区分为客户端(Client)与服务端(Server)。

  • 服务端程序,需要事先启动,等待客户端的连接。
  • 客户端主动连接服务器端,连接成功才能通信。服务端不可以主动连接客户端。

在Java中,提供了两个类用于实现TCP通信程序:

  • 客户端:java.net.Socket 类表示。创建 Socket 对象,向服务端发出连接请求,服务端响应请求,两者建立连接开始通信。

  • 服务端:java.net.ServerSocket 类表示。创建 ServerSocket 对象,相当于开启一个服务,并等待客户端的连接。

  • Socket类

    该类实现客户端套接字,套接字指的是两台设备之间通讯的端点。

    • 构造方法
    方法 解释
    public Socket(String host, int port) 创建套接字对象并将其连接到指定主机上的指定端口号。如果指定的 host 是 null ,则相当于指定地址为回送地址。
    • 小贴士

    回送地址(127.x.x.x) 是本机回送地址(Loopback Address),主要用于网络软件测试以及本地机进程间通信,无论什么程序,一旦使用回送地址发送数据,立即返回,不进行任何网络传输。

    • 成员方法
    方法 解释
    public InputStream getInputStream() 返回此套接字的输入流。如果此 Scoket 具有相关联的通道,则生成的 InputStream 的所有操作也关联该通道。关闭生成的 InputStream 也将关闭相关的 Socket
    public OutputStream getOutputStream() 返回此套接字的输出流。如果此 Scoket 具有相关联的通道,则生成的 OutputStream 的所有操作也关联该通道。关闭生成的 OutputStream 也将关闭相关的 Socket
    public void close() 关闭此套接字。一旦一个 socket 被关闭,它不可再使用。关闭此 socket 也将关闭相关的 InputStreamOutputStream
    public void shutdownOutput() Disables output streams for this socket. Any previously written data will be sent, subsequently terminating the output stream
    • code demo
    public class TCP_Client {
          
          
    	/*
    	 * 客户端代码
    	 */
    	public static void main(String[] args) throws Exception {
          
          
    		//1.创建客户端Socket对象(发送请求)
    		Socket socket = new Socket("127.0.0.1", 8888);
    		
    		//2.获取用于数据传输的的I/O流
    		OutputStream os = socket.getOutputStream();
    		
    		//3.将数据写出
    		os.write("你好,陌生人。".getBytes());
    		
    		//4.客户端读取服务端回写的数据
    		InputStream is = socket.getInputStream();
    		byte[] bys = new byte[1024];
    		int len = is.read(bys);
    		String str = new String(bys, 0, len);
    		System.out.println("客户端发回的信息是:" + str);
    		
    		socket.close();
    	}
    }
    
  • Server class

    This class implements a server socket, an object that waits for requests across the network.

    • Construction method
    method explain
    public ServerSocket(int port) When using this construction method to create a ServerSocket object, it can be bound to a specified port number, and the parameter port is the port number.
    • member method
    method explain
    public Socket accept() Listen for and accept connections, and return a new Socket object for communicating with the client. This method blocks until a connection is established.
  • code demo

    • TestClient class
    public class Client {
          
          
        public static void main(String[] args) throws Exception {
          
          
            while (true) {
          
          
                //创建IO流对象
                Socket socket = new Socket("127.0.0.1", 5555);
                try {
          
          
                    InputStream is = socket.getInputStream();
                    OutputStream os = socket.getOutputStream();
                    //使用打印流对os进行包装
                    PrintStream ps = new PrintStream(os);
                    Scanner sc = new Scanner(System.in);
                    String str = sc.nextLine();
                    ps.println(str);
                    //使用高效率流和转换流对is进行包装
                    BufferedReader br = new BufferedReader(new InputStreamReader(is));
                    String s = br.readLine();
                    System.out.println("接收到服务端会写的信息为:" + s);
                    socket.close();
                }catch (Exception e) {
          
          
                    e.printStackTrace();
                }
            }
        }
    }
    
  • TestSever class

    public class Sever {
          
          
        public static void main(String[] args) throws Exception {
          
          
            ServerSocket sever = new ServerSocket(5555);
            System.out.println("服务器启动!!!");
    
            while (true) {
          
          
                //服务端响应客户端发送过来的请求,用Socket对象接收
                Socket socket = sever.accept();
                System.out.println("接收到请求,响应!");
                new Thread() {
          
          
                    @Override
                    public void run() {
          
          
                        try {
          
          
                            //创建I/O流对象
                            InputStream is = socket.getInputStream();
                            OutputStream os = socket.getOutputStream();
                            //使用高效率流和转换流对is进行包装
                            BufferedReader br = new BufferedReader(new InputStreamReader(is));
                            //使用打印流对os进行包装
                            PrintStream ps = new PrintStream(os);
                            String line = br.readLine();
                            System.out.println("服务器接收到的信息为:" + line);
                            ps.println("Yes, man.");
                            socket.close();
                        } catch (Exception e) {
          
          
                            e.printStackTrace();
                        }
                    }
                }.start();
            }
        }
    }
    

    essay

insert image description here

Guess you like

Origin blog.csdn.net/m0_48489737/article/details/127700973