Artikel ini meringkaskan beberapa soalan temuduga asas frekuensi tinggi Java terpilih pada tahun 2023 yang patut dikumpul (dengan jawapan). Ia mempunyai nilai rujukan tertentu Rakan-rakan yang memerlukan boleh merujuk kepadanya.
Tiga ciri asas berorientasikan objek ialah: enkapsulasi, pewarisan dan polimorfisme.
Warisan: Kaedah yang membenarkan objek jenis tertentu memperoleh sifat objek jenis lain. Warisan bermaksud subkelas mewarisi ciri dan tingkah laku kelas induk, supaya objek subkelas (contoh) mempunyai medan dan kaedah contoh kelas induk, atau subkelas mewarisi kaedah daripada kelas induk, supaya subkelas mempunyai tingkah laku yang sama seperti kelas induk. (Tutorial yang disyorkan: Tutorial pengenalan Java)
Enkapsulasi: Sembunyikan sifat dan butiran pelaksanaan beberapa objek, dan akses kepada data hanya boleh melalui antara muka yang terdedah secara luaran. Dengan cara ini, objek menyediakan tahap perlindungan yang berbeza untuk data dalaman untuk menghalang bahagian program yang tidak berkaitan daripada menukar secara tidak sengaja atau menggunakan bahagian sulit objek secara tidak sengaja.
Polimorfisme: Untuk kelakuan yang sama, objek subkelas yang berbeza mempunyai ungkapan yang berbeza. Terdapat tiga syarat untuk kewujudan polimorfisme: 1) pewarisan; 2) timpa 3) titik rujukan kelas induk kepada objek subkelas.
Contoh mudah: dalam League of Legends, kami menekan kekunci Q:
Acara yang sama akan menghasilkan hasil yang berbeza apabila ia berlaku pada objek yang berbeza.
Biar saya berikan anda satu lagi contoh mudah untuk membantu anda memahami Contoh ini mungkin tidak tepat sepenuhnya, tetapi saya fikir ia membantu untuk memahami.
public class Animal { // 动物 public void sleep() { System.out.println("躺着睡"); } } class Horse extends Animal { // 马 是一种动物 public void sleep() { System.out.println("站着睡"); } } class Cat extends Animal { // 猫 是一种动物 private int age; public int getAge() { return age + 1; } @Override public void sleep() { System.out.println("四脚朝天的睡"); } }
Dalam contoh ini:
Kedua-dua Rumah dan Kucing adalah Haiwan, jadi mereka berdua mewarisi Haiwan dan juga mewarisi tingkah laku tidur daripada Haiwan.
Tetapi untuk tingkah laku tidur, House dan Cat telah ditulis semula dan mempunyai ungkapan (implementasi) yang berbeza Ini dipanggil polimorfisme.
Dalam Cat, atribut umur ditakrifkan sebagai peribadi dan tidak boleh diakses secara langsung oleh dunia luar Satu-satunya cara untuk mendapatkan maklumat umur Kucing adalah melalui kaedah getAge, dengan itu menyembunyikan atribut umur dari luar dipanggil enkapsulasi. Sudah tentu, umur di sini hanyalah satu contoh, dan ia mungkin objek yang jauh lebih kompleks dalam penggunaan sebenar.
// 代码块1 short s1 = 1; s1 = s1 + 1; // 代码块2 short s1 = 1; s1 += 1;
Kod blok 1 dikompil dan ralat dilaporkan Punca ralat ialah: Jenis tidak serasi: Penukaran dari int kepada pendek boleh menyebabkan kerugian".
Blok kod. 2 menyusun secara normal dan Laksanakan
Kami menyusun blok kod 2. Kod bait adalah seperti berikut:
public class com.joonwhee.open.demo.Convert { public com.joonwhee.open.demo.Convert(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: iconst_1 // 将int类型值1入(操作数)栈 1: istore_1 // 将栈顶int类型值保存到局部变量1中 2: iload_1 // 从局部变量1中装载int类型值入栈 3: iconst_1 // 将int类型值1入栈 4: iadd // 将栈顶两int类型数相加,结果入栈 5: i2s // 将栈顶int类型值截断成short类型值,后带符号扩展成int类型值入栈。 6: istore_1 // 将栈顶int类型值保存到局部变量1中 7: return }
Anda boleh melihat bahawa kod bait mengandungi arahan i2s, yang digunakan untuk menukar int kepada pendek. i2s ialah singkatan daripada int kepada pendek
Malah, s1 = 1 adalah bersamaan dengan s1 = (short)(s1 1). baris kod anda sendiri. Anda akan mendapati bahawa soalan asas Java adalah betul-betul sama? hasil keluaran soalan seterusnya
ialah: salah, benar
melaksanakan Integer a = 128, yang bersamaan dengan melaksanakan: Integer a = Integer.valueOf. 128), proses menukar jenis asas secara automatik kepada kelas pembalut dipanggil autoboxingpublic static void main(String[] args) { Integer a = 128, b = 128, c = 127, d = 127; System.out.println(a == b); System.out.println(c == d); }
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
5. Kira 2 kali 8 dengan cara yang paling cekap
2 << bahawa operasi bit mempunyai prestasi tertinggi, bagaimanapun, sebenarnya, penyusun kini "sangat pintar", dan banyak penyusun arahan boleh melakukan pengoptimuman mereka sendiri, jadi dalam amalan sebenar, kita tidak perlu meneruskan operasi bit yang praktikal kebolehbacaan kod yang lemah, dan beberapa pengoptimuman yang bijak boleh mengelirukan pengkompil, menjadikan pengkompil tidak dapat melakukan pengoptimuman yang lebih baikIni mungkin yang dipanggil "rakan sepasukan babi". >
&&:逻辑与运算符。当运算符左右两边的表达式都为 true,才返回 true。同时具有短路性,如果第一个表达式为 false,则直接返回 false。
&:逻辑与运算符、按位与运算符。
按位与运算符:用于二进制的计算,只有对应的两个二进位均为1时,结果位才为1 ,否则为0。
逻辑与运算符:& 在用于逻辑与时,和 && 的区别是不具有短路性。所在通常使用逻辑与运算符都会使用 &&,而 & 更多的适用于位运算。
答:不是。Java 中的基本数据类型只有8个:byte、short、int、long、float、double、char、boolean;除了基本类型(primitive type),剩下的都是引用类型(reference type)。
基本数据类型:数据直接存储在栈上
引用数据类型区别:数据存储在堆上,栈上只存储引用地址
不行。String 类使用 final 修饰,无法被继承。
String:String 的值被创建后不能修改,任何对 String 的修改都会引发新的 String 对象的生成。
StringBuffer:跟 String 类似,但是值可以被修改,使用 synchronized 来保证线程安全。
StringBuilder:StringBuffer 的非线程安全版本,没有使用 synchronized,具有更高的性能,推荐优先使用。
一个或两个。如果字符串常量池已经有“xyz”,则是一个;否则,两个。
当字符创常量池没有 “xyz”,此时会创建如下两个对象:
一个是字符串字面量 "xyz" 所对应的、驻留(intern)在一个全局共享的字符串常量池中的实例,此时该实例也是在堆中,字符串常量池只放引用。
另一个是通过 new String() 创建并初始化的,内容与"xyz"相同的实例,也是在堆中。
两个语句都会先去字符串常量池中检查是否已经存在 “xyz”,如果有则直接使用,如果没有则会在常量池中创建 “xyz” 对象。
另外,String s = new String("xyz") 还会通过 new String() 在堆里创建一个内容与 "xyz" 相同的对象实例。
所以前者其实理解为被后者的所包含。
==:运算符,用于比较基础类型变量和引用类型变量。
对于基础类型变量,比较的变量保存的值是否相同,类型不一定要相同。
short s1 = 1; long l1 = 1; // 结果:true。类型不同,但是值相同 System.out.println(s1 == l1);
对于引用类型变量,比较的是两个对象的地址是否相同。
Integer i1 = new Integer(1); Integer i2 = new Integer(1); // 结果:false。通过new创建,在内存中指向两个不同的对象 System.out.println(i1 == i2);
equals:Object 类中定义的方法,通常用于比较两个对象的值是否相等。
equals 在 Object 方法中其实等同于 ==,但是在实际的使用中,equals 通常被重写用于比较两个对象的值是否相同。
Integer i1 = new Integer(1); Integer i2 = new Integer(1); // 结果:true。两个不同的对象,但是具有相同的值 System.out.println(i1.equals(i2)); // Integer的equals重写方法 public boolean equals(Object obj) { if (obj instanceof Integer) { // 比较对象中保存的值是否相同 return value == ((Integer)obj).intValue(); } return false; }
不对。hashCode() 和 equals() 之间的关系如下:
当有 a.equals(b) == true 时,则 a.hashCode() == b.hashCode() 必然成立,
反过来,当 a.hashCode() == b.hashCode() 时,a.equals(b) 不一定为 true。
反射是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法;并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能称为反射机制。
数据分为基本数据类型和引用数据类型。基本数据类型:数据直接存储在栈中;引用数据类型:存储在栈中的是对象的引用地址,真实的对象数据存放在堆内存里。
浅拷贝:对于基础数据类型:直接复制数据值;对于引用数据类型:只是复制了对象的引用地址,新旧对象指向同一个内存地址,修改其中一个对象的值,另一个对象的值随之改变。
深拷贝:对于基础数据类型:直接复制数据值;对于引用数据类型:开辟新的内存空间,在新的内存空间里复制一个一模一样的对象,新老对象不共享内存,修改其中一个对象的值,不会影响另一个对象。
深拷贝相比于浅拷贝速度较慢并且花销较大。
并发:两个或多个事件在同一时间间隔发生。
并行:两个或者多个事件在同一时刻发生。
并行是真正意义上,同一时刻做多件事情,而并发在同一时刻只会做一件事件,只是可以将时间切碎,交替做多件事情。
网上有个例子挺形象的:
你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。
你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。
你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。
Constructor 不能被 override(重写),但是可以 overload(重载),所以你可以看到⼀个类中有多个构造函数的情况。
值传递。Java 中只有值传递,对于对象参数,值的内容是对象的引用。
public class Demo { /** * 静态变量:又称类变量,static修饰 */ public static String STATIC_VARIABLE = "静态变量"; /** * 实例变量:又称成员变量,没有static修饰 */ public String INSTANCE_VARIABLE = "实例变量"; }
成员变量存在于堆内存中。静态变量存在于方法区中。
成员变量与对象共存亡,随着对象创建而存在,随着对象被回收而释放。静态变量与类共存亡,随着类的加载而存在,随着类的消失而消失。
成员变量所属于对象,所以也称为实例变量。静态变量所属于类,所以也称为类变量。
成员变量只能被对象所调用 。静态变量可以被对象调用,也可以被类名调用。
区分两种情况,发出调用时是否显示创建了对象实例。
1)没有显示创建对象实例:不可以发起调用,非静态方法只能被对象所调用,静态方法可以通过对象调用,也可以通过类名调用,所以静态方法被调用时,可能还没有创建任何实例对象。因此通过静态方法内部发出对非静态方法的调用,此时可能无法知道非静态方法属于哪个对象。
public class Demo { public static void staticMethod() { // 直接调用非静态方法:编译报错 instanceMethod(); } public void instanceMethod() { System.out.println("非静态方法"); } }
2)显示创建对象实例:可以发起调用,在静态方法中显示的创建对象实例,则可以正常的调用。
public class Demo { public static void staticMethod() { // 先创建实例对象,再调用非静态方法:成功执行 Demo demo = new Demo(); demo.instanceMethod(); } public void instanceMethod() { System.out.println("非静态方法"); } }
public class InitialTest { public static void main(String[] args) { A ab = new B(); ab = new B(); } } class A { static { // 父类静态代码块 System.out.print("A"); } public A() { // 父类构造器 System.out.print("a"); } } class B extends A { static { // 子类静态代码块 System.out.print("B"); } public B() { // 子类构造器 System.out.print("b"); } }
执行结果:ABabab,两个考察点:
1)静态变量只会初始化(执行)一次。
2)当有父类时,完整的初始化顺序为:父类静态变量(静态代码块)->子类静态变量(静态代码块)->父类非静态变量(非静态代码块)->父类构造器 ->子类非静态变量(非静态代码块)->子类构造器 。
关于初始化,这题算入门题,我之前还写过一道有(fei)点(chang)意(bian)思(tai)的进阶题目,有兴趣的可以看看:一道有意思的“初始化”面试题
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
重载:一个类中有多个同名的方法,但是具有有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)。
重写:发生在子类与父类之间,子类对父类的方法进行重写,参数都不能改变,返回值类型可以不相同,但是必须是父类返回值的派生类。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。
如果我们有两个方法如下,当我们调用:test(1) 时,编译器无法确认要调用的是哪个。
// 方法1 int test(int a); // 方法2 long test(int a);
方法的返回值只是作为方法运行之后的一个“状态”,但是并不是所有调用都关注返回值,所以不能将返回值作为重载的唯一区分条件。
抽象类只能单继承,接口可以多实现。
抽象类可以有构造方法,接口中不能有构造方法。
抽象类中可以有成员变量,接口中没有成员变量,只能有常量(默认就是 public static final)
抽象类中可以包含非抽象的方法,在 Java 7 之前接口中的所有方法都是抽象的,在 Java 8 之后,接口支持非抽象方法:default 方法、静态方法等。Java 9 支持私有方法、私有静态方法。
抽象类中的方法类型可以是任意修饰符,Java 8 之前接口中的方法只能是 public 类型,Java 9 支持 private 类型。
设计思想的区别:
接口是自上而下的抽象过程,接口规范了某些行为,是对某一行为的抽象。我需要这个行为,我就去实现某个接口,但是具体这个行为怎么实现,完全由自己决定。
抽象类是自下而上的抽象过程,抽象类提供了通用实现,是对某一类事物的抽象。我们在写实现类的时候,发现某些实现类具有几乎相同的实现,因此我们将这些相同的实现抽取出来成为抽象类,然后如果有一些差异点,则可以提供抽象方法来支持自定义实现。
我在网上看到有个说法,挺形象的:
普通类像亲爹 ,他有啥都是你的。
抽象类像叔伯,有一部分会给你,还能指导你做事的方法。
接口像干爹,可以给你指引方法,但是做成啥样得你自己努力实现。
Error 和 Exception 都是 Throwable 的子类,用于表示程序出现了不正常的情况。区别在于:
Error 表示系统级的错误和程序不必处理的异常,是恢复不是不可能但很困难的情况下的一种严重问题,比如内存溢出,不可能指望程序能处理这样的情况。
Exception 表示需要捕捉或者需要程序进行处理的异常,是一种设计或实现问题,也就是说,它表示如果程序运行正常,从不会发生的情况。
修饰类:该类不能再派生出新的子类,不能作为父类被继承。因此,一个类不能同时被声明为abstract 和 final。
修饰方法:该方法不能被子类重写。
修饰变量:该变量必须在声明时给定初值,而在以后只能读取,不可修改。 如果变量是对象,则指的是引用不可修改,但是对象的属性还是可以修改的。
public class FinalDemo { // 不可再修改该变量的值 public static final int FINAL_VARIABLE = 0; // 不可再修改该变量的引用,但是可以直接修改属性值 public static final User USER = new User(); public static void main(String[] args) { // 输出:User(id=0, name=null, age=0) System.out.println(USER); // 直接修改属性值 USER.setName("test"); // 输出:User(id=0, name=test, age=0) System.out.println(USER); } }
其实是三个完全不相关的东西,只是长的有点像。。
final 如上所示。
finally:finally 是对 Java 异常处理机制的最佳补充,通常配合 try、catch 使用,用于存放那些无论是否出现异常都一定会执行的代码。在实际使用中,通常用于释放锁、数据库连接等资源,把资源释放方法放到 finally 中,可以大大降低程序出错的几率。
finalize:Object 中的方法,在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。finalize()方法仅作为了解即可,在 Java 9 中该方法已经被标记为废弃,并添加新的 java.lang.ref.Cleaner,提供了更灵活和有效的方法来释放资源。这也侧面说明了,这个方法的设计是失败的,因此更加不能去使用它。
public class TryDemo { public static void main(String[] args) { System.out.println(test()); } public static int test() { try { return 1; } catch (Exception e) { return 2; } finally { System.out.print("3"); } } }
执行结果:31。
相信很多同学应该都做对了,try、catch。finally 的基础用法,在 return 前会先执行 finally 语句块,所以是先输出 finally 里的 3,再输出 return 的 1。
public class TryDemo { public static void main(String[] args) { System.out.println(test1()); } public static int test1() { try { return 2; } finally { return 3; } } }
执行结果:3。
这题有点陷阱,但也不难,try 返回前先执行 finally,结果 finally 里不按套路出牌,直接 return 了,自然也就走不到 try 里面的 return 了。
finally 里面使用 return 仅存在于面试题中,实际开发中千万不要这么用。
public class TryDemo { public static void main(String[] args) { System.out.println(test1()); } public static int test1() { int i = 0; try { i = 2; return i; } finally { i = 3; } } }
执行结果:2。
这边估计有不少同学会以为结果应该是 3,因为我们知道在 return 前会执行 finally,而 i 在 finally 中被修改为 3 了,那最终返回 i 不是应该为 3 吗?确实很容易这么想,我最初也是这么想的,当初的自己还是太年轻了啊。
这边的根本原因是,在执行 finally 之前,JVM 会先将 i 的结果暂存起来,然后 finally 执行完毕后,会返回之前暂存的结果,而不是返回 i,所以即使这边 i 已经被修改为 3,最终返回的还是之前暂存起来的结果 2。
Sebenarnya, ia boleh dilihat dengan mudah berdasarkan kod bait Sebelum memasuki akhirnya, JVM akan menggunakan arahan iload dan istore untuk menyimpan hasil sementara apabila ia akhirnya kembali, ia akan mengembalikan hasil sementara melalui iload dan arahan pemulangan.
Untuk mengelakkan suasana menjadi tidak normal lagi, saya tidak akan menyiarkan program bytecode khusus di sini. Pelajar yang berminat boleh menyusunnya dan menyemaknya sendiri.
Kaedah lalai antara muka: Java 8 membenarkan kami menambah pelaksanaan kaedah bukan abstrak pada antara muka, hanya gunakan kata kunci lalai
Ungkapan Lambda dan antara muka berfungsi: Lambda Satu ungkapan ialah pada asasnya kelas dalaman tanpa nama, atau ia boleh menjadi sekeping kod yang boleh dihantar. Lambda membenarkan fungsi digunakan sebagai parameter kaedah (fungsi dihantar ke kaedah sebagai parameter Gunakan ungkapan Lambda untuk membuat kod lebih ringkas, tetapi jangan menyalahgunakannya, jika tidak, akan ada masalah kebolehbacaan, Josh). Bloch, pengarang "Effective Java" mencadangkan Sebaik-baiknya menggunakan ungkapan lambda dalam tidak lebih daripada 3 baris.
API Strim: Alat untuk melaksanakan operasi kompleks pada kelas pengumpulan menggunakan pengaturcaraan berfungsi Ia boleh digunakan dengan ungkapan Lambda untuk memproses koleksi dengan mudah. Abstraksi utama untuk bekerja dengan koleksi dalam Java 8. Ia membolehkan anda menentukan operasi yang ingin anda lakukan pada koleksi dan boleh melakukan operasi yang sangat kompleks seperti mencari, menapis dan memetakan data. Menggunakan API Strim untuk mengendalikan data pengumpulan adalah serupa dengan menggunakan SQL untuk melaksanakan pertanyaan pangkalan data. Anda juga boleh menggunakan API Strim untuk melaksanakan operasi secara selari. Ringkasnya, Stream API menyediakan cara yang cekap dan mudah digunakan untuk memproses data.
Rujukan kaedah: Rujukan kaedah menyediakan sintaks yang sangat berguna yang boleh merujuk secara langsung kaedah atau pembina kelas atau objek Java (contoh). Digunakan bersama lambda, rujukan kaedah boleh menjadikan struktur bahasa lebih padat dan ringkas serta mengurangkan kod berlebihan.
API tarikh dan masa: Java 8 memperkenalkan API tarikh dan masa baharu untuk menambah baik pengurusan tarikh dan masa.
Kelas pilihan: NullPointerException yang terkenal ialah punca kegagalan sistem yang paling biasa. Projek Google Guava telah lama diperkenalkan Pilihan sebagai cara untuk menyelesaikan pengecualian penuding nol, tidak meluluskan kod yang dicemari oleh kod semakan nol dan mengharapkan pengaturcara menulis kod bersih. Diilhamkan oleh Google Guava, Optional kini merupakan sebahagian daripada perpustakaan Java 8.
Alat baharu: alatan kompilasi baharu, seperti: jjs enjin Nashorn, jdeps penganalisis pergantungan kelas.
Sumbernya berbeza: sleep() datang dari kelas Thread, wait() datang dari kelas Objek.
Kesan pada kunci penyegerakan adalah berbeza: sleep() tidak akan berfungsi sebagai kunci penyegerakan pada meja Jika utas semasa memegang kunci penyegerakan, sleep tidak akan membenarkan utas melepaskan kunci penyegerakan. wait() akan melepaskan kunci penyegerakan dan membenarkan utas lain memasuki blok kod disegerakkan untuk pelaksanaan.
Skop penggunaan yang berbeza: sleep() boleh digunakan di mana-mana sahaja. wait() hanya boleh digunakan dalam kaedah kawalan disegerakkan atau blok kawalan disegerakkan, jika tidak, IllegalMonitorStateException akan dibuang.
Kaedah pemulihan adalah berbeza: kedua-duanya akan menjeda urutan semasa, tetapi pemulihan adalah berbeza. sleep() akan disambung semula selepas masa tamat; wait() memerlukan urutan lain untuk memanggil notify()/nofityAll() objek yang sama untuk disambung semula.
Benang memasuki keadaan menunggu tamat masa (TIMED_WAITING) selepas melaksanakan kaedah sleep() dan memasuki keadaan sedia (READY) selepas melaksanakan kaedah yield().
Kaedah sleep() tidak mengambil kira keutamaan utas apabila memberi peluang kepada utas lain untuk dijalankan, jadi kaedah ini akan memberi peluang kepada utas keutamaan rendah untuk dijalankan; keutamaan yang sama atau keutamaan yang lebih tinggi Benang mempunyai peluang untuk dijalankan.
digunakan untuk menunggu urutan semasa ditamatkan. Jika utas A melaksanakan penyataan threadB.join(), maksudnya ialah: thread semasa A menunggu sehingga threadB ditamatkan sebelum kembali daripada threadB.join() dan terus melaksanakan kodnya sendiri.
Secara umumnya, terdapat tiga cara: 1) mewarisi kelas Thread 2) melaksanakan antara muka Boleh Dipanggil;
Antaranya, Thread sebenarnya melaksanakan antara muka Boleh Dijalankan. Perbezaan utama antara Runnable dan Callable ialah sama ada terdapat nilai pulangan.
run(): panggilan kaedah biasa, dilaksanakan dalam utas utama, bukan Urutan baharu akan dibuat untuk pelaksanaan.
start(): Mulakan thread baharu Pada masa ini, thread berada dalam keadaan sedia (runnable) dan tidak berjalan Setelah kepingan masa CPU diperoleh, kaedah run() mula dilaksanakan.
Suatu urutan boleh berada dalam salah satu keadaan berikut:
NEW:新建但是尚未启动的线程处于此状态,没有调用 start() 方法。
RUNNABLE:包含就绪(READY)和运行中(RUNNING)两种状态。线程调用 start() 方法会会进入就绪(READY)状态,等待获取 CPU 时间片。如果成功获取到 CPU 时间片,则会进入运行中(RUNNING)状态。
BLOCKED:线程在进入同步方法/同步块(synchronized)时被阻塞,等待同步锁的线程处于此状态。
WAITING:无限期等待另一个线程执行特定操作的线程处于此状态,需要被显示的唤醒,否则会一直等待下去。例如对于 Object.wait(),需要等待另一个线程执行 Object.notify() 或 Object.notifyAll();对于 Thread.join(),则需要等待指定的线程终止。
TIMED_WAITING:在指定的时间内等待另一个线程执行某项操作的线程处于此状态。跟 WAITING 类似,区别在于该状态有超时时间参数,在超时时间到了后会自动唤醒,避免了无期限的等待。
TERMINATED:执行完毕已经退出的线程处于此状态。
线程在给定的时间点只能处于一种状态。这些状态是虚拟机状态,不反映任何操作系统线程状态。
1)Lock 是一个接口;synchronized 是 Java 中的关键字,synchronized 是内置的语言实现;
2)Lock 在发生异常时,如果没有主动通过 unLock() 去释放锁,很可能会造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁;synchronized 不需要手动获取锁和释放锁,在发生异常时,会自动释放锁,因此不会导致死锁现象发生;
3)Lock 的使用更加灵活,可以有响应中断、有超时时间等;而 synchronized 却不行,使用 synchronized 时,等待的线程会一直等待下去,直到获取到锁;
4)在性能上,随着近些年 synchronized 的不断优化,Lock 和 synchronized 在性能上已经没有很明显的差距了,所以性能不应该成为我们选择两者的主要原因。官方推荐尽量使用 synchronized,除非 synchronized 无法满足需求时,则可以使用 Lock。
1.作用于非静态方法,锁住的是对象实例(this),每一个对象实例有一个锁。
public synchronized void method() {}
2.作用于静态方法,锁住的是类的Class对象,因为Class的相关数据存储在永久代元空间,元空间是全局共享的,因此静态方法锁相当于类的一个全局锁,会锁所有调用该方法的线程。
public static synchronized void method() {}
3.作用于 Lock.class,锁住的是 Lock 的Class对象,也是全局只有一个。
synchronized (Lock.class) {}
4.作用于 this,锁住的是对象实例,每一个对象实例有一个锁。
synchronized (this) {}
5.作用于静态成员变量,锁住的是该静态成员变量对象,由于是静态变量,因此全局只有一个。
public static Object monitor = new Object(); synchronized (monitor) {}
死锁的四个必要条件:
1)互斥条件:进程对所分配到的资源进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
2)请求和保持条件:进程已经获得了至少一个资源,但又对其他资源发出请求,而该资源已被其他进程占有,此时该进程的请求被阻塞,但又对自己获得的资源保持不放。
3)不可剥夺条件:进程已获得的资源在未使用完毕之前,不可被其他进程强行剥夺,只能由自己释放。
4)环路等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被 链中下一个进程所请求。即存在一个处于等待状态的进程集合{Pl, P2, …, pn},其中 Pi 等待的资源被 P(i+1) 占有(i=0, 1, …, n-1),Pn 等待的资源被 P0占 有,如下图所示。
预防死锁的方式就是打破四个必要条件中的任意一个即可。
1)打破互斥条件:在系统里取消互斥。若资源不被一个进程独占使用,那么死锁是肯定不会发生的。但一般来说在所列的四个条件中,“互斥”条件是无法破坏的。因此,在死锁预防里主要是破坏其他几个必要条件,而不去涉及破坏“互斥”条件。。
2)打破请求和保持条件:1)采用资源预先分配策略,即进程运行前申请全部资源,满足则运行,不然就等待。 2)每个进程提出新的资源申请前,必须先释放它先前所占有的资源。
3) Memecahkan syarat yang tidak boleh dipisahkan: Apabila sesuatu proses menduduki sumber tertentu dan kemudian memohon lagi untuk sumber lain tetapi tidak dapat memenuhinya, proses itu mesti mengeluarkan sumber yang didudukinya pada asalnya.
4) Putuskan keadaan menunggu gelung: laksanakan strategi peruntukan sumber yang teratur, nomborkan semua sumber dalam sistem secara seragam dan semua proses hanya boleh memohon sumber dalam bentuk peningkatan nombor jujukan.
Jika kita terus mencipta benang baru dalam kaedah pemprosesan, apabila kaedah ini kerap dipanggil, banyak benang akan dibuat, yang bukan sahaja akan menggunakan sumber sistem, tetapi juga mengurangkan kestabilan Berhati-hati untuk merosakkan sistem, dan anda boleh pergi terus ke bahagian kewangan untuk menyelesaikan bil.
Jika kita menggunakan kumpulan benang secara munasabah, kita boleh mengelakkan dilema untuk merosakkan sistem. Secara umum, menggunakan kumpulan benang boleh membawa faedah berikut:
threadFactory: Kilang digunakan untuk mencipta benang pekerja.
corePoolSize (bilangan utas teras): Apabila kumpulan utas berjalan kurang daripada utas corePoolSize, utas baharu akan dibuat untuk mengendalikan permintaan, walaupun utas pekerja lain melahu.
workQueue (queue): Satu baris gilir menyekat yang digunakan untuk mengekalkan tugasan dan menyerahkannya kepada urutan pekerja.
maximumPoolSize (bilangan maksimum utas): Bilangan maksimum utas yang dibenarkan dibuka dalam kumpulan benang.
pengendali (dasar penolakan): Apabila menambahkan tugas pada kumpulan benang, dasar penolakan akan dicetuskan dalam dua situasi berikut: 1) Status berjalan kumpulan benang tidak BERJALAN; pool telah mencapai bilangan maksimum benang dan disekat Apabila baris gilir penuh.
keepAliveTime (kekalkan masa hidup): Jika bilangan utas semasa dalam kumpulan benang melebihi corePoolSize, lebihan utas akan ditamatkan apabila masa terbiarnya melebihi keepAliveTime.
Polisi Gugur: Dasar Gugurkan. Strategi penolakan lalai terus membuang RejectedExecutionException. Pemanggil boleh menangkap pengecualian ini dan menulis kod pengendaliannya sendiri mengikut keperluan.
Polisi Buang: Dasar buang. Jangan lakukan apa-apa dan buang sahaja tugas yang ditolak.
DiscardOldestPolicy: Buang polisi tertua. Meninggalkan tugas tertua dalam baris gilir menyekat adalah bersamaan dengan tugas seterusnya dalam baris gilir yang akan dilaksanakan, dan kemudian menyerahkan semula tugas yang ditolak. Jika baris gilir menyekat adalah baris gilir keutamaan, maka strategi "jatuhkan tertua" akan menyebabkan tugasan keutamaan tertinggi digugurkan, jadi sebaiknya jangan gunakan strategi ini dengan baris gilir keutamaan.
CallerRunsPolicy: Dasar panggilan pemanggil. Laksanakan tugas dalam urutan pemanggil. Strategi ini melaksanakan mekanisme pelarasan yang tidak meninggalkan tugasan mahupun melemparkan pengecualian, tetapi menggulung semula tugasan kepada pemanggil (utas utama yang memanggil kumpulan utas untuk melaksanakan tugasan Memandangkan melaksanakan tugasan mengambil masa tertentu ,). jadi utas utama tidak boleh menyerahkan tugasan untuk sekurang-kurangnya satu tempoh masa, membenarkan masa kumpulan utas selesai memproses tugasan yang sedang dilaksanakan.
Senarai (pembantu yang baik untuk menangani pesanan): Antara muka Senarai menyimpan a. set bukan unik ( Terdapat beberapa elemen yang merujuk objek yang sama), objek yang dipesan.
Set (fokus pada sifat unik): Set pendua tidak dibenarkan dan berbilang elemen tidak akan merujuk objek yang sama.
Peta (pengguna profesional yang menggunakan Kunci untuk mencari): Gunakan storan pasangan nilai kunci. Peta mengekalkan nilai yang dikaitkan dengan Key. Dua Kekunci boleh merujuk kepada objek yang sama, tetapi Kekunci tidak boleh diulangi Kekunci biasa ialah jenis Rentetan, tetapi ia juga boleh menjadi sebarang objek.
ArrayList dilaksanakan berdasarkan tatasusunan dinamik di bahagian bawah, dan LinkedList dilaksanakan berdasarkan senarai terpaut di bahagian bawah.
Untuk mengindeks data mengikut indeks (kaedah dapatkan/set): ArrayList mencari terus nod pada kedudukan sepadan tatasusunan melalui indeks, manakala LinkedList perlu mula merentasi dari nod kepala atau nod ekor sehingga nod sasaran ditemui, jadi dari segi kecekapan, ArrayList adalah lebih baik daripada LinkedList.
Untuk sisipan dan pemadaman rawak: ArrayList perlu mengalihkan nod di belakang nod sasaran (gunakan kaedah System.arraycopy untuk mengalihkan nod), manakala LinkedList hanya perlu mengubah suai atribut nod seterusnya atau sebelumnya sebelum dan selepas nod sasaran, jadi dari segi kecekapan LinkedList adalah lebih baik daripada ArrayList.
Untuk sisipan dan pemadaman berurutan: Memandangkan ArrayList tidak memerlukan nod bergerak, ia lebih baik daripada LinkedList dari segi kecekapan. Inilah sebabnya mengapa terdapat lebih banyak ArrayLists dalam penggunaan sebenar, kerana kebanyakan penggunaan kami adalah sisipan berurutan.
Vector dan ArrayList adalah hampir sama Satu-satunya perbezaan ialah Vector menggunakan kaedah yang disegerakkan untuk memastikan keselamatan thread, jadi ArrayList mempunyai prestasi yang lebih baik dari segi prestasi.
Perhubungan yang serupa termasuk: StringBuilder dan StringBuffer, HashMap dan Hashtable.
Kami sedang menggunakan JDK 1.8 dan lapisan bawah terdiri daripada "pohon merah hitam senarai terpaut" , seperti yang ditunjukkan di bawah, dan sebelum JDK 1.8, ia terdiri daripada "senarai pautan tatasusunan".
Terutamanya untuk meningkatkan prestasi carian apabila konflik cincang teruk (senarai terpaut terlalu panjang Prestasi carian menggunakan senarai terpaut ialah O(n), manakala menggunakan pokok merah-hitam adalah O(log masuk).
Untuk sisipan, lalai adalah menggunakan nod senarai terpaut. Apabila bilangan nod pada kedudukan indeks yang sama melebihi 8 (ambang 8) selepas ditambah: jika panjang tatasusunan lebih besar daripada atau sama dengan 64, ia akan mencetuskan penukaran nod senarai terpaut kepada nod pokok merah-hitam ( treeifyBin); dan jika panjang tatasusunan kurang daripada 64, maka Senarai terpaut tidak akan dicetuskan untuk menukar kepada pokok merah-hitam, tetapi akan dikembangkan kerana jumlah data pada masa ini masih agak kecil.
Untuk penyingkiran, apabila bilangan nod pada kedudukan indeks yang sama mencapai 6 selepas dialih keluar, dan nod pada kedudukan indeks ialah nod pokok merah-hitam, penukaran nod pokok merah-hitam kepada nod senarai terpaut (untreeify) akan dicetuskan.
Kapasiti asal lalai ialah 16. Kapasiti HashMap mestilah 2 kepada kuasa Nth HashMap akan mengira 2 terkecil kepada kuasa Nth yang lebih besar daripada atau sama dengan kapasiti berdasarkan kapasiti yang kita lalui. Contohnya, jika 9 diluluskan, kapasiti adalah. 16.
HashMap membenarkan kunci dan nilai menjadi batal, Hashtable tidak. bukan.
Kapasiti asal lalai HashMap ialah 16 dan Hashtable ialah 11.
Peluasan HashMap ialah 2 kali ganda daripada asal, dan pengembangan Hashtable ialah 2 kali ganda daripada asal tambah 1.
HashMap tidak selamat untuk benang, Hashtable selamat untuk benang.
Nilai cincang HashMap telah dikira semula dan Hashtable menggunakan Kod cincang secara langsung.
HashMap mengalih keluar kaedah mengandungi dalam Hashtable.
HashMap mewarisi daripada kelas AbstractMap dan Hashtable mewarisi daripada kelas Kamus.
Kaunter program: peribadi benang. Ruang memori kecil yang boleh dianggap sebagai penunjuk nombor baris bagi kod bait yang dilaksanakan oleh utas semasa. Jika utas melaksanakan kaedah Java, pembilang ini merekodkan alamat arahan kod bait mesin maya yang sedang dilaksanakan jika utas melaksanakan kaedah Native, nilai pembilang adalah kosong.
Timbunan mesin maya Java: benang peribadi. Kitaran hayatnya adalah sama seperti benang. Tindanan mesin maya menerangkan model memori pelaksanaan kaedah Java: apabila setiap kaedah dilaksanakan, bingkai tindanan dicipta untuk menyimpan jadual pembolehubah tempatan, tindanan operan, pautan dinamik, kaedah keluar dan maklumat lain. Proses daripada panggilan kepada penyiapan pelaksanaan setiap kaedah sepadan dengan proses daripada menolak bingkai tindanan ke dalam tindanan mesin maya kepada mengeluarkannya.
Timbunan kaedah setempat: benang peribadi. Fungsi yang dimainkan oleh timbunan kaedah tempatan dan timbunan mesin maya adalah sangat serupa Satu-satunya perbezaan antaranya ialah timbunan mesin maya berfungsi untuk mesin maya untuk melaksanakan kaedah Java (iaitu, kod bait), manakala timbunan kaedah tempatan digunakan. oleh mesin maya Kepada perkhidmatan kaedah Asli.
Timbunan Java: perkongsian benang. Untuk kebanyakan aplikasi, timbunan Java ialah sekeping memori terbesar yang diuruskan oleh mesin maya Java. Timbunan Java ialah kawasan memori yang dikongsi oleh semua benang dan dicipta apabila mesin maya dimulakan. Satu-satunya tujuan kawasan memori ini adalah untuk menyimpan contoh objek, dan hampir semua kejadian objek memperuntukkan memori di sini.
Kaedah kaedah: Seperti timbunan Java, ia adalah kawasan memori yang dikongsi oleh setiap utas Ia digunakan untuk menyimpan maklumat kelas (kaedah pembinaan, definisi antara muka), pemalar, pembolehubah statik dan tepat dalam masa. penyusun yang telah dimuatkan oleh mesin maya Kod tersusun (kod bait) dan data lain. Kawasan kaedah ialah konsep yang ditakrifkan dalam spesifikasi JVM Di mana ia diletakkan, pelaksanaan yang berbeza boleh diletakkan di tempat yang berbeza.
运行时常量池:运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
String str = new String("hello");
上面的语句中变量 str 放在栈上,用 new 创建出来的字符串对象放在堆上,而"hello"这个字面量是放在堆中。
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
启动类加载器(Bootstrap ClassLoader):
这个类加载器负责将存放在
扩展类加载器(Extension ClassLoader):
这个加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载
应用程序类加载器(Application ClassLoader):
这个类加载器由sun.misc.Launcher$AppClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
自定义类加载器:
用户自定义的类加载器。
类加载的过程包括:加载、验证、准备、解析、初始化,其中验证、准备、解析统称为连接。
加载:通过一个类的全限定名来获取定义此类的二进制字节流,在内存中生成一个代表这个类的java.lang.Class对象。
验证:确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
准备:为静态变量分配内存并设置静态变量初始值,这里所说的初始值“通常情况”下是数据类型的零值。
解析:将常量池内的符号引用替换为直接引用。
初始化:到了初始化阶段,才真正开始执行类中定义的 Java 初始化程序代码。主要是静态变量赋值动作和静态语句块(static{})中的语句。
在什么时候?
在触发GC的时候,具体如下,这里只说常见的 Young GC 和 Full GC。
触发Young GC:当新生代中的 Eden 区没有足够空间进行分配时会触发Young GC。
触发Full GC:
对什么?
对那些JVM认为已经“死掉”的对象。即从GC Root开始搜索,搜索不到的,并且经过一次筛选标记没有复活的对象。
做了什么?
对这些JVM认为已经“死掉”的对象进行垃圾收集,新生代使用复制算法,老年代使用标记-清除和标记-整理算法。
在Java语言中,可作为GC Roots的对象包括下面几种:
Mark-Clear Algorithm
Tanda dahulu semua objek yang perlu dikitar semula, dan selepas penandaan selesai, semua objek yang ditanda akan dikitar semula secara seragam. Terdapat dua kelemahan utama: satu ialah masalah kecekapan, kecekapan proses penandaan dan pembersihan tidak tinggi, yang lain adalah masalah ruang, selepas penandaan dan pembersihan akan menghasilkan sejumlah besar serpihan ingatan tidak berterusan, terlalu banyak serpihan ruang mungkin; sebab Pada masa hadapan, apabila objek yang lebih besar perlu diperuntukkan semasa program sedang berjalan, memori bersebelahan yang mencukupi tidak dapat ditemui dan satu lagi tindakan kutipan sampah perlu dicetuskan terlebih dahulu.
Algoritma Salin
Untuk menyelesaikan masalah kecekapan, algoritma pengumpulan dipanggil "Menyalin" muncul, yang membahagikan memori yang tersedia kepada saiz mengikut kapasiti yang sama kepingan, hanya gunakan satu daripadanya pada satu masa. Apabila blok memori ini kehabisan, salin objek yang masih hidup ke blok lain, dan kemudian bersihkan ruang memori yang digunakan sekaligus. Dengan cara ini, keseluruhan separuh kawasan dikitar semula setiap kali, dan tidak perlu mempertimbangkan situasi yang rumit seperti pemecahan memori apabila memperuntukkan memori Hanya gerakkan penunjuk atas timbunan dan peruntukkan memori mengikut urutan cekap untuk dijalankan. Cuma kos algoritma ini adalah untuk mengurangkan memori kepada separuh daripada saiz asalnya, yang agak terlalu tinggi.
Tanda - Algoritma Penyusunan
Algoritma pengumpulan salinan akan melakukan lebih banyak operasi penyalinan apabila kadar kemandirian objek tinggi, dan kecekapan akan menjadi lebih rendah. Lebih penting lagi, jika anda tidak mahu membazirkan 50% ruang, anda perlu mempunyai ruang tambahan untuk jaminan peruntukan untuk menangani situasi yang melampau di mana semua objek dalam memori yang digunakan adalah 100% hidup Oleh itu, peruntukan seperti ini tidak boleh digunakan secara langsung dalam algoritma generasi lama.
Mengikut ciri-ciri generasi lama, seseorang telah mencadangkan satu lagi algoritma "Mark-Compact" Proses penandaan masih sama seperti algoritma "Mark-Compact", tetapi langkah-langkah seterusnya tidak berkaitan secara langsung. ke objek yang boleh dikitar semula, tetapi semua objek yang masih hidup dialihkan ke satu hujung, dan kemudian ingatan di luar sempadan hujung terus dibersihkan.
Algoritma Pengumpulan Generasi
Pada masa ini, mesin maya komersial semuanya menggunakan algoritma "Koleksi Generasi" untuk pengumpulan sampah idea hanyalah untuk membahagikan memori kepada beberapa blok mengikut kitaran hidup objek yang berbeza.
Secara amnya, heap Java dibahagikan kepada generasi baru dan generasi lama, supaya algoritma pengumpulan yang paling sesuai boleh digunakan mengikut ciri-ciri setiap generasi.
Dalam generasi baru, didapati bahawa sebilangan besar objek mati setiap kali semasa pengumpulan sampah, dan hanya beberapa yang bertahan Kemudian menggunakan algoritma salinan, dan hanya perlu membayar kos salinan yang kecil bilangan objek yang masih hidup untuk melengkapkan koleksi.
Pada generasi lama, kerana kadar survival objek adalah tinggi dan tiada ruang tambahan untuk menjamin peruntukannya, algoritma mark-clean atau mark-clean mesti digunakan untuk kitar semula.
Pada musim emas, tiga dan perak, saya percaya ramai pelajar sedang bersedia untuk menukar pekerjaan.
Saya telah meringkaskan artikel asal saya yang baru-baru ini: ringkasan asal, yang mengandungi analisis banyak soalan temu duga frekuensi tinggi, yang kebanyakannya saya temui semasa menemu duga dengan syarikat-syarikat besar Saya menyemak setiap satunya Apabila menganalisis soalan, mereka akan dianalisis secara mendalam mengikut piawaian yang lebih tinggi Anda mungkin tidak dapat memahaminya sepenuhnya selepas membacanya sekali sahaja, tetapi saya percaya bahawa anda akan memperoleh sesuatu dengan membacanya berulang kali.
Untuk lebih banyak pengetahuan berkaitan pengaturcaraan, sila lawati: Kursus Pengaturcaraan! !
Atas ialah kandungan terperinci [Kompilasi Hematemesis] Soalan dan jawapan temu duga frekuensi tinggi asas Java 2023 (Koleksi). Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!