# 定義類
-
格式:
public class 類名{ // 靜態代碼塊 // 構造代碼塊 // 成員變量 // 構造方法 // 成員方法(set\get) // 內部類 } -
案例
/** * Created by PengZhiLin on 2021/8/19 9:18 */ public class Person { // 靜態代碼塊 static { System.out.println("靜態代碼塊是隨著類的加載而執行,并且只執行一次"); } // 構造代碼塊 { System.out.println("構造代碼塊是每次調用構造方法之前都會執行一次"); } // 成員變量 private String name; private int age; // 構造方法 public Person(){ } public Person(String name,int age){ this.name = name; this.age = age; } // 成員方法(set\get) public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public void show(){ System.out.println(name+","+age); } // 內部類 public class NClass{ } }
對象的創建和使用
-
格式:
- 創建對象:
類名 對象名 = new 類名(實參); - 對象使用:
- 成員變量:
對象名.成員變量名 - 非靜態成員方法:
- 無返回值的方法:
對象名.成員方法名(實參); - 有返回值的方法:
- 方式一:
對象名.成員方法名(實參); - 方式二:
數據類型 變量名 = 對象名.成員方法名(實參) ; - 方式三:
另一個方法名(對象名.成員方法名(實參))
- 方式一:
- 無返回值的方法:
- 成員變量:
- 靜態成員:
- 靜態成員變量:
類名.成員變量名 - 靜態成員方法:
- 無返回值的方法:
類名.成員方法名(實參); - 有返回值的方法:
- 方式一:
類名.成員方法名(實參); - 方式二:
數據類型 變量名 = 類名.成員方法名(實參) ; - 方式三:
另一個方法名(類名.成員方法名(實參))
- 方式一:
- 無返回值的方法:
- 靜態成員變量:
- 創建對象:
-
案例
/** * Created by PengZhiLin on 2021/8/19 9:17 */ public class Test { public static void main(String[] args) { // 1.創建Person對象 Person p1 = new Person(); Person p2 = new Person("張三",18); // 2.使用對象 p1.setName("李四"); p1.setAge(19); System.out.println(p1.getName()+","+p1.getAge()); int age = p1.getAge(); // 1.對象名或者類名 點 // 2.根據提示選擇要調用的方法--->查看是否有返回值以及參數個數和類型 // 3.有返回值,并且要得到返回值,就直接在后面點var+回車,如果不需要得到返回值,就直接分號結束 // 4.有參數,就傳實際參數,沒有參數,就不傳 } }
繼承
-
格式:
public class 子類名 extends 父類名{} -
繼承后的成員訪問特點:
-
子類可以繼承父類的所有成員變量和成員方法
-
優先在子類中查找,子類如果有,就直接使用,如果沒有就去父類中找....
-
-
方法的重寫:
- 概述: 子類中出現和父類一模一樣的方法(方法名,返回值類型,形參列表)
- 注意:
- 重寫的方法可以使用@Override注解標識
- 子類中重寫方法的訪問權限不能低于父類中方法的訪問權限
- 子類中出現和父類一模一樣的靜態方法不屬于方法重寫
多態
多態的幾種表現形式
-
多態的條件:
- 必須要有繼承或者實現
- 必須要有父類的引用指向子類的對象,或者接口的引用指向實現類的對象
- 方法的重寫
-
多態的表現形式:
-
普通父類多態
public class Person{} public class Student extends Person{} public class Test{ public static void main(String[] args){ Person p = new Student(); } } -
抽象父類多態
public abstract class Person{} public class Student extends Person{} public class Test{ public static void main(String[] args){ Person p = new Student(); } } -
父接口多態
public interface IA{} public class Imp implements IA{}
public class Test{
public static void main(String[] args){
IA a = new Imp();
}
} -
多態時訪問成員的特點
-
訪問特點:
- 成員變量:
編譯看左邊,運行看左邊 - 成員方法:
- 靜態成員方法:
編譯看左邊,運行看左邊 - 非靜態成員方法:
編譯看左邊,運行看右邊
- 靜態成員方法:
- 成員變量:
多態的應用場景:
-
變量多態
public class Animal {} public class Dog extends Animal{} public class Cat extends Animal{} public class Test{ public static void main(String[] args){ Animal anl = new Dog(); anl = new Cat(); } } -
形參多態
public class Animal {} public class Dog extends Animal{} public class Cat extends Animal{} public class Test{ public static void main(String[] args){ Dog dog = new Dog(); method(dog); method(new Cat()); } public static void method(Animal anl){ // 里面訪問的規則: 遵守多態時成員訪問特點進行訪問成員 } } -
返回值多態
public class Animal {} public class Dog extends Animal{} public class Cat extends Animal{} public class Test{ public static void main(String[] args){ Dog dog = new Dog(); method(dog); method(new Cat()); } public static Animal method(Animal anl){ // return new Dog(); // return new Cat(); return new Animal(); } }
引用類型轉換
向上轉型
- 概述: 子類類型自動轉換為父類類型
- eg:
Animal anl = new Dog();
向下轉型
- 概述: 父類類型變量強制轉換為子類類型--->父類類型的變量指向的對象一定是子類類型的對象
- eg:
Dog d = (Dog)anl;
instanceof關鍵字
-
格式:
if(對象名 instanceof 數據類型){} 如果該對象是屬于后面的數據類型,就返回true 如果該對象不屬于后面的數據類型,就返回false -
案例:
/** * Created by PengZhiLin on 2021/8/19 10:54 */ abstract class Animal { public abstract void eat(); } class Dog extends Animal{ @Override public void eat() { System.out.println("狗吃骨頭..."); } public void lookHome(){ System.out.println("狗在看家..."); } } class Cat extends Animal{ @Override public void eat() { System.out.println("貓吃魚..."); } public void catchMouse(){ System.out.println("貓在抓老鼠..."); } } public class Test { public static void main(String[] args) { method(new Dog()); method(new Cat()); } public static void method(Animal anl){ anl.eat(); // 轉型 if (anl instanceof Dog){ Dog dog = (Dog)anl; dog.lookHome(); } if (anl instanceof Cat){ Cat cat = (Cat)anl; cat.catchMouse(); } } }
接口
定義格式
-
格式:
public interface 接口名{ //常量(jdk7及其之前)--使用public static final修飾,這三個關鍵字可以省略 //抽象方法(jdk7及其之前)--使用public abstract修飾,這2個關鍵字可以省略 //默認方法(jdk8及其之后)--使用public default修飾,public關鍵字可以省略 //靜態方法(jdk8及其之后)--使用public static修飾,public關鍵字可以省略 //私有方法(jdk9及其之后)--使用private修飾,不可以省略 } -
案例:
/** * Created by PengZhiLin on 2021/8/19 11:17 */ public interface IA { //常量(jdk7及其之前)--使用public static final修飾,這三個關鍵字可以省略 public static final int NUM = 10; //抽象方法(jdk7及其之前)--使用public abstract修飾,這2個關鍵字可以省略 public abstract void method1(); //默認方法(jdk8及其之后)--使用public default修飾,public關鍵字可以省略 public default void method2(){ System.out.println("method2 默認方法..."); } //靜態方法(jdk8及其之后)--使用public static修飾,public關鍵字可以省略 public static void method3(){ System.out.println("method3 靜態方法..."); } //私有方法(jdk9及其之后)--使用private修飾,不可以省略 }
實現接口
-
單實現:
public class 實現類 implements 接口名{ }
- 多實現:
```java
public class 實現類 implements 接口名1,接口名2,..{
}
-
先繼承后使用
public class 實現類 extends 父類 implements 接口名{ } -
如果要實現的接口中有抽象方法,實現類必須全部重寫,否則實現類也得是個抽象類
接口中成員的訪問特點
-
特點
常量--->一般供接口直接訪問,實現類也可以 抽象方法-->只供實現類重寫的 默認方法-->只供實現類重寫或者直接繼承使用 靜態方法-->只供接口直接訪問 私有方法-->只能在接口內部訪問 -
案例
/** * Created by PengZhiLin on 2021/8/19 11:21 */ public class Imp implements IA { @Override public void method1() { System.out.println("重寫method1抽象方法..."); } } /** * Created by PengZhiLin on 2021/8/19 11:17 */ public class Test { public static void main(String[] args) { Imp imp = new Imp(); System.out.println(IA.NUM);// 訪問常量 imp.method1();// 訪問抽象方法 imp.method2();// 訪問默認方法 IA.method3();// 訪問靜態方法 } }
接口和接口之間的關系
-
單繼承
public interface 子接口名 extends 父接口名{} -
多繼承
public interface 子接口名 extends 父接口名1,接口名2,...{} -
多層繼承
public interface 父接口名 extends 爺接口名1{} public interface 子接口名 extends 父接口名{}
集合
-
集合繼承關系和特點:
單列集合:以單個單個元素進行存儲數據 List集合(接口): 元素可重復,元素有索引 ArrayList類: 底層數據結構是數組,查詢快,增刪慢 LinkedList類:底層數據結構是鏈表,查詢慢,增刪快 Set集合(接口): 元素不可重復,元素無索引 HashSet類:底層數據結構是哈希表結構,可以保證元素唯一 LinkedHashSet類:底層數據結構是鏈表+哈希表,由哈希表保證元素唯一,由鏈表保證元素存取順序一致 TreeSet類:底層數據結構是紅黑樹,可以對元素進行排序 雙列集合:以鍵值對的形式進行存儲數據 Map集合(接口): 鍵唯一,值可以重復,如果鍵重復了,值會被覆蓋,根據鍵找值 HashMap類:底層數據結構是哈希表結構,可以保證鍵唯一 LinkedHashMap類:底層數據結構是鏈表+哈希表,由哈希表保證鍵唯一,由鏈表保證鍵值對存取順序一致 TreeMap類:底層數據結構是紅黑樹,可以對鍵進行排序 -
集合的api:
- Collection接口的api
- List接口的api
- LinkedList類的api
- Map接口的api
- Collections工具類的api
-
HashSet集合保證元素唯一的原理:
1.存儲元素的時候會調用元素的hashCode方法,計算哈希值 2.判斷哈希值對應的位置是否有數據 4.如果沒有數據,就直接存儲進去 5.如果有數據,說明產生了哈希沖突 6.然后調用該元素的equals方法需要和該位置上所有元素進行一一比較: 如果該位置上所有元素與該元素不相等,就存儲 如果該位置上所有元素有任何一個元素與該元素相等,就不存儲- 注意: 如果元素是自定義類型,保證元素唯一,需要重寫equals and hashCode方法
IO流
-
分類:
-
字節流:
-
字節輸入流:
InputStream--->read(), read(byte[] bys)- 普通字節輸入流
FileInputStream: 讀一個字節,讀一個字節數組 - 字節緩沖輸入流
BufferedInputStream: 讀一個字節,讀一個字節數組 - 反序列化流
ObjectInputStream: 讀對象(readObject())
- 普通字節輸入流
-
字節輸出流:
OutputStream---> write(int b), write(byte[] bys,int off,int len)-
普通字節輸出流
FileOutputStream: 寫一個字節,寫一個字節數組 -
字節緩沖輸出流
BufferedOutputStream: 寫一個字節,寫一個字節數組 -
序列化流
ObjectOutputStream: 寫對象(writeObject()) -
打印流
PrintStream:println(), print()
-
-
-
字符流:
-
字符輸入流:
Reader----> read(), read(char[] chs)-
普通字符輸入流
FileReader: 讀一個字符,讀一個字符數組 -
字符緩沖輸入流
BufferedReader: 讀一行(readLine()) -
轉換輸入流
InputStreamReader: 讀一個字符,讀一個字符數組-->指定編碼讀,字節流轉換為字符流
-
-
字符輸出流:
Writer------> write(int c), write(char[] chs,int off,int len)- 普通字符輸出流
FileWriter: 寫一個字符,寫一個字符數組 - 字符緩沖輸出流
BufferedWriter: 根據系統寫換行newLine() - 轉換輸出流
OutputStreamWriter: 寫一個字符,寫一個字符數組-->指定編碼寫,字節流轉換為字符流
- 普通字符輸出流
-
-
-
IO流使用步驟:
讀寫一個字節: 1.創建輸入流對象,關聯數據源文件路徑 2.創建輸出流對象,關聯目的地文件路徑 3.定義一個int類型的變量,用存儲讀取到的字節數據 4.循環讀取數據 5.在循環中,寫出數據 6.關閉流,釋放資源 讀寫一個字節數組: 1.創建輸入流對象,關聯數據源文件路徑 2.創建輸出流對象,關聯目的地文件路徑 3.定義一個byte類型的數組,用來存儲讀取到的字節數據 3.定義一個int類型的變量,用存儲讀取到的字節個數 4.循環讀取數據 5.在循環中,寫出數據 6.關閉流,釋放資源
屬性集
-
相關api:
public Properties() :創建一個空的屬性列表。 public void load(InputStream inStream): 從字節輸入流中讀取鍵值對。 public void load(Reader reader) 從字符輸入流中讀取鍵值對。 public Set<String> stringPropertyNames() :所有鍵的名稱的集合。 public String getProperty(String key) :使用此屬性列表中指定的鍵搜索屬性值。 -
案例:
// 配置文件: 一定要放在src下面 driverClass = com.mysql.jdbc.Driver url = jdbc:mysql://localhost:3306/web18_1 username = root password = root
/**
-
Created by PengZhiLin on 2021/8/19 12:11
*/
public class JDBCUtils {public static String driverClass;
public static String url;
public static String username;
public static String password;static {
try {
// 1.創建Properties對象
Properties pro = new Properties();// 2.加載配置文件 //pro.load(new FileInputStream("day16\\src\\db.properties")); InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("db.properties"); pro.load(is); // 3.取值 driverClass = pro.getProperty("driverClass"); url = pro.getProperty("url"); username = pro.getProperty("username"); password = pro.getProperty("password"); } catch (Exception e) { e.printStackTrace(); }}
}
# 反射
## 反射之操作成員方法
#### Method類概述
```java
Method類概述
* 每一個成員方法都是一個Method類的對象。
通過反射獲取類的成員方法
Class類中與Method相關的方法
* Method getDeclaredMethod(String name,Class... args);----->推薦
* 根據方法名和參數類型獲得對應的構造方法對象,包括public、protected、(默認)、private的
參數1:要獲取的方法的方法名
參數2:要獲取的方法的形參類型的Class對象
* Method[] getDeclaredMethods();----->推薦
* 獲得類中的所有成員方法對象,返回數組,只獲得本類的,包括public、protected、(默認)、private的
通過反射執行成員方法
Method對象常用方法
* Object invoke(Object obj, Object... args)
* 參數1:調用該方法的對象
* 參數2:調用該法時傳遞的實際參數
返回值:該方法執行完畢后的返回值
* void setAccessible(true)
設置"暴力訪問"——是否取消權限檢查,true取消權限檢查,false表示不取消
示例代碼
public class Person {
public void show1() {
System.out.println("無參數無返回值show1");
}
public void show2(int num, String str) {
System.out.println("有參數無返回值show2,參數num:" + num + ",參數str:" + str);
}
public int show3() {
System.out.println("無參數有返回值show3");
return 3;
}
private String show4(String str) {
System.out.println("有參數有返回值show4,參數str:" + str);
return "itheima";
}
}
/**
* Created by PengZhiLin on 2021/8/19 12:18
*/
public class Test {
public static void main(String[] args) throws Exception{
// 1.獲取字節碼對象
Class<Person> c = Person.class;
// 2.通過反射創建Person對象
Constructor<Person> cons = c.getDeclaredConstructor();
Person p = cons.newInstance();
// 3.通過反射獲取成員方法
Method show1M = c.getDeclaredMethod("show1");
Method show2M = c.getDeclaredMethod("show2",int.class,String.class);
Method show3M = c.getDeclaredMethod("show3");
Method show4M = c.getDeclaredMethod("show4",String.class);
show1M.invoke(p);
show2M.invoke(p,18,"itheima");
Object res1 = show3M.invoke(p);
System.out.println("res1"+res1);
show4M.setAccessible(true);
Object res2 = show4M.invoke(p, "itcast");
System.out.println("res2:"+res2);
}
}
jdk8新特性
Lambda,Stream流,方法引用
-
Lambda表達式
格式: (參數)->{代碼塊} 前提: 接口必須是函數式接口 套路: 1.判斷是否可以使用Lambda表達式 2.如果可以使用,就寫上()->{} 3.填充小括號中的內容--->函數式接口中的抽象方法的形參一致 4.填充大括號中的內容--->實現函數式接口抽象方法的方法體 省略: 1.小括號中參數類型可以省略 2.如果小括號中只有一個參數,那么小括號也可以一起省略 3.如果大括號中只有一條語句,那么大括號,分號和return可以省略(一起省略) -
Stream流
-
使用步驟: 獲取流--->操作流---->收集結果
-
Stream流api:
-
forEach
-
count
-
collect
-
filter
-
limit
-
skip
-
map
-
concat
-
-
案例:
/** * Created by PengZhiLin on 2021/8/19 14:33 */ public class Test1 { public static void main(String[] args) { // 1.獲取流 Stream<String> stream1 = Stream.of("張三豐", "張翠山", "金毛獅王", "張無忌"); Stream<String> stream2 = Stream.of("110", "120", "119", "114"); // 2.操作流--->過濾出姓張的元素,并取前2個,打印輸出 //stream1.filter(name->name.startsWith("張")).limit(2).forEach(name-> System.out.println(name)); // 3.操作流--->轉換Integer類型,并跳過前2個,打印輸出 //stream2.map(str->Integer.parseInt(str)).skip(2).forEach(i-> System.out.println(i)); // 4.操作流--->合并2個流,并收集到集合中 //List<String> list = Stream.concat(stream1, stream2).collect(Collectors.toList()); //System.out.println(list); Set<String> set = Stream.concat(stream1, stream2).collect(Collectors.toSet()); System.out.println(set); } }
-
-
方法引用
使用場景: 如果一個Lambda表達式大括號中的代碼就是調用另一個方法或者與另一個方法代碼相同,就直接把該方法引用過濾,替換Lambda表達式 套路: 1.判斷是否可以使用方法引用 2.確定引入的方法的類型(構造方法,靜態方法,非靜態方法) 3.根據引入的格式引入方法 構造方法: 類名::new 靜態方法: 類名::方法名 有參數成員方法: 對象名::方法名 無參數成員方法: 類名:: 方法名/** * Created by PengZhiLin on 2021/8/19 14:42 */ class Person{ String name; public Person(String name) { this.name = name; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + '}'; } } public class Test2 { public static void main(String[] args) { // 1.獲取流 Stream<String> stream1 = Stream.of("張三豐", "張翠山", "金毛獅王", "張無忌"); Stream<String> stream2 = Stream.of("110", "120", "119", "114"); // 2.操作流--->過濾出姓張的元素,并取前2個,打印輸出 //stream1.filter(name->name.startsWith("張")).limit(2).forEach(name-> System.out.println(name)); //stream1.filter(name->name.startsWith("張")).limit(2).forEach(System.out::println); // 3.操作流--->轉換Integer類型,并跳過前2個,打印輸出 //stream2.map(str->Integer.parseInt(str)).skip(2).forEach(i-> System.out.println(i)); //stream2.map(Integer::parseInt).skip(2).forEach( System.out::println); // 4.操作流--->把姓名轉換為Person對象,打印輸出 //stream1.map(Person::new).forEach(System.out::println); // 5.操作流--->把姓名轉換為姓名對應的字符長度,打印輸出 stream1.map(String::length).forEach(System.out::println); } }
線程安全
線程創建和啟動
-
線程創建:
-
通過繼承Thread方式
-
創建線程子類繼承Thread類
-
在線程子類中重寫run方法,把線程需要執行的任務放在run方法里面
-
創建線程子類對象
-
調用start方法啟動線程執行任務
-
-
通過實現Runnable方式
-
創建實現類實現Runnable接口
-
在實現類中重寫run方法,把線程需要執行的任務放在run方法里面
-
創建Thread線程對象,并傳入實現類對象
-
使用線程對象調用start方法啟動線程,執行任務
-
-
-
啟動:
-
調用線程的start()方法
-
線程的調度:搶占式調度
-
每一條線程都會有獨立的棧空間,來執行任務.
-
可見性問題演示
-
概述: 一個線程沒有看見另一個線程對共享變量的修改
-
例如下面的程序,先啟動一個線程,在線程中將一個變量的值更改,而主線程卻一直無法獲得此變量的新值。
- 線程類:
public class MyThread extends Thread { static boolean flag = false;// 主和子線程共享變量 @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // 把flag的值改為true flag = true; System.out.println("修改后flag的值為:"+flag); } }- 測試類:
public class Test { public static void main(String[] args) { /* 多線程的安全性問題-可見性: 一個線程沒有看見另一個線程對共享變量的修改 */ // 創建子線程并啟動 MyThread mt = new MyThread(); mt.start(); // 主線程 while (true){ if (MyThread.flag == true){ System.out.println("死循環結束"); break; } } /* 分析后期望的結果:主線程一直死循環,當子線程把共享變量flag的值改為true,主線程就結束死循環 實際結果: 主線程一直死循環,當子線程把共享變量flag的值改為true,主線程依然還是死循環 原因: 子線程對共享變量flag值的改變,對主線程不可見 解決辦法: 使用volatile關鍵字,當變量被修飾為volatile時,會迫使線程每次使用此變量,都會去主內存獲取,保證其可見性 */ } } -
原因:
-
JMM內存模型(Java Memory Model)描述了Java程序中各種變量(線程共享變量)的訪問規則,以及在JVM中將變量存儲到內存和從內存中讀取變量這樣的底層細節。
-
簡而言之: 就是所有共享變量都是存在主內存中的,線程在執行的時候,有單獨的工作內存,會把共享變量拷貝一份到線程的單獨工作內存中,并且對變量所有的操作,都是在單獨的工作內存中完成的,不會直接讀寫主內存中的變量值

可見性問題解決
-
解決辦法: 使用volatile關鍵字,當變量被修飾為volatile時,會迫使線程每次使用此變量,都會去主內存獲取,保證其可見性
-
代碼:
public class MyThread extends Thread { volatile static boolean flag = false;// 主和子線程共享變量 @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // 把flag的值改為true flag = true; System.out.println("修改后flag的值為:"+flag); } }
有序性問題演示和解決
有序性問題演示
-
有些時候“編譯器”在編譯代碼時,會對代碼進行“重排”,例如:
? int a = 10; //1
? int b = 20; //2
? int c = a + b; //3
-
單線程: 第一行和第二行可能會被“重排”:可能先編譯第二行,再編譯第一行,總之在執行第三行之前,會將1,2編譯完畢。1和2先編譯誰,不影響第三行的結果。
-
但在“多線程”情況下,代碼重排,可能會對另一個線程訪問的結果產生影響:

多線程環境下,我們通常不希望對一些代碼進行重排的!!
有序性問題解決
- 使用volatile修飾共享變量,禁止編譯器重排
原子性問題演示
-
概述:所謂的原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了執行并且不會受到任何因素的干擾而中斷,要么所有的操作都不執行,多個操作是一個不可以分割的整體。
-
請看以下示例:
- 一條子線程和一條主線程都對共享變量a進行++操作,每條線程對a++操作100000次
1.制作線程類
public class MyThread extends Thread{ static int a = 0; @Override public void run() { // 子線程對a進行自增10萬次 for (int i = 0; i < 100000; i++) { a++; } System.out.println("子線程執行完畢"); } }
public class Test {
public static void main(String[] args) throws Exception{
// 創建并啟動子線程
new MyThread().start();
// 主線程對a進行自增10萬次
for (int i = 0; i < 100000; i++) {
MyThread.a++;
}
// 為了保證子線程和主線程都執行完畢
Thread.sleep(3000);
// 打印最終共享變量a的值(子線程,主線程對a的操作都執行完畢了)
System.out.println("最終:"+MyThread.a);
/*
分析后期望的結果: 20萬
實際的結果: 小于或者等于20萬
原因: 主線程和子線程同時對a進行自增,產生了覆蓋的效果
*/
}
}
原因:兩個線程對共享變量的操作產生覆蓋的效果
原子性問題解決
-
解決辦法: 加鎖,或者使用原子類
-
代碼:
public class MyThread extends Thread{ //static int a = 0; static AtomicInteger a = new AtomicInteger(0); @Override public void run() { // 子線程對a進行自增10萬次 for (int i = 0; i < 100000; i++) { /*synchronized ("suo"){ a++; }*/ a.getAndIncrement(); } System.out.println("子線程執行完畢"); } } public class Test { public static void main(String[] args) throws Exception { // 創建并啟動子線程 new MyThread().start(); // 主線程對a進行自增10萬次 for (int i = 0; i < 100000; i++) { /*synchronized ("suo"){ MyThread.a++; }*/ MyThread.a.getAndIncrement(); } // 為了保證子線程和主線程都執行完畢 Thread.sleep(3000); // 打印最終共享變量a的值(子線程,主線程對a的操作都執行完畢了) System.out.println("最終:" + MyThread.a.get()); /* 解決辦法: 加鎖,或者原子類 */ } }
AtomicInteger類的工作原理-CAS機制

同步代碼塊
-
格式:
synchronized(鎖對象){ } -
鎖對象
- 語法: 可以是任意類對象
- 注意: 如果多條線程想要實現同步,那么這多條線程的鎖對象必須一致(相同)
-
格式: 方法的返回值類型前面加上synchronized,其余都不變
-
鎖對象:
-
非靜態成員方法: 鎖對象就是this
-
靜態成員方法: 該方法所在類的字節碼對象,類名.class
-
Lock鎖
-
public void lock();加鎖 -
public void unlock();釋放鎖 -
Lock鎖的等待喚醒機制:
package com.itheima.demo7_lock鎖擴展; /** * Created by PengZhiLin on 2021/8/9 9:55 */ public class MyThread extends Thread { @Override public void run() { for (int i = 0; i < 100; i++) { Test.lock.lock(); // 條件(flag=true): 子線程進入無限等待 if (Test.flag == true) { try { Test.condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } } // 條件(flag=false): 子線程被喚醒,執行任務代碼 if (Test.flag == false) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("子線程:第" + i + "次i循環"); // 喚醒主線程 Test.condition.signal(); // 修改旗幟變量的值 Test.flag = true; } Test.lock.unlock(); } } }import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * Created by PengZhiLin on 2021/8/9 9:55 */ public class Test { // 共享Lock對象 static Lock lock = new ReentrantLock(); // 共享鎖對象 static Condition condition = lock.newCondition(); // 共享條件變量 static boolean flag = false; public static void main(String[] args) { // 創建并啟動子線程 new MyThread().start(); // 主線程任務 for (int j = 0; j < 100; j++) { lock.lock(); // 條件(flag=false): 主線程進入無限等待 if (flag == false) { try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } } // 條件(flag=true): 主線程被喚醒,執行任務代碼 if (flag == true) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("主線程:第" + j + "次j循環"); // 喚醒子線程 condition.signal(); // 修改旗幟變量的值 flag = false; } lock.unlock(); } } }
浙公網安備 33010602011771號