JavaSE底层知识

面向对象

深拷贝,浅拷贝

  • Person p2 = new Person(p1)是调用了构造器,对属性进行复制,并且new了一个新地址,p1 p2指向不同,互不影响,是深拷贝
  • 而Person p2 = p1则是直接让p2指向p1,p1的改变影响p2,是浅拷贝

为何要封装

  • 有时候需要对属性进行crud操作,倘若有Animal类,有属性legsNumber,腿子的数量就不能是单数,通过自定义set方法可以解决逻辑问题
  • 账户类中有余额属性,在set时也需要进行判断,可以自定义set,而把属性本身设为private,防止直接访问修改(当然你要说用反射那当我没说)

匿名对象

  • 用完一次之后就等垃圾回收
  • 如果Person p1 = new Person(“zhangsan”,15, new Pet(“年年”))这样new出来的Pet对象是不会被回收的

什么是javaben

  • public类
  • private属性
  • 有get set方法
  • 有public无参构造器

this关键字和super关键字

  • this就是本类,super就是上一级父类
  • this.可以调属性:this.name = name;
  • this.可以调方法:在参数多的构造器中this.参数少的构造器
  • 当子类定义有参的构造方法时,super(形参列表)必须与父类构造一致
  • this()和super()都是构造方法,并且都要求放在第一行,但是this()和super()只会同时出现其中一个,一般用this(形参)针对父类构造,super(形参)针对子类构造

示例:

class Person{
	private String name;
	private int age;
	
	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	
}
class Student extends Person{
	private int id;
	
	public Student(String name, int age, int id) {
		//形参要求齐全
		super(name, age);//先super()构造
		this.id = id;
	}
}

继承的权限

  • 权限大小:public>protected>缺省>private
  • 每“出一层”,可见范围少一个

同包建立任意类(子类,普通类):private不可见了

跨包建立子类:缺省也不可见了

跨包建立普通类(非子类):protected也不可见了

实际开发中一般只用public和private

重载和重写(覆写)

  • 重载:形参类型or数量不同,构造器就是重载
  • 重写:保证子父类结构一致,给与子类方法独特的个性
  • 属性也是可以重写的,参数名相同,可以更改类型

重写后如何调用父类方法?

class Super{
	public void tell(){
		System.out.println("我是你爹");
	}
}
 
class Sub extends Super{
	public void tell(){//覆写
		System.out.println("***********");
		super.tell();//调用父类的被覆写的方法
		super.tell();//可以调用多次,并且位置不限
		System.out.println("我是你儿子");
	}
}
 
public class Test {
public static void main(String[] args) {
	Sub A = new Sub();
	A.tell();
}
}

编译看左,运行看右(虚拟方法调用)

  • 编译看左,运行看右:(虚拟方法调用)

  • Person A = new Man(); 左边决定能用什么属性or方法,右边决定用谁的属性or方法

  • 在编译期只能调用父类中声明的方法,但在执行期实际执行的是子类重写的方法

  • 多态性是一个运行期行为,编译器看不出来

instanceof方法

如果 对象属于xx类,那么对象 instansof XX就返回ture

System.out.println(new AllComments() instanceof Object);//true
System.out.println(new Father() instanceof Son);//false

包装类

例如:Object.Number.Integer

Boolean

  • 可传String和boolean

  • 忽略大小写

      System.out.println(new Boolean("sadasd"));//fanlse
      System.out.println(new Boolean(""));//fanlse
      System.out.println(new Boolean("TrUe"));//true
      System.out.println(new Boolean(true));//true
    

Float

  • 可传double float String

示例:

Float A = new Float(12.3);
//double自动转成float
Float A2 = new Float(12.3f);
Float B = new Float("12.3");
//String自动转成float
Float C = new Float("12.3aaa");
//编译正常,运行报错,Float没有定义怎么处理字母

默认值问题

包装类的默认值是null 如Integer:null,普通类的默认值如int:0

自动装箱

可以直接把基本数据类型赋值给包装类

Integer A = 1;
相当于
Integer A = new Integer(1);

自动拆箱

Integer A = 1; 
int a = A;
相当于
Integer A = new Integer(1);
int a = A.intValue();

String到包装类or普通数据类型(parse)

  • 为什么不能直接Integer A = (Integer)(new String(“1111”)呢

  • 因为Integer和String两个类没有子父类关系

  • 应该使用parseXxx方法来解析

      Integer A = Integer.parseInt((new String("111")));//111
      Double B = Double.parseDouble(A.toString());//111.0
      Float C = Float.parseFloat("111");//111.0
      Boolean D = Boolean.parseBoolean("true");//D就是true
    

toString

  • Object中的默认toString是输出地址值
  • 自定义类需要重写toString

static

  • 不用new就可以用,随类的加载而加载
  • static属性:既可以调用,也可以对象调用
  • static方法:只能调用static属性和方法,不能访问普通方法;不能使用this和super

final和abstract

final

  • 定义不能继承的类

  • 定义不能重写的方法

  • 定义常量属性

  • final和abstract不相同

  • 可以先声明后定义

      public final class NoInherit{ }
      //不能继承的类
      public static final void tell(){}
      //不能重写的方法
      public static final int a;
      //静态常量,final一般都是跟static连用
      
      	final a;
      a = 1;
      //此后a就不能变了
      //a=1之前不能调用,会报错
    

abstract

  • abstract不能修饰private方法、static方法、final方法
  • 抽象类的抽象方法更多是一种规范
  • new 抽象类 or 接口 的时候要用代码块包裹所有需要重写的abstract类,如:

代码块

  • 执行顺序:静态代码块—>普通代码块—>构造器
  • 且静态代码块只随类加载一次
  • 有继承关系时,先调用父类的静态代码块

示例:

public class PersonTest{
public static void main(String[] args) {
new Person();
new Person();
}
}
class Person{
	
	public Person(){
		System.out.println("构造器");
	}
	{
		System.out.println("代码块");
	}
	static{
		System.out.println("静态代码块");
	}
}

内部类和JVM类的加载规则

在定义类的时候可以把另一个类作为class类型的属性

JVM的类加载规则 :

  • static类型的属性和方法,在类加载的时候就会存在于内存中。
  • 要想使用某个类的static属性和方法,那么这个类必须要加载到JAVA虚拟机中。
  • 非静态内部类不能有静态成员变量或静态方法

静态属性or方法不用new就加载,而非静态的内部类需要new才能实例化,显然是矛盾的,所以非静态内部类不能有静态成员变量或静态方法

异常处理

异常处理分类

  • Error错误
    Java虚拟机无法解决的严重问题,如JVM系统内部错误、资源耗尽等严重情况。如:StackOverflowError栈溢出 和 OutOfMemoryError堆溢出OOM ,一般不编写针对性的代码进行处理(异常处理代码),而只是对原本代码进行改动
  • Exception异常
    其他编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理,不改动原有代码,只增加异常预处理

Exception异常体系结构

编译时异常checked

  • 运行时异常:都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
  • 运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。

运行时异常unchecked

  • 非运行时异常 (编译异常):是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,
  • 一般情况下不自定义检查异常。

异常处理

3个异常处理语句

printStackTrance是最常用的

e.toString():  获得异常种类和错误信息
e.getMessage():获得错误信息
e.printStackTrace():在控制台打印出异常种类,错误信息和出错位置等

try-catch-finally

  • catch可以有多个,也可以把多个catch合并为一个父类的catch
  • finally中不建议有return,但分情况,下面说明几种特殊的return的情况,注意区分基本数据类型和引用数据类型的差异
try中catch中finally中返回值情况(基本数据类型)返回值情况(引用类型)
返回finally中的入栈值同左
有且异常(执行)执行finally,但返回catch中入栈的return值执行finally且返回finally
无异常则finally中,有异常则catch中无条件执行且返回finally的值

throws抛异常

这种处理方法并没有真正解决Exception,并且如果调用方法,throws会层层向上抛,一般引用别人定义好的方法时,可将其方法throws出来的Exception进行try-catch处理,这样可以更灵活

throw主动抛异常

throw new RuntimeException() ,有时候为了保证逻辑的完整性,需要抛异常来阻止操作,并返回throw异常交给拦截器来处理

  • throw编译时异常(throw new Exception("")),其方法要throws对应的编译时异常
  • throw运行时异常(throw new RuntimeException("")),其方法不需要throws

举个例子:throw运行时异常,不用thorws

class Student {
	int id;
	public Student(int id){
		if(id>0){
		this.id = id;
		}
		else{
			throw new RuntimeException("id不能为负数");
	}
	}}

再举个例子:throw编译时异常,需要throws

class Student {
	int id;
	public Student(int id) throws Exception{
		if(id>0){
		this.id = id;
		}
		else{
			throw new Exception();
		}
	}
}

自定义异常类

自定义异常类需要满足

  • 继承于某一个异常类
  • 空参构造和String msg构造
  • 指定serialVersionUID(指定序列化版本)

示例:

class MyException extends RuntimeException{
	static final long serialVersionUID = -123456789L;
	
	public MyException(){}
	
	public MyException(String msg){
		super(msg);
	}
}

在使用上和普通异常类是完全一样的

多线程

程序 进程 线程

  • 程序:静态的代码,没有运行起来的才叫程序
    eg:文件夹中的所有文件
    整个文件夹内包括的所有静态代码的集合就是程序

  • 进程:是程序的一次运行or正在运行的一个程序,是一个动态的过程,并且有自身的产生、存在、消亡的过程——生命周期
    eg:运行中的QQ、运行中的LOL、运行中的360安全卫士

  • 线程:进程可以细分为线程,是程序内部的一条执行路径。如果一个进程可以同时并行执行多个线程,就是支持多线程的。
    线程是调度和执行的单位,有独立的运行栈和程序计数器,线程切换的开销小
    一个进程中多个线程共享相同的资源,因此可以进程间通信更高效,但也带来了安全隐患
    eg:同时扫漏洞、扫毒、清理垃圾就是多线程,多线程增加了cpu利用率
    eg:图形化界面的基本都是多线程
    eg:QQ同时有很多人互相发消息

  • 一个java应用程序java.exe至少有三个线程:main()主线程、gc()垃圾回收线程、异常处理线程,还可以有内存监控 操作日志

CPU的单核和多核

  • 单核CPU:实际上是一种假的多线程,表象的多线程实际上是快速在不同线程之间切换(时分复用),是一种并发

eg:同一个进程,使用单核cpu,单线程比多线程更快,因为没有线程切换时间

  • 多核CPU:可以实现并行,且

eg:现在的很多手机都是8核心,但这8核并不是完全相同,当手机写备忘录时,调用功耗小功能更弱的核,玩游戏时调用更强但功耗大的核

并发、并行

并发:一个CPU同时执行多个线程

并行:多个CPU同时执行多个线程

多线程的创建和使用

传统多线程

  • 线程具有优先级1~10,且是一个统计学上的优先级,满足大数定律
  • Thread类实现了Runnable接口,可以创建线程
  • 开发中常用的是线程池,线程池减少了线程的创建和销毁的用时
  • 在自己手写多线程时,可能出现逻辑正确,但有几个输出特例不符合多线程,这可能是由于多线程运行到屏幕输出的不稳定时间差造成的

Thread类的常用方法

  • void start(): 启动线程,并执行对象的run()方法
  • void run(): 线程在被调度时执行的操作
  • String getName(): 返回线程的名称
  • void setName(String name):设置该线程名称
  • static Thread currentThread(): 返回当前线程对象。在Thread子类中就 是this,通常用于主线程和Runnable Callable实现类
  • static void yield():线程让步。暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程 若队列中没有同优先级的线程,忽略此方法。
  • join() :当某个程序执行流中调用其他线程的 join() 方法时,调用线程将 被阻塞,直到 join() 方法加入的 join 线程执行完为止,低优先级的线程也可以获得执行。该方法需要try-catch
  • static void sleep(long millis):(指定时间:毫秒) 令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。 抛出* InterruptedException异常。该方法需要try-catch
  • stop(): 强制线程生命期结束,不推荐使用
  • boolean isAlive():返回boolean,判断线程是否还活着

sleep yield join wait notify的区别放在后面的线程同步中

Runnable接口:作为Thread对象的构造形参

  1. 相比直接使用Thread,可以实现“多继承”
  2. 线程的开启仍需要实现Thread类
  3. new Thread( new MyThread() ) .start来启动

示例:

class MyrunThread implements Runnable {
 
    public void run() {
        for (int i = 1; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}
public class RunnableTest {
    public static void main(String[] args) {
 	//一步写法
        new Thread(new MyrunThread()).start();
        //两步写法
        MyrunThread m1 = new MyrunThread();
        Thread M1 = new Thread(m1);
        //M1可以调用Thread类的所有方法
        M1.start();
    }
}

Callable接口:借助FutureTask解耦合

  1. 与Runnable相比,Callable功能更强大,可以有返回值,可以抛异常
  2. Runnable是重写run()方法,Callable是重写call()方法
  3. 返回值的功能需要借助FutureTask类,并且从FutureTask类对象.get()获取返回值
  4. 因为即便是用Callable接口实现,也存在不需要返回值的情况,因此引入FutureTask类来解耦合
  5. new Thread( new FutureTask( new MyThread() ) ).start来启动

实现Callable接口,重写call()方法

class Mythread implements Callable<Integer>{
 
    @Override
    public Integer call() throws Exception {
int  sum = 0;
        for (int  i = 1 ; i<100 ; i++){
            if(i%4==0){
                System.out.println(i);
                sum+=i;
            }
        }
        return sum;
        //这里的返回值是不能通过Thread直接获取的
        //需要利用FutureTask
    }
}

借助FutureTask,实现Thread

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
       Mythread M1 = new Mythread();//实现Callable接口实例
       FutureTask F1 = new FutureTask(M1);//传入FutureTask中
       Thread T1 = new Thread(F1);//传入Thread中
        T1.start();//启动
        System.out.println(F1.get());
 
        //start运行call方法,但是不一定必须接收返回值
    }
}

线程池

与传统相比的优点

  • 传统的线程实现方式有包含了线程创建 线程销毁 的时间,而线程是可以复用的,因此引入线程池,把线程创建和销毁 改为 用后归还,不销毁,重复利用资源,提高线程利用率,提高程序的响应速度
  • 便于统一管理线程对象
  • 可以控制最大并发量

线程池模型——银行

柜台窗口:线程,且不销毁
等待区:等待队列
窗口不够用时:增加线程
等待区没有座位时:拒绝策略——报异常或交给某个线程去左
窗口空闲太多:减少线程

线程池创建

JUC并发工具包提供了ThreadPoolExecutor创建线程池,ThreadPoolExecutor有8个参数,分别为:

  • corePoolSize:核心线程数——永不关闭的柜台窗口数
  • maximumPoolSize:最大线程数——最多的窗口总数
  • keepAliveTime:线程存活时间——非核心线程过多长时间没有接到任务就销毁——需要设置单位unit
  • unit:TimeUnit.SECONDS 或TimeUnit.MICROSECONDS等等都可以
  • BlockingQueue :等待队列,new ArrayBlockingQueue<>(capacity:10)这里capacity的值设为10
  • ThreadFactory:线程工厂——无需自己创建,直接调用Executors.defaultThreadFactory()
  • RejectedExecutionHandler:拒绝策略——new ThreadPoolExecutor.AbortPolicy()被拒绝的任务的处理程序,抛出一个 RejectedExecutionException

创建好之后用ExecutorService类对象来接收,之后就可以直接调用了
例如:利用lambda表达式

for(){
executorService.execute( ()->{ 线程业务逻辑 } );
}//线程池会自动调整当前线程数
executorService.shutdown();//关闭线程池

参考:
拒绝策略实现步骤

线程池详解

线程同步

线程同步的同步意为:协同步调,按预定的先后次序进行运行
防止因为并发导致的数据读写错误

线程的生命周期

锁机制

同步的实现离不开锁机制,以下几种常见的锁
参考资料

死锁

参考:死锁的预防
死锁的预防一般有:
hashCode指定顺序Lock接口超时放弃

手写死锁:

class Mythread implements Runnable{
public void run(){
......
synchronized(锁A){
//A锁套B锁
    synchronized(锁B){
     ......
    }
}
......
}
}
 
class Youthread implements Runnable{
public void run(){
......
synchronized(锁B){
//B锁套A锁
    synchronized(锁A){
     ......
    }
}
......
}
}

如果是锁进行了递归调用,一个锁也是可能产生死锁的

悲观锁

  • 认为总有线程会操作自己正在操作的数据
  • 使用场景:频繁写入;保证安全性
  • 锁实现:synchronized关键字、Lock接口

乐观锁

  • 认为没有线程会操作自己正在操作的数据
  • 使用场景:多读少写;保证效率高
  • 锁实现:CAS算法

读锁(S锁)(共享锁)

  • 只读,例如SELECT
  • 加S锁后,别的事务只能再加S锁而不能加X锁,但加锁者本身可以再加X锁

若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。

写锁(X锁)(排他锁)

  • 写,例如INSERT、UPDATE 或 DELETE
  • 加X锁后,其他任何事务不能再加任何S锁和X锁

若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。

公平锁

  • 多个共用同一把锁的线程有序排队,并按排队顺序上锁
  • private ReentrantLock lockkk = new ReentrantLock(true);叫公平锁

手写公平锁:

private  ReentrantLock lockkk = new ReentrantLock(true);//公平锁
。。。。。。。。。
    public void run(){
        for(int i = 1;i<=100;i++){
            lockkk.lock();//上公平锁lockkk
            System.out.print(Thread.currentThread().getName());
            Depot(this.account,1000);
            //输出内容
            try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }//加上sleep效果更明显,防止因为屏幕输出时间差造成的误导
 
            lockkk.unlock();//释放公平锁lockkk

//此后该调用lock的线程进入排队的末尾,不与其他线程争抢资源
        }
}

非公平锁

  • 多个共用同一把锁的线程有序排队,但不按顺序上锁,仍像synchronized一样靠抢
  • private ReentrantLock lockkk = new ReentrantLock(false);
    private ReentrantLock lockkk = new ReentrantLock();都是非公平锁

volatile关键字

参考资料

  • 只能修饰变量,如:private volatile static int phoneNumber = 0;
  • 对变量的写操作不依赖于当前值,若是++i , i+=5这种依赖于原有值的操作则可能仍然会有错误
  • 该变量是独立的:该变量没有包含在具有其他变量的不变式中
  • volatile不需要加锁,比synchronized更轻量级,不会阻塞线程
  • synchronized既能保证可见性,又能保证原子性,而volatile只能保证可见性,无法保证原子性

synchronized关键字

为什么用对象来充当监视器?:
 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,要获取内置锁,否则处于阻塞状态。

同步方法(粗粒度锁)

  • 比同步代码块更笨重,开销更大
  • 用this上锁,因此也有潜在的风险
  • 同步方法(粗粒度锁):
      A、修饰一般方法: public synchronized void method(){…},获取的是当前调用对象 this 上的锁;
      B、修饰静态方法: public static synchronized void method (){…},获取当前类的字节码对象上的锁。

同步代码块(细粒度锁)

  • 对象监视器:同一个对象同一把锁,处于同一个同步机制里
  • 监视器用this指的是当前类对象,
    例如:用Runnable接口实现的线程,run()方法写为synchronized,那么这个锁就是Runnable的对象,如果用这一个对象作为形参创建了多个线程,就是线程同步的
    例如:用extends直接继承Thread,run()方法写为synchronized,那么new几个线程就加了几把锁,失去了同步功能

Lock接口

Lock接口可以主动在任意位置lock和unlock,所以更加灵活

ReentrantLock接口

public class ReentrantLock implements Lock, java.io.Serializable

  • 提供公平锁机制,而synchronized是非公平的
  • boolean tryLock() 方法:如果拿不到锁就返回false,不会像synchronized一直等待
  • void lock() 上锁,且需要void unlock() 主动解锁
  • 当发生异常时,synchronized会自动释放锁,而Lock接口需要在try-catch-finally中手动释放锁,防止死锁
  • 在性能上来说,如果线程竞争资源不激烈时,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时 Lock 的性能要远远优于 synchronized

例子

private  ReentrantLock locker = new ReentrantLock(true);//公平锁
。。。。。。。。。
    public void run(){
        for(int i = 1;i<=100;i++){//模拟多线程
            locker.lock();//上公平锁lockkk
          try{执行的可能出错的代码
          }catch(Exception e){
			 e.printStackTrace();
		}finally{
			locker.unlock()
		}
        }
    }

sleep yield join wait notify

首先确定一点,CPU资源调度和线程同步不是同一个概念,即便是没有监视器(没有锁),也存在CPU资源调度问题(也有sleep和yield方法);
而只有加锁之后才有wait和notify来操作线程的执行

CPU资源角度

  • sleep和yield都不释放锁,因此影响的是cpu资源调度
  • 都是静态方法,因此可以无关监视器,无关同步

sleep线程休眠

  • 不释放锁,同监视器的线程仍然阻塞
  • 稳定让渡cpu资源,不同监视器的线程可以执行
  • 静态方法,Thread.sleep(123123)随处可用

yield线程让步

  • 不释放锁,同监视器的线程仍然阻塞
  • 不稳定让渡cpu资源,重新争夺cpu资源,可能没有明显效果
  • 其他监视器的线程需:优先级>=调用yield的线程,才能争夺cpu资源

线程同步角度

  • wait notify notifyAll 都是需要写在同步代码块之中的,针对已经获取了Obj锁进行操作,都不是静态方法
  • 从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){…}语句块内。
  • 从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁

wati

  • 释放CPU的同时释放锁
  • 进入等待池,等待其他线程使用obj.notify()唤醒
  • 调用obj.wait()后直接释放资源
  • 因为是写在synchronized代码块中,所以调用obj.wait()之后还需等其他线程obj.notifyAll()之后才执行wait之后剩余的代码

notify notifyAll

  • 在相同监视器下(相同锁下),前者是随机唤醒一个线程,后者是唤醒所有线程
  • notify和notifyAll的最终效果都是从等待池唤醒一个,但notify可能会导致两个线程都挂起,造成阻塞(所有线程都wait进入了等待池,没有线程再执行唤醒)

对象内部锁

其实,每个对象都拥有两个池,分别为锁池(EntrySet)和(WaitSet)等待池。

  • 锁池:假如已经有线程A获取到了锁,这时候又有线程B需要获取这把锁(比如需要调用synchronized修饰的方法或者需要执行synchronized修饰的代码块),由于该锁已经被占用,所以线程B只能等待这把锁,这时候线程B将会进入这把锁的锁池。
  • 等待池:假设线程A获取到锁之后,由于一些条件的不满足(例如生产者消费者模式中生产者获取到锁,然后判断队列为满),此时需要调用对象锁的wait方法,那么线程A将放弃这把锁,并进入这把锁的等待池。

如果有其他线程调用了锁的notify方法,则会根据一定的算法从等待池中选取一个线程,将此线程放入锁池。
如果有其他线程调用了锁的notifyAll方法,则会将等待池中所有线程全部放入锁池,并争抢锁。

锁池与等待池的区别:等待池中的线程不能获取锁,而是需要被唤醒进入锁池,才有获取到锁的机会。

参考资料1

参考资料2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值