log
目录
Java Base
public class MyPublicClass {
public int publicField; // 公共字段,任何类都可以访问
protected int protectedField; // 受保护字段,同一包或子类可以访问
private int privateField; // 私有字段,只有本类可以访问
void defaultField() {} // 默认(包级私有),同一包内的类可以访问
}
用final修饰class可以阻止被继承:
用final修饰method可以阻止被子类覆写:
用final修饰field可以阻止被重新赋值:
用final修饰局部变量可以阻止被重新赋值:
一个.java文件只能包含一个public类,但可以包含多个非public类。如果有public类,文件名必须和public类的名字相同。
整形类型:
byte: 1byte
short: 2bytes
int: 4bytes
long: 8bytes
float: 4bytes2
double:8bytes
Integer:
Integer可以为null, int不能
用于数据库字段,或API参数可返回。因为它们可能为null
数值比较用:a.equals(b), 不能用a==b
自动装箱(Autoboxing)
自动拆箱(unboxing)
Integer a = 10; 相当于 Integer.valueOf(10)
int b = a; 相当于 b = a.intValue()
int[] numbers = {1,2,3,4,5}
String[] names = {}
基础数据类型boolean byte short int long float double char
基础数据类型在栈上,类数据类型在堆上
----------
String str = "hi," + "lao" + "zhang";
str1.equals(str2)
格式化
String str = String.format("my name is %s, my ages is %d", "wang", 20);
String str = "hello";
str = "world"; 堆上新增加一个区域保存world
原来的字符串"hello"还在堆上,只是我们无法通过变量str访问它而已。
因此,字符串的不可变是指字符串内容不可变
String str = null; 不指向堆上的区域
String: 是不可变类型,函数传值是一个副本,下面两个是可变类型
StringBuffer: 字符串拼接时synchronized保障线程安全
StringBuilder: 和StringBuffer一样继承自AbstractStringBuilder
函数只是传值
String str = "laowang";
change(str)
System.out.println(str); 输出还是laowang
public static void change(str) {
str = "xiaowang";
}
StringBuffer sb = new StringBuffer("xiaowang");
change(sb)
System.out.println(sb);
因为不创建副本,StringBuffer性能优于String, 但不如StringBuilder
StringBuffer 线程安全
StringBuilder线程不安全
String类是final类型,不可继承
public final class String {
}
String不可变的好处如下。
·只有当字符串是不可变的,字符串常量池才能实现,字符串池的实现可以在运行时节约很多堆空间,因为不同的字符串变量都指向池中的同一个字符串;
可以避免一些安全漏洞,比如在 Socket 编程中,主机名和端口都是以字符串的形式传入,因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞;
多线程安全,因为字符串是不可变的,所以同一个字符串实例可以被多个线程共享,保证了多线程的安全性,
适合做缓存的 key,因为字符串是不可变的,所以在它创建的时候哈希值就被缓存了,不需要重新计算速度更快,所以字符串很适合作缓存的中的 key。
int[] intarr = new int[10];
int[] intarr1 = {1, 2, 3, 4, 5};
int len = intarr.length(); 数组长度
是不可变数组,要管理可变数组使用ArrayList
java泛型不支持基础类型int, 需要用对象类型Integer
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
Java中主要的日期类型包括java.util.Date、
java.time包下的新API(如LocalDate、LocalTime、LocalDateTime、Instant等)
以及java.util.Calendar类,分别适用于不同场景的日期时间处理需求。
----------------
异或运算
n = 0 ^ 0; // 0
n = 0 ^ 1; // 1
n = 1 ^ 0; // 1
n = 1 ^ 1; // 0
// 多行字符串
String s = """
SELECT * FROM
users
WHERE id > 100
ORDER BY name DESC
""";
--------------
double d = 3.1415926;
System.out.printf("%.2f\n", d); // 显示两位小数3.14
System.out.printf("%.4f\n", d); // 显示4位小数3.1416
Scanner scanner = new Scanner(System.in);
System.out.print("Input your name: ");
String name = scanner.nextLine();
System.out.print("Input your age: ");
int age = scanner.nextInt(); // 读取一行输入并获取整数
数组和ArrayList都可以用枚举
int[] ns = { 1, 4, 9, 16, 25 };
for (int n : ns) {
System.out.println(n);
}
int[] ns = { 28, 12, 89, 73, 65, 18, 96, 50, 8, 36 };
Arrays.sort(ns);
System.out.println(Arrays.toString(ns));
---------------------
class Student extends Person {}
class Action inplements ActionInterface {}
final class 禁止继承
sealed指定三个类可继承
public sealed class Shape permits Rect, Circle, Triangle {
}
强制转型
Object obj = "hello";
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.toUpperCase());
}
也可以这样写:
if (obj instanceof String s) {
System.out.println(s.toUpperCase());
}
抽象类
class Person {
public abstract void run();
}
如果一个抽象类没有字段,所有方法全部都是抽象方法:
就可以把该抽象类改写为接口:interface。
interface Person {
void run();
String getName();
}
类只能继承自一个抽象类
类可以继承自多个接口
一个interface可以继承自另一个interface。interface继承自interface使用extends
在接口中,可以定义default方法
interface Person {
String getName();
default void run() {
System.out.println(getName() + " run");
}
}
interface是可以有静态字段的,并且静态字段必须为final类型:
public interface Person {
public static final int MALE = 1;
public static final int FEMALE = 2;
}
匿名内部类,实现了Runable接口并实例化
class Outer {
private String name;
Outer(String name) {
this.name = name;
}
void asyncHello() {
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hello, " + Outer.this.name);
}
};
new Thread(r).start();
}
}
-------------------
cp 就是classpath
java -classpath .;C:\work\project1\bin;C:\shared abc.xyz.Hello
java -cp .;C:\work\project1\bin;C:\shared abc.xyz.Hello
在linux中,路径用:分开
自带“依赖关系”的class容器就是模块module
src/module-info.java 表明本模块依赖
module hello.world {
requires java.base; // 可不写,任何模块都会自动引入java.base
requires java.xml;
}
从Java 9开始,原有的Java标准库已经由一个单一巨大的rt.jar分拆成了几十个模块,这些模块以.jmod扩展名标识,可以在$JAVA_HOME/jmods目录下找到它们:
java.base.jmod
java.compiler.jmod
java.datatransfer.jmod
java.desktop.jmod
---------------
枚举和枚举类
enum Weekday {
SUN, MON, TUE, WED, THU, FRI, SAT;
}
public class Weekday {
public static final int SUN = 0;
public static final int MON = 1;
public static final int TUE = 2;
public static final int WED = 3;
public static final int THU = 4;
public static final int FRI = 5;
public static final int SAT = 6;
}
java14开始:
记录类record:
定义class时使用final,无法派生子类;
每个字段使用final,保证创建实例后无法修改任何字段。
record Point(int x, int y) {}
相当于下面代码:
final class Point extends Record {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int x() {
return this.x;
}
public int y() {
return this.y;
}
public String toString() {
return String.format("Point[x=%s, y=%s]", x, y);
}
public boolean equals(Object o) {
...
}
public int hashCode() {
...
}
}
-------------
Java规定:
1.必须捕获的异常,包括Exception及其子类,但不包括RuntimeException及其子类,这种类型的异常称为Checked Exception。
2.不需要捕获的异常,包括Error及其子类,RuntimeException及其子类。
下面代码编译时会抛出异常:
public class Main {
public static void main(String[] args) {
byte[] bs = toGBK("中文");
System.out.println(Arrays.toString(bs));
}
static byte[] toGBK(String s) {
return s.getBytes("GBK");
}
}
如下编写可以通过编译:
static byte[] toGBK(String s) throws UnsupportedEncodingException {
return s.getBytes("GBK");
}
可以在main中捕获异常:
public class Main {
public static void main(String[] args) {
try {
byte[] bs = toGBK("中文");
System.out.println(Arrays.toString(bs));
} catch (UnsupportedEncodingException e) {
System.out.println(e);
}
}
static byte[] toGBK(String s) throws UnsupportedEncodingException {
return s.getBytes("GBK");
}
}
让main抛出异常
public static void main(String[] args) throws Exception {
byte[] bs = toGBK("中文");
System.out.println(Arrays.toString(bs));
}
同时处理两个异常:
try {
process1();
process2();
process3();
} catch (IOException | NumberFormatException e) {
// IOException或NumberFormatException
System.out.println("Bad input");
} catch (Exception e) {
System.out.println("Unknown error");
}
}
通过printStackTrace()可以打印出方法的调用栈
try {
process1();
} catch (Exception e) {
e.printStackTrace();
}
多级抛出异常,方便跟踪异常栈
public class Main {
public static void main(String[] args) {
try {
process1();
} catch (Exception e) {
e.printStackTrace();
}
}
static void process1() {
try {
process2();
} catch (NullPointerException e) {
throw new IllegalArgumentException(e);
}
}
static void process2() {
throw new NullPointerException();
}
}
在catch中抛出异常,不会影响finally的执行。JVM会先执行finally,然后抛出异常。
自定义异常:
在一个大型项目中,可以自定义新的异常类型,但是,保持一个合理的异常继承体系是非常重要的。
一个常见的做法是自定义一个BaseException作为“根异常”,然后,派生出各种业务类型的异常。
BaseException需要从一个适合的Exception派生,通常建议从RuntimeException派生:
public class BaseException extends RuntimeException {
}
public class UserNotFoundException extends BaseException {
}
public class LoginFailedException extends BaseException {
}
多个构造方法:
public class BaseException extends RuntimeException {
public BaseException() {
super();
}
public BaseException(String message, Throwable cause) {
super(message, cause);
}
public BaseException(String message) {
super(message);
}
public BaseException(Throwable cause) {
super(cause);
}
}
这种增强的NullPointerException详细信息是Java 14新增的功能,但默认是关闭的,我们可以给JVM添加一个-XX:+ShowCodeDetailsInExceptionMessages参数启用它:
java -XX:+ShowCodeDetailsInExceptionMessages Main.java
------------
// assert
public class Main {
public static void main(String[] args) {
int x = -1;
assert x > 0;
System.out.println(x);
}
}
JVM默认关闭断言指令,即遇到assert语句就自动忽略了,不执行。
要执行assert语句,必须给Java虚拟机传递-enableassertions(可简写为-ea)参数启用断言
java -ea Main.java
-----------
Reflect反射:
Class cls = new Class(String);
Class类的构造方法是private,只有JVM能创建Class实例,
我们自己的Java程序是无法创建Class实例的。
获取类的三种方法:
Class cls1 = String.class;
String s = "Hello";
Class cls2 = s.getClass();
Class cls3 = Class.forName("java.lang.String");
boolean sameClass = cls1 == cls2; // true
判断类型和子类:
Integer n = new Integer(123);
boolean b1 = n instanceof Integer; // true,因为n是Integer类型
boolean b2 = n instanceof Number; // true,因为n是Number类型的子类
打印类的信息
public static void main(String[] args) {
printClassInfo("".getClass());
printClassInfo(Runnable.class);
printClassInfo(java.time.Month.class);
printClassInfo(String[].class);
printClassInfo(int.class);
}
static void printClassInfo(Class cls) {
System.out.println("Class name: " + cls.getName());
System.out.println("Simple name: " + cls.getSimpleName());
if (cls.getPackage() != null) {
System.out.println("Package name: " + cls.getPackage().getName());
}
System.out.println("is interface: " + cls.isInterface());
System.out.println("is enum: " + cls.isEnum());
System.out.println("is array: " + cls.isArray());
System.out.println("is primitive: " + cls.isPrimitive());
}
访问字段:
Class stdClass = Student.class;
// 获取public字段"score":
System.out.println(stdClass.getField("score"));
// 获取继承的public字段"name":
System.out.println(stdClass.getField("name"));
// 获取private字段"grade":
System.out.println(stdClass.getDeclaredField("grade"));
调用方法:
Class stdClass = Student.class;
// 获取public方法getScore,参数为String:
System.out.println(stdClass.getMethod("getScore", String.class));
// 获取继承的public方法getName,无参数:
System.out.println(stdClass.getMethod("getName"));
// 获取private方法getGrade,参数为int:
System.out.println(stdClass.getDeclaredMethod("getGrade", int.class));
----------------
Java语言使用@interface语法来定义注解(Annotation)
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
元注解
@Target: 定义Annotation能够被应用于源码的哪些位置
@Retention: 定义了Annotation的生命周期
@Repeatable:定义Annotation是否可重复
@Inherited: 定义子类是否可继承父类定义的Annotation
定义:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
---------------
泛型
public interface Comparable<T> {
int compareTo(T o);
}
class Person implements Comparable<Person> {
String name;
int score;
Person(String name, int score) {
this.name = name;
this.score = score;
}
public String toString() {
return this.name + "," + this.score;
}
public int compareTo(Person other) {
return this.name.compareTo(other.name);
}
}
比较接口:
Person[] ps = new Person[] {
new Person("Bob", 61),
new Person("Alice", 88),
new Person("Lily", 75),
};
Arrays.sort(ps);
System.out.println(Arrays.toString(ps));
泛型中静态函数返回值要加一个<T>
public class Pair<T> {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() { ... }
public T getLast() { ... }
// 可以编译通过:
public static <T> Pair<T> create(T first, T last) {
return new Pair<T>(first, last);
}
}
Java的泛型是由编译器在编译时实行的,编译器内部永远把所有类型T视为Object处理,但是,在需要转型的时候,编译器会根据T的类型自动为我们实行安全地强制转型。
局限一:<T>不能是基本类型,例如int,因为实际类型是Object,Object类型无法持有基本类型:
局限二:无法取得带泛型的Class
所有泛型实例,无论T的类型是什么,getClass()返回同一个Class实例,因为编译后它们全部都是Pair<Object>
Pair<Integer>不是Pair<Number>的子类
<? extends Number>的泛型定义称之为上界通配符(Upper Bounds Wildcards),即把泛型类型T的上界限定在Number了。
<? extends Number> getFirst();
即返回值是Number或Number的子类,因此,可以安全赋值给Number类型的变量:
Number x = p.getFirst();
int sumOfList(List<? extends Integer> list) {
int sum = 0;
for (int i=0; i<list.size(); i++) {
Integer n = list.get(i);
sum = sum + n;
}
return sum;
}
这是一个对参数List<? extends Integer>进行只读的方法
void set(Pair<? super Integer> p, Integer first, Integer last) {
p.setFirst(first);
p.setLast(last);
}
Pair<? super Integer>表示,方法参数接受所有泛型类型为Integer或Integer父类的Pair类型。
1.<? extends T>允许调用读方法T get()获取T的引用,但不允许调用写方法set(T)传入T的引用(传入null除外);
2.<? super T>允许调用写方法set(T)传入T的引用,但不允许调用读方法T get()获取T的引用(获取Object除外)。
一个是允许读不允许写,另一个是允许写不允许读。
PECS原则
何时使用extends,何时使用super?为了便于记忆,我们可以用PECS原则:Producer Extends Consumer Super。
即:如果需要返回T,它是生产者(Producer),要使用extends通配符;如果需要写入T,它是消费者(Consumer),要使用super通配符。
无限定通配符
void sample(Pair<?> p) {
}
因为<?>通配符既没有extends,也没有super,因此:
不允许调用set(T)方法并传入引用(null除外);
不允许调用T get()方法并获取T引用(只能获取Object引用)。
换句话说,既不能读,也不能写,那只能做一些null判断:
static boolean isNull(Pair<?> p) {
return p.getFirst() == null || p.getLast() == null;
}
Pair<?>是所有Pair<T>的超类:
--------------
List<Integer> list = List.of(1, 2, 5);
但是List.of()方法不接受null值,如果传入null,会抛出NullPointerException异常。
转数组:
List<String> list = List.of("apple", "pear", "banana");
Object[] array = list.toArray();
for (Object s : array) {
System.out.println(s);
}
自动转为类型
List<Integer> list = List.of(12, 34, 56);
Integer[] array = list.toArray(new Integer[3]);
List定义方法:
class Person {
public boolean equals(Object o) {
if (o instanceof Person p) {
return this.name.equals(p.name) && this.age == p.age;
}
return false;
}
}
ArrayList<>()
Map<String, Student> map = new HashMap<>();
Map<DayOfWeek, String> map = new EnumMap<>(DayOfWeek.class);
map.put(DayOfWeek.MONDAY, "星期一");
map.put(DayOfWeek.TUESDAY, "星期二");
Map不但需要正确覆写equals()方法,还要正确覆写hashCode()方法。
----------------------------------------
读取配置文件
String f = "setting.properties";
Properties props = new Properties();
props.load(new java.io.FileInputStream(f));
String filepath = props.getProperty("last_open_file");
String interval = props.getProperty("auto_save_interval", "120");
写入配置文件
Properties props = new Properties();
props.setProperty("url", "http://www.liaoxuefeng.com");
props.setProperty("language", "Java");
props.store(new FileOutputStream("C:\\conf\\setting.properties"), "这是写入的properties注释");
InputStream和Reader的区别是一个是字节流,一个是字符流。字符流在内存中已经以char类型表示了,不涉及编码问题。
Properties props = new Properties();
props.load(new FileReader("settings.properties", StandardCharsets.UTF_8));
Set接口并不保证有序,而SortedSet接口则保证元素是有序的:
HashSet是无序的,因为它实现了Set接口,并没有实现SortedSet接口;
TreeSet是有序的,因为它实现了SortedSet接口。
Queue实际上是实现了一个先进先出(FIFO:First In First Out)的有序表
放入PriorityQueue的元素,必须实现Comparable接口,PriorityQueue会根据元素的排序顺序决定出队的优先级。
Deque来实现一个双端队列,它的功能是:
既可以添加到队尾,也可以添加到队首;
既可以从队首获取,又可以从队尾获取。
不可变集合
Collections还提供了一组方法把可变集合封装成不可变集合:
封装成不可变List:List<T> unmodifiableList(List<? extends T> list)
封装成不可变Set:Set<T> unmodifiableSet(Set<? extends T> set)
封装成不可变Map:Map<K, V> unmodifiableMap(Map<? extends K, ? extends V> m)
这种封装实际上是通过创建一个代理对象,拦截掉所有修改方法实现的
线程安全集合
Collections还提供了一组方法,可以把线程不安全的集合变为线程安全的集合:
变为线程安全的List:List<T> synchronizedList(List<T> list)
变为线程安全的Set:Set<T> synchronizedSet(Set<T> s)
变为线程安全的Map:Map<K,V> synchronizedMap(Map<K,V> m)
--------
InputStream / OutputStream
IO流以byte(字节)为最小单位,因此也称为字节流。
Reader / Writer
字符流传输的最小数据单位是char
包java.io提供了同步IO,而java.nio则是异步IO
FileInputStream、FileOutputStream、
FileReader和FileWriter。
序列化
public interface Serializable {
}
反序列化
和ObjectOutputStream相反,ObjectInputStream负责从一个字节流读取Java对象:
测试:
public class CalculatorTest {
Calculator calculator;
@BeforeEach
public void setUp() {
this.calculator = new Calculator();
}
@AfterEach
public void tearDown() {
this.calculator = null;
}
@Test
void testAdd() {
assertEquals(100, this.calculator.add(100));
assertEquals(150, this.calculator.add(50));
assertEquals(130, this.calculator.add(-20));
}
}
JUnit提供assertThrows()来期望捕获一个指定的异常
条件测试:
使用config.getConfigFile()
@Test
void testWindows() {
assertEquals("C:\\test.ini", config.getConfigFile("test.ini"));
}
@Test
void testLinuxAndMac() {
assertEquals("/usr/local/test.cfg", config.getConfigFile("test.cfg"));
}
@Test
@DisabledOnOs(OS.WINDOWS)
void testOnNonWindowsOs() {
// TODO: this test is disabled on windows
}
@Test
@DisabledOnJre(JRE.JAVA_8)
void testOnJava9OrAbove() {
// TODO: this test is disabled on java 8
}
@Test
@EnabledIfEnvironmentVariable(named = "DEBUG", matches = "true")
void testOnlyOnDebugMode() {
// TODO: this test is only run on DEBUG=true
}
参数化测试
@ParameterizedTest
@ValueSource(ints = { 0, 1, 5, 100 })
void testAbs(int x) {
assertEquals(x, Math.abs(x));
}
@ParameterizedTest
@ValueSource(ints = { -1, -5, -100 })
void testAbsNegative(int x) {
assertEquals(-x, Math.abs(x));
}
@MethodSource注解,它允许我们编写一个同名的静态方法来提供测试参数:
@ParameterizedTest
@MethodSource
void testCapitalize(String input, String result) {
assertEquals(result, StringUtils.capitalize(input));
}
static List<Arguments> testCapitalize() {
return List.of( // arguments:
Arguments.of("abc", "Abc"), //
Arguments.of("APPLE", "Apple"), //
Arguments.of("gooD", "Good"));
}
使用@CsvSource,它的每一个字符串表示一行,一行包含的若干参数用,分隔,因此,上述测试又可以改写如下:
@ParameterizedTest
@CsvSource({ "abc, Abc", "APPLE, Apple", "gooD, Good" })
void testCapitalize(String input, String result) {
assertEquals(result, StringUtils.capitalize(input));
}
-----------
// 多线程同步
public class Main {
public static void main(String[] args) throws Exception {
var add = new AddThread();
var dec = new DecThread();
add.start();
dec.start();
add.join();
dec.join();
System.out.println(Counter.count);
}
}
class Counter {
public static int count = 0;
}
class AddThread extends Thread {
public void run() {
for (int i=0; i<10000; i++) { Counter.count += 1; }
}
}
class DecThread extends Thread {
public void run() {
for (int i=0; i<10000; i++) { Counter.count -= 1; }
}
}
public class Counter {
public void add(int n) {
synchronized(this) {
count += n;
}
}
...
}
写法二:
public synchronized void add(int n) { // 锁住this
count += n;
} // 解锁
因此,用synchronized修饰的方法就是同步方法,它表示整个方法都必须用this实例加锁。
Java标准库提供了ExecutorService接口表示线程池,它的典型用法如下:
// 创建固定大小的线程池:
ExecutorService executor = Executors.newFixedThreadPool(3);
// 提交任务:
executor.submit(task1);
executor.submit(task2);
executor.submit(task3);
executor.submit(task4);
executor.submit(task5);
因为ExecutorService只是接口,Java标准库提供的几个常用实现类有:
FixedThreadPool:线程数固定的线程池;
CachedThreadPool:线程数根据任务动态调整的线程池;
SingleThreadExecutor:仅单线程执行的线程池。
Runnable VS Callable
Runnable接口有个问题,它的方法没有返回值。如果任务需要一个返回结果,那么只能保存到变量,还要提供额外的方法读取,非常不便。所以,Java标准库还提供了一个Callable接口,和Runnable接口比,它多了一个返回值:
class Task implements Callable<String> {
public String call() throws Exception {
return longTimeCalculation();
}
}
进程与线程有什么区别
进程是操作系统分配资源的最小单位,线程是CPU调度的最小单位;
一个进程中可以包含多个线程;
进程与进程之间是相对独立的,进程中的线程之间并不完全独立,可以共享进程中的堆内存、方法区内存、系统资源等;
进程上下文的切换要比线程的上下文切换慢很多;
某个进程发生异常,不会对其它进程造成影响,但,某个线程发生异常,可能会对此进程中的其它线程造成影响;
线程池
避免了频繁创建和销毁线程所带来的性能开销
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
submit()和execute()方法都是用来提交任务到线程池中执行的,但它们之间存在一些区别:
① 参数类型,execute()方法只能接收实现了Runnable接口类型的任务,而submit()方法可以接收Runnable类型的任务和Callable接口的实现类。
② 返回类型,execute()方法没有返回值,submit()方法则返回一个Future对象,通过这个对象可以获取任务的执行结果或者取消任务执行。
③ 异常处理,execute()方法在执行任务出现异常时会直接抛出异常,而submit()方法则会捕获异常并封装到Future对象中,可以通过调用Future对象的get()方法来获取执行过程中的异常。
④ 对线程池的影响,当线程池已满时,execute()方法会直接抛出RejectedExecutionException异常,而submit()方法会将任务放入阻塞队列中,等待有空闲的线程时再执行。
Runtime.getRuntime().availableProcessors()获取的是CPU核心线程数,也就是计算资源。
CPU密集型,线程池大小设置为N,也就是和cpu的线程数相同,可以尽可能地避免线程间上下文切换,但在实际开发中,一般会设置为N+1,为了防止意外情况出现线程阻塞,如果出现阻塞,多出来的线程会继续执行任务,保证CPU的利用效率。
IO密集型,线程池大小设置为2N,这个数是根据业务压测出来的,如果不涉及业务就使用推荐。
只要有一个用户线程在运行,守护线程就会一直运行。只有所有的用户线程都结束的时候,守护线程才会退出。
编写代码时,也可以通过thread.setDaemon(true)指定线程为守护线程。
守护线程和用户线程的创建方式相同,只是在用户线程的基础上将setDaemon设置为true即可。
yield的主要作用是让当前正在执行的线程从运行状态变为就绪状态。
调用 yield 方法并不会释放线程持有的任何监视器锁,这与 wait 方法不同,后者会释放锁并导致线程进入等待状态。
wait() 必须在同步方法或同步代码块中使用,因为它需要释放锁;yield() 则可以在任何地方使用。
java.util.concurrent.locks包提供了更灵活的锁机制,如ReentrantLock。与synchronized相比,Lock提供了更多的功能,如可中断的获取锁、尝试获取锁以及定时获取锁等。
/**
* 线程不安全的单例模式
*/
public class SingleInstance {
private static SingleInstance instance;
public static SingleInstance getInstance(){
if(instance == null){
synchronized (SingleInstance.class){
if(instance == null){
instance = new SingleInstance();
}
}
}
return instance;
}
}
对于上面的代码包含三个步骤:
① 分配内存空间 ② 初始化对象 ③ 将instance引用指向内存空间
正常执行的CPU指令顺序为①②③,CPU对程序进行重排序后的执行顺序是①③②,此时就会出现问题。
private static volatile Singleton instance; // 关键:volatile修饰
volatile的作用?:
禁止指令重排序:确保写操作前的所有操作不会重排到写操作之后
保证内存可见性:修改立即对其他线程可见
当一个共享变量被 volatile 修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
volatile 关键字在 Java 中确保了变量的更新操作会立即被其他线程看到,但它并不能保证原子性。
每次线程访问volatile变量时,它都必须从主内存中读取该变量的最新值,而不是使用线程本地缓存中的值。
对于复合操作(如 i++)并不能保证其原子性。在这种情况下,应该使用其他同步机制(如 synchronized 或原子类)来确保操作的原子性。
在多核处理器的系统中,由于缓存一致性问题,即使在一个线程中对 volatile 变量的写入操作可能会被其他线程延迟看到,这是因为不同核心的缓存需要时间来同步。
37、Java中提供了哪些类解决多线程特性问题?
在Java中解决原子性问题的方案包括synchronized、Lock、ReentranLock、ReadWriteLock、CAS操作、Java中提供的原子类等。
解决可见性和有序性问题,可以禁用CPU缓存和编译器优化。
JVM提供了禁用缓存和编译优化的方法,包括volatile关键字、synchronized、final关键字以及Java内存模型中的Happens-Before原则。
@Async的作用就是异步处理任务。
在方法上添加@Async,表示此方法是异步方法;
在类上添加@Async,表示类中的所有方法都是异步方法;
使用此注解的类,必须是Spring管理的类;
需要在启动类或配置类中加入@EnableAsync注解,@Async才会生效;
在使用@Async时,如果不指定线程池的名称,也就是不自定义线程池,@Async是有默认线程池的,使用的是Spring默认的线程池SimpleAsyncTaskExecutor。
为了提高程序的执行性能,编译器和CPU会对程序的指令进行重排序,可以分为编译器重排序和CPU重排序,CPU重排序又可以分为指令级重排序和内存系统重排序。
48、as-if-serial原则是什么?
编译器和CPU对程序代码的重排序必须遵循as-if-serial原则,as-if-serial原则规定编译器和CPU无论对程序代码如何重排序,都必须保证程序在单线程环境下运行的正确性。
ThreadLocal 是一个本地线程副本变量工具类,在每个线程中都创建了一个 ThreadLocalMap 对象,简单说 ThreadLocal 就是一种以空间换时间的做法,每个线程可以访问自己内部 ThreadLocalMap 对象内的 value。通过这种方式,避免资源在多线程间共享。
Semaphore(信号量)在Java中是一个非常重要的并发工具,它主要用于限制可以同时访问某些资源的线程数量。
Callable接口的call()方法允许有返回值,而Runnable接口的run()方法则不返回任何结果。
Callable的call()方法允许抛出异常,而Runnable的run()方法则不能抛出任何被检查的异常。
Future是一个代表异步计算结果的接口。它提供了检查计算是否完成的方法以及获取计算结果的方法。通过Future,我们可以了解Callable任务是否已经完成,如果完成了,还可以获取它的返回值。
线程安全和并发安全的数据结构
数据结构选择,选择适合并发环境的数据结构,
如ConcurrentHashMap、CopyOnWriteArrayList等,它们内部已经实现了必要的同步机制。
Thread dump可以帮助开发人员诊断和解决多线程应用程序中的问题,例如死锁、资源竞争等。通过分析Thread dump,可以了解每个线程的状态、调用栈、锁信息等,从而找出问题的根源。
调用线程的interrupt()方法可以设置线程的中断状态。
在线程的运行过程中,可以使用Thread.currentThread().isInterrupted()方法检查中断状态,如果为true,则退出循环,从而停止线程。
乐观锁 自旋
编译器优化,将锁消除,前提是Java必须运行在server模式,同时必须开启逃逸分析;
-server -XX:+DoEscapeAnalysis -XX:+EliminateLocks
其中+DoEscapeAnalysis表示开启逃逸分析,+EliminateLocks表示锁消除。
锁粗化就是把多次的锁请求合并成一个请求,扩大锁的范围,降低锁请求、同步、释放带来的性能损耗。
什么是内置锁?
偏向锁
可以通过FileChannel类的lock或者tryLock方法进行加锁解锁。
// 以可写的方式打开一个文件 nezha.txt 的通道
FileChannel channel = FileChannel.open(Paths.get("nezha.txt"), StandardOpenOption.WRITE);
// 阻塞直至获取锁
FileLock lock = channel.lock();
// 会立即返回,要么返回锁,要么返回null
FileLock tryLock = channel.tryLock();
为了重量级锁synchronized提高性能,Java虚拟机(JVM)对synchronized的实现进行了优化,引入了锁升级的概念。
synchronized 锁升级的原理是基于锁状态的变化,从无锁状态开始,逐步升级到偏向锁、轻量级锁,最终到重量级锁。
CountDownLatch和CyclicBarrier在Java并发编程中都是常用的同步工具,但它们在使用场景和特性上存在明显的区别。
Optional
定义Optional对象
import java.util.Optional
@Value("${myValue:#{null}}")
private Optional<String> value;
if (value.isPresent()) {
// do something cool
}
-------------------------
@Entity
public class Book {
@Column
private LocalDate publishingDate;
...
public Optional getPublishingDate() {
return Optional.ofNullable(publishingDate);
}
public void setPublishingDate(LocalDate publishingDate) {
this.publishingDate = publishingDate;
}
}
Session session = em.unwrap(Session.class);
Optional<Book> book = session.byId(Book.class).loadOptional(1L);
-------------------------
使用@Entity(name=***)时: Repository中的@Query(***)中,只能写sql语句(当然也不用写nativeQuery=true了!)
使用@Entity+@Table(name=***)时: Repository中的@Query(***)中,可以写hql与sql(而且写sql时候必须加上nativeQuery=true)
nativeQuery = true时 有nativeQuery = true时,是可以执行原生sql语句
nativeQuery = false时
select * from xxx中xxx也不是数据库对应的真正的表名,而是对应的实体名,
并且sql中的字段名也不是数据库中真正的字段名,而是实体的字段名。
Optional.of():传递参数,如果of中的对象是null,就报空指针异常。
Optional.ofNullable():允许ofNullable传递null对象
Optional.empty():返回空的Optional实例
optional.isPresent():判断Optional实例是否为空
optional.orElse():如果optional为空的话返回orElse中的对象
optional.get():获取optional中的T对象
optional.map():如果optional不为null,则执行map方法中的映射函数得到返回值。
optional.map(Function<? super T,? extends U> mapper)
常规写法
User user = .....
if(user != null) {
String name = user.getUsername();
if(name != null) {
return name.toUpperCase();
} else {
return null;
}
} else {
return null;
}
使用optional
Optional<User> user = ...
return user.map(u -> u.getUsername())
.map(name -> name.toUpperCase())
.orElse(null);
常规写法
if (map.get("user")!=null){
Map<String,Object> user = (Map<String, Object>) map.get("user");
if (user.get("info")!=null){
Map<String,Object> info = (Map<String, Object>) user.get("info");
if (info.get("address")!=null){
String address = (String) info.get("address");
System.out.println(address);
}
}
}
使用optional
String address=Optional.ofNullable(map)
.map(m->(Map<String,Object>)m.get("user"))
.map(user->(Map<String,Object>)user.get("info"))
.map(info->(String)info.get("address"))
.orElse(null);
常规写法
if (user!=null){
UserInfo info = user.getInfo();
if (info!=null){
String address = info.getAddress();
}
}
使用optional
String address = Optional.ofNullable(user)
.map(u -> u.getInfo())
.map(info -> info.getAddress())
.orElse(null);
当某个值为空时设置默认值:
User resultUser = Optional.ofNullable(user).orElse(new User());