Java
基础知识
for(;;) 和 while(true) 的区别
虽然两者都能实现死循环,但是源码中都是选择 for(;;) 原因:
编译前 编译后
while (1) mov eax,1
test eax,eax
je foo+23h
jmp foo+18h
编译前 编译后
for (;;) jmp foo+23h
对比之下,for (;;) 指令少,不占用寄存器,而且没有判断跳转,比 while (1) 好。 也就是说两者在在宏观上完全一样的逻辑,但是底层完全不一样,for 相对于来说更加简洁明了。
equal 与==的区别
- 区别
- ==是运算符,equal 是方法,
- 比较基本类型:只能用==, 不能用 equal
- 比较包装类型:==比较的是内存地址,而 equal 比较的是值
- 比较对象:==和 equal 比较的都是内存地址,因为 equal 没有被重写,没有被重写的 equal 都是 object 的 equal 方法 ::: warning 注意 String(还有 Date,Integer)类型重写了 equals 方法,使其比较的是存储对象的内容是否相等,而不是堆内存地址。 :::
Class.this 和 this 的区别
当 inner class(内部类)必顺使用到 outer class(外部类)的 this instance(实例)时,或者匿名内部类要使用外部类的实例。
待续...
包装类
int VS Integer
初始化
- 1 int 类的变量初始为 0. Integer 的变量则初始化为 null.
- 2 Integer 变量必须实例化后才能使用,int 变量不需要 .
- 3 在 Int 是将值直接存储,Integer 对象是生成指针指向此对象。
比较
- Integer ? int
<br/>
只要两个变量的值是向等的,则结果为 true。 因为包装类 Integer 和基本数据类型 int 比较时,java 会自动拆包装为 int,然后进行比较,实际上就变为两个 int 变量的比较。 - new Integer() ? !new Integer(),
<br/>
结果为 false ! new Integer() 当变量值在 -128~127 之间时,非 new 生成的 Integer 变量指向的是 java 常量池中 cache 数组中存储的指向了堆中的 Integer 对象, 而 new Integer() 生成的变量指向堆中新建的对象,两者在内存中的地址不同; - new Interger() ? new Integer()
<br/>
false - !new Integer() ? !new Integer()
<br/>
如果两个值相等,且在区间 -128 到 127 之间则 true 只要不同或者有值在区间外就不相同
应用
Integer a = Integer.parseInt("1");
Integer b = Integer.parseInt("1");
synchronized(i)
"析构函数"
java 提供 finalize() 方法,垃圾回收器准备释放内存的时候,会先调用 finalize()。
- 特征
- 对象不一定会被回收。
- 垃圾回收不是析构函数。
- 垃圾回收只与内存有关。
- 垃圾回收和 finalize() 都是靠不住的,只要 JVM 还没有快到耗尽内存的地步,它是不会浪费时间进行垃圾回收的。
- 应对方法
- 主动调用 System.gc() 方法强制垃圾回收器来释放这些对象的内存。
- Java 1.1 通过提供一个 System.runFinalizersOnExit() 方法,不象 System.gc() 方法那样,System.runFinalizersOnExit() 方法并不立即试图启动垃圾回收器。而是当应用程序或 Applet 退出时,它调用每个对象的 finalize() 方法。
- 继承 finalize()
泛型
T 的位置
- 示例
static <T> void show(Collection<T> C) {
System.out.println("使用泛型 ------->" + C);
}
- 解释
- 第一处:静态方法不能直接引用类定义处的泛型,需要提前定义好泛型才能使用
- 第二处:指定 Collection 的元素类型为 T
边界通配符
<? extends T>和<? super T>
- 引例
- 这种情况下是可行的
Plate<Fruit> plate = new Plate<>(apple);
- 这种情况下,虽然苹果和水果有继承关系,但盘子间没有继承关系会报错
Plate<Fruit> plate = new Plate<Apple>(apple);
- 上下界通配符
- Apple -> Fruit -> Food
<? extends T>
可以是任何 T 的子类
Plate<? extends Food> plate = new Plate<Fruit>(apple);
<? super T>
可以是任何 T 的父类
Plate<? super Apple> plate = new Plate<Food>(fruit);
- 上下界通配符的副作用
- 上界
<? extends T>
不能往里存,只能往外取
Plate<? extends Food> plate = new Plate<Fruit>(apple);
Food a = plate.getItem();
// plate.setItem(food); 失效
- 下界<? super T>不影响往里存,但往外取只能放在 Object 对象里
Plate<? super Apple> plate = new Plate<Food>(fruit);
plate2.setItem(apple);
// Apple b = plate2.getItem(); 失效
// 元素的类型信息全部丢失。
::: tip 补充
- ?与 T 的区别
- 对编译器来说所有的 T 都代表同一种类型。比如下面这个泛型方法里,三个 T 都指代同一个类型,要么都是 String,要么都是 Integer。
- 但通配符
<?>没有这种约束,Plate<?>
单纯的就表示:盘子里放了一个东西,是什么我不知道。所以题主问题里的错误就在这里,Plate<? extends Fruit>里什么都放不进去。 :::
注解
- 注解会影响程序的编译和运行
- 注解本身只是一个标注,本身不用包含逻辑处理内容
作用范围
- RUNTIME:程序运行时起作用,例如:
@WebServlet
- SOURCE:编译时期作用,例如
@Override
@Target
指定注解针对的目标
- ElementType.Type 类、方法
- ElementType.Field 成员变量
- ElementType.METHOD 成员方法
- ElementType.PARAMETER 方法参数
- ElementType.CONSTRUCTOR 构造器
- ElementType.PACKAGE 包
- ElementType.ANNOTATION_TYPE 注解
@Retention
指定注解的保留域
- RetentionPolicy.SOURCE 源代码级别,由编译器处理,处理后不再保留
- RetentionPolicy.CLASS 注解信息保留到 class 文件中
- RetentionPolicy.RUNTION 由 jvm 读取,运行时使用
示例
- 注解类
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InitAnno {
}
- 被注解类
public class Foo {
@InitAnno
public void bar() {
System.out.println("进入了 bar 方法");
}
}
- Main
public static void main(String[] args) {
Class<?> clazz = Class.forName("Foo");
Annotation annotation = clazz.getAnnotation(InitAnno.class);
if (annotation == null) {
System.out.println("类前没有 InitAnno 注解");
}
Method[] methods = clazz.getMethods();
for (Method method : methods) {
boolean isInitAnno = method.isAnnotationPresent(InitAnno.class);
if (isInitAnno) {
method.invoke(clazz.getConstructor(null).newInstance(null), null);
}
}
}
多线程
实现
- 继承 Thread 类
MyThread thread = new MyThread();
thread.start();
- 实现 Runnable 接口
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
Thread thread = new Thread(new MyRunnable() {
@Override
public void run() {
for(int i = 0;i<100;i++) {
System.out.println("MyRunnable1");
}
}
});
thread.start();
::: tip 提示 Runnable 相比 Thread 耦合低,lambda 表达式更低 ::: 3. 实现 Callable 接口
Callable<String> callable = () -> {
System.out.println("进去了");
return "Hello";
};
FutureTask<String> featureTask = new FutureTask<>(callable);
Thread thread = new Thread(featureTask);
System.out.println(featureTask.get());
注意!Callable 与 Thread 没有直接关系,需要间接实现
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
public class FutureTask<V> implements RunnableFuture<V> {
private Callable<V> callable;
}
graph LR
A[Callable] -->B(FutureTask) --> C(RunnableFuture) --> D(Runnable) --> E(Thread)
常用方法
1. 休眠
thread.sleep(3000);
2. 合并
thread.join(3000);
3. 礼让
yield();
线程同步
每个 java 对象都有一个内置锁,内置锁会保护使用 synchronized 关键字修饰的方法 要调用该方法必须先获取锁,否则就处于堵塞状态
- 静态变量
public class SameRunnable implements Runnable {
private static int num = 0;
@Override
public synchronized void run() {
++num;
Thread.sleep(10);
System.out.println("第" + Thread.currentThread().getName() + "位访客是第" + num + "个");
}
}
- 静态代码块,锁定类 synchronized 关键字也能修饰代码块,以下例子也能有相同的效果
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(new Runnable() {
public void run() {
Test test = new Test();
test.print();
}
}).start();
}
}
public synchronized void print() {
synchronized (Test.class) { // 锁定类 (非静态可 this), 不能是类的实例
System.out.println("开始");
Thread.currentThread().sleep(1000);
System.out.println("结束");
}
}
}
- 锁定实例方法 ::: danger 注意 synchronized 关键字只是修饰共享的资源,下面的例子不能得到想要的效果 :::
public synchronized void print() {
System.out.println("开始");
Thread.currentThread().sleep(1000);
System.out.println("结束");
}
线程安全的单例模式
- 锁定类
public volatile class Runner {
private static Runner runner;
// 记得加关键字,防止多个线程访问时还没创建过对象
// 1. 🔒整个方法
public synchronized static Runner getRunner() {
if (runner == null) {
runner = new Runner();
}
return runner;
}
// 只锁代码块,不影响该方法内的其他业务
public static Runner getRunner() {
synchronized(Runner.class) {
if (runner == null) {
runner = new Runner();
}
}
return runner;
}
}
::: tip 其他锁定对象
- synchronized(runner): n 个空指针异常,不能锁空对象
- Integer i = Integer.parseInt("1"); synchronized(i): 也可以
- Integer a = Integer.parseInt("1"); Integer b = Integer.parseInt("1"); synchronized(i) 开启两个线程,若 a,b 值 (详情见 Integer 包装类) 相同,则线程安全,否则不安全 :::
voletile 关键字
- 引例
public class exam {
public static void main(String[] args) {
int num = 0;
int finalNum = num;
new Thread(() -> {
while (finalNum == 0) {
}
}).start();
TimeUnit.SECONDS.sleep(1);
num = 1;
System.out.println(num);
}
}
- 在该例子中,循环不会停止 new Thread 与 main 同时操作 num,他们分别从主内存复制到工作内存,对各自的工作内存中的数据进行操作, main 线程对 num+1,同步到主线程,但 new Thread 的任务未停止,没有与主内存同步,循环不会终止
- 若循环中加一个执行语句,循环会终止,工作内存会即使与主内存进行同步
当各个线程操作时,数据没有进行同步到主内存,产生错误 voletile 关键字使多个线程直接操作主内存,不在经过工作内存
待续...
JUC
特征
synchronized 与 Lock 对比
- synchronized 自动上锁解锁,Lock 手动上锁解锁
- synchronized 无法判断是否获取到锁,Lock 可以
- synchronized 拿不到锁会一直等待,Lock 不会
- synchronized 是关键字,jvm 实现,Lock 是接口,jdk 实现
- synchronized 是非公平锁,Lock 自由设置
公平锁:多个线程排队加锁
<br/>
非公平锁:不判断是否有其他等待线程,直接占用
基本操作
private Lock lock = new ReentrantLock();
lock.lock();
...
lock.unlock();
死锁
/*
* num = 1 的人拿到 chopsticks1,等待 chopsticks2
* num = 2 的人拿到 chopsticks2,等待 chopsticks1
* */
@Override
public void run() {
if (num == 1) {
synchronized (chopsticks1) {
Thread.sleep(100);
synchronized (chopsticks2) {
System.out.println("1 吃完了");
}
}
}
if (num == 2) {
synchronized (chopsticks2) {
synchronized (chopsticks1) {
System.out.println("2 吃完了");
}
}
}
}
生产者消费者
// 1. synchronize
class Container {
private int num = 5;
public synchronized void add() {
while (num != 0) {
this.wait();
}
num += 1;
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + "生产了 1 个,还有" + num + "个");
this.notify();
}
public synchronized void pop(int i) {
while (num == 0) {
this.wait();
}
num -= 1;
System.out.println(i + "购买了 1 个,还有" + num + "个");
this.notify();
}
}
// 2. Lock 要以 condition.await 和 condition.singal 代替 wait 和 notify
class Container2 {
private int num = 5;
ReentrantLock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void add() {
lock.lock();
while (num != 0) {
condition.await();
}
num += 1;
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + "生产了 1 个,还有" + num + "个");
condition.signal();
lock.unlock();
}
}
tryLock
class TimeLock {
private final ReentrantLock lock = new ReentrantLock();
public void tryLock() {
try {
if (lock.tryLock(3, TimeUnit.SECONDS)) {
System.out.println("3 秒内拿到了锁");
TimeUnit.SECONDS.sleep(5);
} else {
System.out.println("3 秒内没拿到锁");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock(); // 只能由上锁者去解锁
}
}
}
}
同时读写
- 对 ArrayList 读写操作同时存在会抛出异常
public class ReadAndWrite {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
//List<String> list = new Vector<>();
// List<String> list = Collections.synchronizedList(new ArrayList<>());
// List<String> list = new CopyOnWriteArrayList();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
TimeUnit.MILLISECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add("a");
System.out.println(Thread.currentThread().getName() + list);
}, String.valueOf(i)).start();
}
}
}
- 原因 ArrayList 不是线程安全的
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
- 解决方法
- 更换为 Vector
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
-
更换为 Collections.synchronizedList()
-
JUC: CopyOnWriteList
CopyOnWrite 实现了读写分离,当我们往一个容器添加元素的时候,不是直接给容器添加,而是先将当前容器复制一 份,向新的容器中添加数据,添加完成之后,再将原容器的引用指向新的容器。
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
计数器
- 减法计数器 countDownLatch 可以确保某个线程优先执行,当计数器清零在唤醒其他线程
public class CountDown {
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(80);
new Thread(() -> {
for (int i = 0; i < 100; i++) {
countDownLatch.countDown();
System.out.println(i);
}
}).start();
countDownLatch.await(); // 必须唤醒,且计数器要清零
for (int i = 0; i < 10; i++) {
System.out.println("main");
}
}
}
::: warning 注意 new CountDownLatch(80), countDownLatch.countDown(), countDownLatch.await() 必须配合使用,只要计数器没有清零,计数器不会停止,其他线程也不能唤醒 :::
- 加法计数器 试图唤醒当前线程,当加到一定数量成功唤醒,之后清零,再次累加循环
// 构造器
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
// Test
public class CyclicBarrier_ {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> {
System.out.println("放行");
});
for (int i = 0; i < 10; i++) {
final int temp = i;
new Thread(() -> {
cyclicBarrier.await();
}).start();
}
}
}
- 计数线程限流 限制同时进入的线程数
- 初始化
- 获得许可
- 释放
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(5); // 限制最多 5 人
for (int i = 0; i < 15; i++) {
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "进去了");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "出去了");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}).start();
}
}
读写锁
读写锁也是为了实现线程同步,只不过粒度更细,可以为读和写设置不同的锁
class Cache {
private Map<Integer, String> map = new HashMap<>();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void write(Integer id, String value) {
readWriteLock.writeLock().lock();
System.out.println("开始写入 ID: " + id);
map.put(id, value);
System.out.println("写入完成 ID: " + id);
readWriteLock.writeLock().unlock();
}
public String read(Integer id) {
readWriteLock.readLock().lock();
System.out.println("开始读取 ID: " + id);
String a = map.get(id);
System.out.println("读取完成 ID: " + id);
readWriteLock.readLock().unlock();
return a;
}
}
::: tip 补充 写入锁也叫独占锁,只能被 1 个线程占用,读取锁也叫共享锁,多个线程可以同时占用。 :::
线程池
- 基本使用
预先创建好一定数量的线程对象,存入缓冲池中,需要用的时候直接从缓冲池中取出,用完之后不要销毁,还回到缓冲池中,为了提高资源的利用率。优势:
- 提高线程的利用率
- 提高响应速度
- 便于统一管理线程对象
- 可以控制最大的并发数
public class ThreadPool_ {
public static void main(String[] args) {
// 单例
//ExecutorService executorService = Executors.newSingleThreadExecutor();
// 指定线程数量
//ExecutorService executorService = Executors.newFixedThreadPool(5);
// 缓冲线程池
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 60; i++) {
final int temp = i;
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName() + " " + temp);
});
}
executorService.shutdown();
}
}
- 构造函数
三个常用线程池都 return new ThreadPoolExecutor(); ThreadPoolExecutor 的构造函数如下
public ThreadPoolExecutor( int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
- corePoolSize: 核心池数量
- maximumPoolSize: 线程池容量上限,任务量增大时,线程池主动扩容
- keepAliveTime: 线程对象存活时间
- unit: 线程对象存活时间单位
- workQueue: 线程队列 (新的任务在队列中等候获取线程对象)
- threadFactory: 线程工厂创建线程对象
- handler: 拒绝策略
- 拒绝策略
RejectedExecutionHandler 是一个接口,均在 ThreadPoolExecutor 中实现
- AbortPolicyz:直接抛出异常
/**
* A handler for rejected tasks that throws a
* {@code RejectedExecutionException}.
*/
public static class AbortPolicy implements RejectedExecutionHandler {
- DiscardPolicy: 直接拒绝
/**
* A handler for rejected tasks that silently discards the
* rejected task.
*/
public static class DiscardPolicy implements RejectedExecutionHandler {
- DiscardOldestPolicy: 尝试与等待队列中最开始的任务争夺,不抛出异常
/**
* A handler for rejected tasks that discards the oldest unhandled
* request and then retries {@code execute}, unless the executor
* is shut down, in which case the task is discarded.
*/
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
- CallerRunsPolicy 由发起请求线程处理
/**
* A handler for rejected tasks that runs the rejected task
* directly in the calling thread of the {@code execute} method,
* unless the executor has been shut down, in which case the task
* is discarded.
*/
public static class CallerRunsPolicy implements RejectedExecutionHandler {
- 自定义线程池
executorService = new ThreadPoolExecutor(
4,
10,
2L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(20),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
4 种 workQueue 堵塞队列,用来存储等待执行的任务
- ArrayBlockingQueue: 基于数组的先进先出队列,创建时必须指定大小。
- LinkedBlockingQueue: 基于链表的先进先出队列,创建时可以不指定大小,默认值是 Integer.MAX VALUE。最大值。
- SynchronousQueue: 它不会保持提交的任务,而是直接新建一个线程来执行新来的任务。
- PriorityBlockingQueue: 具有优先级的阻塞队列。
Forkjoin
- 概念
- Forkjoin 是 JDK 1.7 后发布的多线程并发处理框架,功能上和 JUC 类似, JUC 更多时候是使用单个类完成操作,Forkjoin 使用多个类同时完成某项工作,处理上比 JUC 更加丰富,
- 本质上是对线程池的一种的补充,对线程池功能的一种扩展,基于线程池,
- 它的核心思想就是将一个大型的任务拆分成很多个小任务,然后由多个线程并发执行,最终将小任务的结果进行汇总,生成最终的结果。
- 基本使用 设置临界值,递归分配任务,知道任务不能被再分
public class FolkJoin_ extends RecursiveTask<Long> {
private Long start;
private Long end;
private Long temp = 200_0000L;
public FolkJoin_(Long start, Long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if ((end - start) < temp) {
Long sum = 0L;
for (Long i = start; i < end ; i++) {
sum += i;
}
return sum;
} else {
Long avg = (start + end) / 2;
FolkJoin_ tast1 = new FolkJoin_(start, avg);
FolkJoin_ tast2 = new FolkJoin_(avg, end);
tast1.fork();
tast2.fork();
return tast1.join()+tast2.join();
}
}
}
Main.java
Long start = System.currentTimeMillis();
ForkJoinPool folkJoinPool = new ForkJoinPool();
FolkJoin_ task = new FolkJoin_(0L, 10_0000_0000L);
folkJoinPool.execute(task);
System.out.println(task.get() + " " + (System.currentTimeMillis() - start) / 1000.0);