JAVA知识点总结

本文深入探讨位运算符在编程中的应用,包括左移、右移和无符号右移运算符的特性与计算原理。此外,详细解析Java中线程安全、synchronized关键字、ReentrantLock、Condition、ReadWriteLock及StampedLock的使用方法,阐述多线程环境下并发编程的技巧与注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

<< >>运算符

<<表示左移移,不分正负数,低位补0; 
负数在机器中都是补码,因此左移右移都i是根据补码进行。
负数:r = -20 << 2
-20 的二进制原码 :1001 0100
-20 的二进制反码 :1110 1011
-20 的二进制补码 :1110 1100

左移两位后的补码:1011 0000 (逻辑右移,低位补零)
反码:1010 1111
原码:1101 0000
结果:r = -80

>>表示右移,如果该数为正,则高位补0,若为负数,则高位补1;
正数:r = 20 >> 2
20的二进制补码:0001 0100
向右移动两位后:0000 0101
结果:r = 5

负数:r = -20 >> 2
-20 的二进制原码 :1001 0100
-20 的二进制反码 :1110 1011
-20 的二进制补码 :1110 1100
右移两位后的补码:1111 1011 (负数高位补1)

反码:1111 1010
原码:1000 0101
结果:r = -5

>>>表示无符号右移,不管正数负数都是高位补0

注解

@Documented

@Documented注解表明这个注释是由 javadoc记录的,在默认情况下也有类似的记录工具。 如果一个类型声明被注释了文档化,它的注释成为公共API的一部分

@RequestParam

@RequestParam接收的参数,

  • 一可以是来自requestHeader中,即请求头
  • 二可以是**Content-Typeapplication/x-www-form-urlencoded 编码的内容,Content-Type默认为该属性,也就是body里的参数。**

所以RequestParam可以用于get,post,delete,put等各种方法。但是post,put不支持批量插入数据,如果改用 json 字符串来传值的话,类型设置为 application/json,点击发送的话,会报错,后台接收不到值,为 null

@RequestBody

@RequestBody接收的参数是来自requestBody中,即body。一般用于处理非 Content-Type: application/x-www-form-urlencoded编码格式的数据,比如:application/jsonapplication/xml等类型的数据。

GET请求中,因为没有HttpEntity,所以@RequestBody并不适用,一般用于Post,put方法

POST请求中,通过HttpEntity传递的参数,必须要在请求头中声明数据的类型Content-Type,否则默认是application/x-www-form-urlencoded

Mybatis

@Interceptor

作用:拦截某个方法,在此方法进行前,先进行某些行动。

@Intercepts({ @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = { Statement.class }) })

@signature,含有三个参数:
type :选择拦截的类,只有四种选择: Executor、StatementHandler、ParameterHandler 和 ResultSetHandler,也就是不是这四种类型的,它不会拦截。
method:选择的type中某类方法。
args:根据方法参数,确定唯一的方法。(因为有重载的存在)。
需要继承Interceptor接口,随后写入Mybatis配置文件中

拦截器顺序
1 不同拦截器顺序: Executor -> ParameterHandler -> StatementHandler -> ResultSetHandler
2 对于同一个类型的拦截器的不同对象拦截顺序: 在 mybatis 核心配置文件根据配置的位置,拦截顺序是 从上往下

<plugin interceptor="com.demo.mybatis.study.ResultSetHandlerInterceptor">
	<property name="dialect" value="com.demo.mybatis.study.ialect.MySQLDialect"/>
</plugin>

传入的Invocation是什么,其实就是我们要拦截对象的一个代理。在这里就是ResultSetHandler的代理类

@Intercepts({ @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = { Statement.class }) })
public class ResultSetHandlerInterceptor implements Interceptor {

	public Object intercept(Invocation invocation) throws Throwable {
		//从代理类中得到拦截对象
		ResultSetHandler resultSet = (ResultSetHandler) invocation.getTarget();

		//在这里,利用方法工具,得到此类resultSet某些字段,随后可以进行合法判断,或者写入日志等操作
		//假设这里有一大串代码

		return invocation.proceed();//invocation.proceed(),启动被拦截的方法。
	}
	
	// Mybatis只能拦截四种类型的接口:Executor、StatementHandler、ParameterHandler 和 ResultSetHandler, 通过@Intercepts和@Signature} 两个注解共同完成。
   //试想一下,开发人员在没有指明类型,或者随便写一个拦截点,比如Object,那Mybatis疯了,难道所有对象都去拦截,所以Mybatis会做一次判断,拦截点看看是不是这四个接口里面的方法,是的话,才会进行拦截
	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
		
	}
	
	//mybatis 配置文件读取properties,也就是上面写的<property name="dialect" value="com.demo.mybatis.study.ialect.MySQLDialect"/>
	public void setProperties(Properties properties) {
	}
}

Pluginwrap方法,判断依据是利用反射,获取这个拦截器ResultSetHandler的注解 InterceptsSignature,然后解析里面的值,它判断当前目标对象是否有实现对应需要拦截的接口,

  1. 如果没有则返回目标对象本身,也就是不拦截。
  2. 如果有,则拦截,返回一个代理对象。而这个代理对象的InvocationHandler正是一个Plugin。所以当目标对象在执行接口方法时,通过代理对象执行的,调用对应InvocationHandler的invoke方法,也就是Plugin的invoke方法。

这里invoke方法的逻辑是:把目标对象、要执行的方法以及方法参数封装成一个Invocation对象,再把封装好的Invocation作为参数传递给当前拦截器的intercept方法,也就是我们的拦截器中传入的参数Invocation。如果不需要拦截,则直接调用当前的方法。Invocation中定义了定义了一个proceed方法,其逻辑就是调用当前方法,所以如果在intercept中需要继续调用当前方法的话可以调用invocation的procced方法。

StatementHandler

StatementHandler为一个接口,有一个实现类:RoutingStatementHandler路由类,它内部持有一个private final StatementHandler delegate,构造函数会选择SimpleStatementHandler,PreparedStatementHandler,CallableStatementHandler其中一个实现,RoutingStatementHandler的query、update等方法都是调用delegate的方法
StatementHandler还有一个抽象类BaseStatementHandler,它共有3个实现类,也就是上面三个类。

  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }

  }

我们一般使用PreparedStatementHandler,他会处理PreparedStatement
BaseStatementHandler拥有一个BoundSql字段,直译就是受约束的SQL。BoundSql语句的解析主要是通过对#{}字符的解析,将其替换成?。最后均包装成预表达式供PrepareStatement调用执行。#{}中的key属性以及相应的参数映射,均保存至BoundSql的List<parameterMapping>属性中供最后的预表达式对象PrepareStatement赋值使用

@ControllerAdvice

ControllerAdvice有三个使用场景:

  1. 全局异常处理 @ExceptionHandler(value = MethodArgumentNotValidException.class)
  2. 全局数据绑定 @ModelAttribute 把参数添加到一个全局Model中,其中 value 属性表示这条返回数据的 key,而方法的返回值是返回数据的 value。用于添加一些通用属性,添加后,在controller中参数输入model,便可以根据key选择需要的属性。
@ControllerAdvice(annotations = {Controller.class, RestController.class})
public class WebControllerAdvice {
    @ModelAttribute(value = "param")
    public String m(){
        return "123435";
    }
    @ModelAttribute
    public void addAttributes(Model model) {
        model.addAttribute("msg", "欢迎访问 csdn.cd"); 
        HashMap<String, String> map = new HashMap<>();
        map.put("name", "li");
        map.put("age", "20");
        model.addAttribute("info", map);
    }
}
@RestController
public class HelthCheckController {
    @GetMapping
    public String health(@ModelAttribute("param") String param){
        return param;
    }
}
  1. 全局请求数据预处理 @InitBinder。将请求参数进行预处理,处理完在输入给controller。一般有两个作用:

1、当多个不同实体的多个参数名称相同时,使用前缀进行区分
2、使用@InitBinder格式化Date类型参数。在实际开发中后端某个参数是Date类型,而前端传回来的值是String类型,这时后端接参时不会自动转化为Date类型。

setFieldDefaultPrefix()的参数是url参数的前缀,然后@InitBinder("ccb")的ccb是传给controller的参数的前缀

@InitBinder("ccb")
public void b(WebDataBinder binder) {
    binder.setFieldDefaultPrefix("b.");
}
@InitBinder("cca")
public void a(WebDataBinder binder) {
    binder.setFieldDefaultPrefix("a.");
}
    @PostMapping("/book")
    public void addBook(@ModelAttribute("ccb") Book book, @ModelAttribute("cca") Author author) {
        System.out.println(book);
        System.out.println(author);
    }

传入的url
在这里插入图片描述

基础知识

堆、栈

引用变量:

当变量指向一个对象时,这个变量就被称为引用变量,比如A a =new A();

a就是引用变量,它指向了一个A对象,也可以说它引用了一个A对象。我们通过操纵这个a来操作A对象。 此时,变量a的值为它所引用对象的地址。A是new 的,所以是在堆上的。

在方法内部声明,当该方法运行完时,内存即被释放。

成员变量:

通过new建立,只要还有引用,那么就不会被GC。
从系统的角度来说,声明局部变量有利于内存空间的更高效利用(方法运行完即回收)。
成员变量可用于各个方法间进行数据共享。

在这里插入图片描述

java类型转换

我们经常看到将一个方法的返回值object强转为某个类型,其实这个object就是具体的某类型,因此才可以这样强转。运行下面代码,可以看到object其实为integer。

    public static void main(String[] args) {
        Object object=new Integer(155);
        Integer integer = (Integer) good();
        System.out.println("helloworld");

    }

    public static Object good() {
        return new Integer(15);
    }

在这里插入图片描述

list.toarray()  //不带参数时,转化为object[]
list.toarray(new Integer[list.size()]) //带参数时,转为为对用参数的类型(不能时string)

Object里拥有的方法、

public final native Class<?> getClass();

public native int hashCode();

public boolean equals(Object obj) {
        return (this == obj);
    }
    
protected native Object clone() throws CloneNotSupportedException;

public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
    
public final native void notify();

public final native void notifyAll();

public final native void wait(long timeout) throws InterruptedException;

public final void wait(long timeout, int nanos) throws InterruptedException {
        if (timeout < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos > 0) {
            timeout++;
        }

        wait(timeout);
    }
public final void wait() throws InterruptedException {
        wait(0);
    }
protected void finalize() throws Throwable { }

我们可以看到,因为java.lang.Object类里已有public方法.toString(),所以对任何严格意义上的java对象都可以调用下面方法。但在使用时要注意,必须保证object不是null值,否则将抛出NullPointerException异常。采用这种方法时,通常派生类会覆盖Object里的toString()方法。

1 Object object = getObject();
2 System.out.println(object.toString()); 

但是使用下面方法时,这是标准的类型转换,将object转成String类型的值。使用这种方法时,需要注意的是类型必须能转成String类型。因此最好用instanceof做个类型检查,以判断是否可以转换。否则容易抛出CalssCastException异常。比如下面就会报错

Object obj = new Integer(100);
String strVal = (String)obj;

使用Interger进行比较时,千万不能用==,因为==比较的是对象地址,在[-128,127]的数会被cache缓存,此时数值相同,==就是true,但是超过这个范围就是false.

向上转型

public class haha {
    public static void main(String[] args) {
        person person_1 =new student("hah",18);
        System.out.println(person_1.getClass());
        System.out.println(person_1.getClass().getDeclaredFields()[0]);
        System.out.println("helloworld");
    }
}

class person{
    private String name;

    public person(String name) {
        this.name = name;
    }
}

class student extends person{
    public Integer age;

    public student(String name, Integer age) {
        super(name);
        this.age = age;
    }

}

结果为

在这里插入图片描述

很明显,person_1还是student。

向上转型中,class并没有发生变化,字段,方法均没有减少,仅仅是如果直接在父类中访问子类的字段/方法,那么是过不了编译的。

& && | || 短路

String test=null;
System.out.println(test==null & test.equals(""));

结果报NullPointerException,说明&为非短路,也就是&两边的条件都会判断,而&&则是短路的,第一个为false则直接返回false,不判断第二个,同理|||也是一样的。

&&的执行优先级,即是执行顺序高于||

System.out.println( true || true && false );   //true

明显得出,&&是优于||执行的。
&&|| 多个连接起来时,则没有短路了,全部都会判断。

String test=null;
System.out.println( false && false || test.equals("") );  //NullPointerException
String test=null;
System.out.println( false && false && test.equals("") );  //false

明显,第一个判断到了最后一个条件,而仅仅&&连接时,发生短路。因此有些时候,我们需要用将||( )抱起来,保证短路发生。

享元

包装类型如ByteInteger都是不变类,因此,反复创建同一个值相同的包装类型是没有必要的。以Integer为例,如果我们通过Integer.valueOf()这个静态工厂方法创建Integer实例,当传入的int范围在-128~+127之间时,会直接返回缓存的Integer实例;对于Byte来说,因为它一共只有256个状态,所以,通过Byte.valueOf()创建的Byte实例,全部都是缓存对象。

Integer i = 1 ; 这种写法叫做装箱(基本类型与其对应的包装类型之间的赋值使用自动装箱与拆箱完成),而装箱操作是通过 Integer.valueOf(1) 完成的,所以:
Integer i = 1 等同于 Integer.valueOf(1)

toString和String.valueOf

在这里插入图片描述

在这里插入图片描述

1.相同点

  • Object中有public方法toString,因此任何一个对象都能调用toSring
  • valueOf的参数是Object,可以传入任何一个对象
  • 两者返回类型都是String,均能够将一个对象转化为String

2.区别

  • 当对象为null时,调用toString会报java.lang.NullPointerException
  • 当对象为null时,调用String.valueOf()会返回"null"字符串

关键字

transient

java 的transient关键字,你只需要实现Serilizable接口,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。
静态变量不管是不是transient关键字修饰,都不会被序列化。
因为静态变量在全局区,本来流里面就没有写入静态变量,我打印静态变量当然会去全局区查找,而我们的序列化是写到磁盘上的,所以JVM查找这个静态变量的值,是从全局区查找的,而不是磁盘上。被写到了全局区,其实就是方法区,只不过被所有的线程共享的一块空间。

集合

Linkedlist

Linkedlist双向链表,优点,增加删除,用时间很短,但是因为没有索引,对索引的操作,比较麻烦,只能循环遍历,但是每次循环的时候,都会先判断一下,这个索引位于链表的前部分还是后部分,每次都会遍历链表的一半 ,而不是全部遍历。
双向链表,都有一个previous和next, 链表最开始的部分都有一个fiest和last 指向第一个元素,和最后一个元素。增加和删除的时候,只需要更改一个previous和next,就可以实现增加和删除,所以说,LinkedList对于数据的删除和增加相当的方便。

多线程

线程安全

  1. 如果一个类被设计为允许多线程正确访问,比如对方法加锁,我们就说这个类就是线程安全的(thread-safe)。Java标准库的java.lang.StringBuffer也是线程安全的。
  2. 一些不变类,例如StringIntegerLocalDate,它们的所有成员变量都是final,多线程同时访问时只能读不能写,这些不变类也是线程安全的。
  3. 类似Math这些只提供静态方法,没有成员变量的类,也是线程安全的。

除了上述几种少数情况,大部分类,例如ArrayList,都是非线程安全的类,我们不能在多线程中修改它们。

而线程安全不是多线程协调运行,多线程协调运行的原则就是当条件不满足时,线程进入等待状态;当条件满足时,线程被唤醒,继续执行任务

synchronized

synchronized是Java语言层面提供的语法,所以我们不需要考虑异常,而ReentrantLock是Java代码实现的锁,我们就必须先获取锁,然后在finally中正确释放锁

static synchronized

  1. synchronized 锁的是this对象
  2. static synchronized 锁的是 当前类的Class对象

两个锁毫不相干,双方都不影响,static方法的锁是static method之间共享,static方法需要等到上一个static方法把锁释放才能获得锁。

wait()与notify()

notify()

  • notifyAll()将唤醒所有当前正在this锁等待的线程
  • notify()只会唤醒其中一个(具体哪个依赖操作系统,有一定的随机性)。
  • 通常来说,notifyAll()更安全。有些时候,如果我们的代码逻辑考虑不周,用notify()会导致只唤醒了一个线程,而其他线程可能永远等待下去醒不过来了。

wait()

  • wait()方法的执行机制非常复杂,它不是一个普通的Java方法,而是定义在Object类的一个native方法,必须在synchronized块中才能调用wait()方法。
  • 要始终在while循环中wait(),并且每次被唤醒后拿到this锁就必须再次判断:
while (queue.isEmpty()) {
    // 释放this锁:
    this.wait();
    // 重新获取this锁
}

死锁

由于Java的线程锁是可重入锁,所以,获取锁的时候,不但要判断是否是第一次获取,还要记录这是第几次获取。每获取一次锁,记录+1,每退出synchronized块,记录-1,减到0的时候,才会真正释放锁。

死锁产生的条件是多线程各自持有不同的锁,并互相试图获取对方已持有的锁,导致无限等待

避免死锁的方法是多线程获取锁的顺序要一致

线程的状态

         ┌─────────────┐
         │     New     │
         └─────────────┘
                │
                ▼
┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
 ┌─────────────┐ ┌─────────────┐
││  Runnable   │ │   Blocked   ││
 └─────────────┘ └─────────────┘
│┌─────────────┐ ┌─────────────┐│
 │   Waiting   │ │Timed Waiting│
│└─────────────┘ └─────────────┘│
 ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
                │
                ▼
         ┌─────────────┐
         │ Terminated  │
         └─────────────┘

当线程启动后,它可以在RunnableBlockedWaitingTimed Waiting这几个状态之间切换,直到最后变成Terminated状态,线程终止。

Blocked:

A thread that is blocked waiting for a monitor lock is in this state.

Thread state for a thread blocked waiting for a monitor lock. A thread in the blocked state is waiting for a monitor lock to enter a synchronized block/method or reenter a synchronized block/method after calling Object.wait.

WAITING
A thread that is waiting indefinitely for another thread to perform a particular action is in this state.

A thread is in the waiting state due to calling one of the following methods:

A thread in the waiting state is waiting for another thread to perform a particular action. For example, a thread that has called Object.wait() on an object is waiting for another thread to call Object.notify() or Object.notifyAll() on that object. A thread that has called Thread.join() is waiting for a specified thread to terminate.

ReentrantLock

ReentrantLock是Java代码实现的锁,我们就必须先获取锁,然后在finally中正确释放锁。同时需要 private final Lock lock = new ReentrantLock()

if (lock.tryLock(1, TimeUnit.SECONDS)) {
    try {
        ...
    } finally {
        lock.unlock();
    }
}

上述代码在尝试获取锁的时候,最多等待1秒。如果1秒后仍未获取到锁,tryLock()返回false,程序就可以做一些额外处理,而不是无限等待下去。

所以,使用ReentrantLock比直接使用synchronized更安全,线程在tryLock()失败的时候不会导致死锁

condition

private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition()//使用Condition时,引用的Condition对象必须从Lock实例的newCondition()返回

Condition提供的await()signal()signalAll()原理和synchronized锁对象的wait()notify()notifyAll()是一致的,并且其行为也是一样的

tryLock()类似,await()可以在等待指定时间后,如果还没有被其他线程通过signal()signalAll()唤醒,可以自己醒来:

while(queue.isEmpty()){
    if (condition.await(1, TimeUnit.SECOND)) {
    // 被其他线程唤醒后的代码
	} else {
    // 指定时间内没有被其他线程唤醒后的代码
	}
}

ReadWriteLock

private final ReadWriteLock rwlock = new ReentrantReadWriteLock();
private final Lock rlock = rwlock.readLock();
private final Lock wlock = rwlock.writeLock();
readwrite
readyesno
writenono

StampedLock 乐观读锁

读的过程中也允许获取写锁后写入!这样一来,我们读的数据就可能不一致,所以,需要一点额外的代码来判断读的过程中是否有写入,这种读锁是一种乐观锁。

StampedLock不可重入锁

private final StampedLock stampedLock = new StampedLock();  //需要初始化一个stampedLock

long stamp = stampedLock.writeLock(); // 获取写锁,stamp为写锁版本号

long stamp = stampedLock.tryOptimisticRead(); // 获得一个乐观读锁,不需要解锁
stampedLock.validate(stamp) // 检查乐观读锁后是否有其他写锁发生
stamp = stampedLock.readLock(); // 假如有写锁发生,获取一个悲观读锁

CAS Compare and Set(乐观写锁)

Atomic类是通过无锁(lock-free)的方式实现的线程安全(thread-safe)访问。它的主要原理是利用了CAS:Compare and Set。

public int incrementAndGet(AtomicInteger var) {
    int prev, next;
    do {
        prev = var.get();
        next = prev + 1;
    } while ( ! var.compareAndSet(prev, next));
    return next;
    //如果AtomicInteger的当前值是prev,那么就更新为next,返回true。
    //如果AtomicInteger的当前值不是prev,就什么也不干,返回false。
    //通过CAS操作并配合do ... while循环,即使其他线程修改了AtomicInteger的值,最终的结果也是正确的。
}

线程池

接口:ExecutorService

  • FixedThreadPool:线程数固定的线程池;
  • CachedThreadPool:线程数根据任务动态调整的线程池;
  • SingleThreadExecutor:仅单线程执行的线程池。
ExecutorService es = Executors.newFixedThreadPool(4);  //返回ThreadPoolExecutor

接口 :ScheduledExecutorService

ScheduledThreadPool :任务本身固定,需要反复执行的

ScheduledExecutorService ses = Executors.newScheduledThreadPool(4);

在这里插入图片描述

Executors.newFixedThreadPool(4) 返回的是ThreadPoolExecutorThreadPoolExecutor,他extends AbstractExecutorService,是个抽象类AbstractExecutorService,里面实现了submit方法

在这里插入图片描述

随后它又implement ExecutorService,他定义了线程池的一系列抽象方法。

Future 与callable

Runnable的方法没有返回值。Callable,它可以有返回值,并且是一个泛型接口,可以返回指定类型的结果。

在这里插入图片描述

// 定义任务:
Callable<String> task = new Task();   
// 提交任务并获得Future:
Future<String> future = executor.submit(task);	

线程池传入Runable,也会有结果,但是不是计算的结果,而是我们在submit中传入的参数,结果是固定的,在内部FutureTask将会其转为Callable

一个Future<V>接口表示一个未来可能会返回的结果,它定义的方法有:

  • get():获取结果(假如异步任务还没有完成,那么get()会阻塞,直到任务完成后才返回结果)。

  • get(long timeout, TimeUnit unit):获取结果,但只等待指定的时间;

  • cancel(boolean mayInterruptIfRunning):取消当前任务;

  • isDone():判断任务是否已完成。

  • cancel(boolean mayInterruptIfRunning)

    如果任务已经取消、已经完成或者其他原因不能取消,返回false。

    如果任务还没有启动就调用了cancel(true),任务将永远不会被执行。

    如果任务已经启动,参数mayInterruptIfRunning将决定任务是否应该中断执行该任务的线程,以尝试中断该任务。

FutureTask

AbstractExecutorService,里面实现了submit方法,可以看到返回的是FutureTask它是Future的唯一实现类

在这里插入图片描述

public class FutureTask<V> implements RunnableFuture<V> {
    private Callable<V> callable;  //
    public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       //它内部持有Callble,也就是会将Runnable转为Callble,然后那个Adapet其实就是把你传入的参数直接当作为结果输出,其实没什么意义
    }
    ···
}
public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

FutureTaskimplements RunnableFuture这东西同时实现了Runnable, Future,说明可以直接FutureTask交由Executor执行,并无需接住submit的返回值

Callale<Integer> task = new Task();
FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
executor.submit(futureTask);

FutureTask原理讲解与源码剖析

为什么并无需接住submit的返回值?

FutureTask是建立在堆中的。submit(task),new 了一个FutureTask。注意该函数的返回值,为无定界符,不能读,不能写,其实就是没有用。

在这里插入图片描述

该ftask中的Callable字段(子类,原本的Callable没有字段的)如图。execute()后,调用ftask的Callable.call(),即调用了task.run(),run()结束后将结果返回给task.outcome,而ftaskoutcome字段因为传入参数为null,从而永远为空。因为FutureTask是建立在堆中的,从而主线程才能读到task,更新Outcome值。

在这里插入图片描述

想一想如果FutureTask是存在栈里,那它怎么从别的线程返回结果给主线程呢。

FutureTask在Callable运行结束后,会将字段callable设置为null。

CompletableFuture

创建一个CompletableFuture是通过CompletableFuture.supplyAsync()实现的,它需要一个实现了Supplier接口的对象:

public interface Supplier<T> {
    T get();
}

用lambda语法简化了一下,直接传入Main::fetchPrice。紧接着,CompletableFuture已经被提交给默认的线程池执行了。

完成时,CompletableFuture会调用Consumer对象:

public interface Consumer<T> {
    void accept(T t);
}

异常时,CompletableFuture会调用Function对象:

public interface Function<T, R> {
    R apply(T t);
}

**使用Future,还需要再主线程主动使用get(),**因此可见CompletableFuture的优点是:

  • 异步任务结束时,会自动回调某个对象的方法;
  • 异步任务出错时,会自动回调某个对象的方法;
  • 主线程设置好回调后,不再关心异步任务的执行。

串行执行:thenApplyAsync,等待上一个线程执行成功后才执行此线程

并发执行:anyOf()可以实现“任意个CompletableFuture只要一个成功”,allOf()可以实现“所有CompletableFuture都必须成功”,用他们将两个CompletableFuture合并为一个新的CompletableFuture

ThreadLocal

static final ThreadLocal<String> ctx = new ThreadLocal<>();

ThreadLocal相当于给每个线程都开辟了一个独立的存储空间,各个线程的ThreadLocal关联的实例互不干扰。

void processUser(user) {
    try {
        threadLocalUser.set(user);
        step1();
        step2();
    } finally {
        threadLocalUser.remove();
    }
}

ThreadLocal一定要在finally中清除:当前线程执行完相关代码后,很可能会被重新放入线程池中,如果ThreadLocal没有被清除,该线程执行其他代码时,会把上一次的状态带进去。

volatile

Java内存模型告诉我们,各个线程会将共享变量从主内存中拷贝到工作内存,然后执行引擎会基于工作内存中的数据进行操作处理。

volatile作用:

  1. Lock前缀的指令会引起处理器缓存写回内存;
  2. 一个处理器的缓存回写到内存会导致其他处理器的缓存失效;
  3. 当处理器发现本地缓存失效后,就会从内存中重读该变量数据,即可以获取当前最新值。

这样针对volatile变量通过这样的机制就使得每个线程都能获得该变量的最新值。

volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。

反射

instanceof不但匹配指定类型,还匹配指定类型的子类

Integer n = new Integer(123);

boolean b1 = n instanceof Integer; // true,因为n是Integer类型
boolean b2 = n instanceof Number; // true,因为n是Number类型的子类

JVM为每一种基本类型如int也创建了Class,通过int.class访问。

// 获取String的Class实例:
Class cls = String.class;
// 创建一个String实例:
String s = (String) cls.newInstance();

它的局限是:只能调用public的无参数构造方法。带参数的构造方法,或者非public的构造方法都无法通过Class.newInstance()被调用。

Field

一个Field对象包含了一个字段的所有信息:

  • getName():返回字段名称,例如,"name"
  • getType():返回字段类型,也是一个Class实例,例如,String.class
  • getModifiers():返回字段的修饰符,它是一个int,不同的bit表示不同的含义,可以用其判断是否为Public、Final等。
  • f.get(Object)获取指定实例的指定字段的值,object为某个具体类。
  • f.set(p, "Xiao Hong"); 设置某个具体类的指定字段值。
  • get(),set()无法直接访问private字段,若要访问private,需要在之前加上这一句。
f.setAccessible(true);  //别管这个字段是不是public,一律允许访问
//setAccessible(true)可能会失败。如果JVM运行期存在SecurityManager,那么它会根据规则进行检查,有可能阻止setAccessible(true)。

Method

public Method getMethod(String name,Class<?>... parameterTypes)

一个Method对象包含一个方法的所有信息:

  • getName():返回方法名称,例如:"getScore"

  • getReturnType():返回方法返回值类型,也是一个Class实例,例如:String.class

  • getParameterTypes():返回方法的参数类型,是一个Class数组,例如:{String.class, int.class}

  • getModifiers():返回方法的修饰符,它是一个int,不同的bit表示不同的含义。

    String s = "Hello world";
    Method m = String.class.getMethod("substring", int.class); // 获取String substring(int)方法,参数为int:
    String r = (String) m.invoke(s, 6);    // 在s对象上调用该方法并获取结果:
    
    Stringr n = (String) m.invoke(null, "12345"); // 调用该静态方法并获取结果:
    

同样需要使用 m.setAccessible(true)调用非public方法。

使用反射调用方法时,仍然遵循多态原则:即总是调用实际类型的覆写方法(如果存在)。

调用构造方法

两种办法

  1. Class.newInstance()

    Person p = Person.class.newInstance();
    

    调用Class.newInstance()的局限是,它只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。

  2. Constructor

通过Class实例获取Constructor的方法:

  • getConstructor(Class...):获取某个publicConstructor
  • getDeclaredConstructor(Class...):获取某个Constructor
  • getConstructors():获取所有publicConstructor
  • getDeclaredConstructors():获取所有Constructor

注意Constructor总是当前类定义的构造方法,和父类无关,因此不存在多态的问题。

调用非publicConstructor时,必须首先通过setAccessible(true)设置允许访问。setAccessible(true)可能会失败。

类加载器ClassLoader

getClass().getClassLoader()getClassLoader():取得该Class对象的类装载器

一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance()方法就可以创建出该类的一个对象。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值