【Java从入门到项目开发实战】阅读记录

记录阅读该书过程中的一些重要点

调试:
掌握调试快捷按钮,理解图标都代表了什么含义:分别有

  • 跳转到当前执行的代码处
  • Step Over是单步执行,快捷键F8,表示一行一行地执行代码,如果代码上有方法,则不会进入方法定义的内部
  • Step Into是单步进入执行,快捷键F7,表示一行一行地执行代码,如果当前行有方法,则进入方法内部一步步执行代码,一般进入自定义方法而不进入开发平台自带的方法
  • Force Step Into,强制步入执行,Alt + Shift + F7,表示能进入任何方法,
  • Step Out步出执行,从步入的方法没退回到方法调用,此时方法已执行完毕,只是还没有完成赋值
  • Drop Frame回退断点,回退到代码行前面的最近一个断电处
  • Run to Cursor运行到光标处
  • Evaluate Expression,计算表达式
  • 恢复往下执行的程序Resume Program,F9,表示往下执行代码,直到下一个断点或代码执行完毕
  • Mute Breakpoints设置断点失效,单机该按钮,所有断电将变为灰色,断点失效,再次单机该按钮,所有断电恢复其功能。

在Java中使用final关键字来修饰常量,声明方式和变量类似。一般常量名全部使用大写字母来表示,常量只能被赋值一次,如果再次对常量赋值就会出现错误。

在Java中通过抽象类和接口两种形式来体现面向对象的抽象。
抽象类是abstract修饰的类,有以下几点需要注意:

  • abstract修饰的类为抽象类,该类不能创建对象
  • abstract修饰的方法为抽象方法,该方法不能有方法体
  • 有抽象方法的类必须是抽象类,但抽象类中可以没有抽象方法
  • 子类继承抽象类后,必须重写抽象类中的抽象方法
  • 在抽象类中可以有除了抽象方法以外的普通方法
public abstract class Animal {
    // 抽象类
    public abstract void eat(); // 抽象方法
    public abstract void sleep(); // 抽象方法
    public void run() {
        // 普通方法
        System.out.println("动物可以跑");
    }
}
某个类继承该抽象类,必须重写抽象类中的抽象方法,
public class Skm extends Animal {
    @Override
    public void eat() {
        
    }
    
    @Override
    public void sleep() {
        
    }
}

接口是一个规范,负责定义规则,通过interface关键字进行定义。

  • 接口中所有属性默认为被public static final修饰
  • 接口中所有方法默认为被public abstract修饰
    • 区分接口和抽象类,这是Java中两种完全独立的抽象机制,虽然都不能被实例化,且都包含抽象方法,但在定义、设计目的和使用规则上有着本质的区别
    • 接口中方法默认被public abstract修饰,只是说明接口在定义行为时非常“纯粹”和“抽象”,并不代表就是抽象类
      暂时无法在飞书文档外展示此内容
  • 使用implements实现接口,类和接口的关系叫做实现
  • 类可以实现多个接口
public interface AnimalInterface {
    double PI = 3.14; // 静态常量
    void eat(); // 抽象方法
}

public class SkmInterface implements AnimalInterface {
    @Override
    public void eat() {
        
    }
}

了解内部类,这里了解一下匿名内部类,这是一种没有构造器的类,匿名内部类一般用于继承其他类或实现接口,其不需要增加额外的方法,只是对继承方法的实现或重写。

public interface IAnimal {
    void show();
}

public class AnimalAnon {
    public static IAnimal getMessage() {
        return new IAnimal { // 匿名类
            @Override
            // 重写方法
            public void show() {
                System.out.println("匿名内部类实现接口方法");
            }
        };
    }
}

了解Lambda表达式,

@FunctionalInterface // 注解要求该接口只能有一个抽象方法
public interface LambdaInterface {
    // 定义接口
    int sum(int a, int b); // 定义接口的方法,该方法有两个参数,并带有返回值
}

// TestLambda类
public class TestLambda {
    // 利用Lambda表达式实现两个参数带返回值
    // 接口方法通过Lambda表达式直接实现,省去了接口的实现类
    LambdaInterface lambdaInterface =  (int a, int b) -> {
        return a + b;
    };
    int sum = lambdaInterface.sum(3, 5); // 调用已实现的方法
    System.out.println("a + b = " + sum);
}

Lambda表达式还有一个较为常用的功能就是对集合进行遍历,

异常:
Java对异常的处理是通过5个和异常相关的关键字组合来完成的,这5个关键字分别是try, catch, finally, throw, throws
异常抛出主要使用throws和throw两个关键字来实现,异常抛出功能更多的是捕捉底层出错行为,为开发人员提供帮助信息。
throw则是显式抛出异常。

理解注解与注释:注解时给机器看的注释,注释是给程序员看的代码提示。当编译器生成类文件时,注解被嵌入字节码中,在程序运行时可以用来获取标注的内容。

JVM相关知识:
JVM本质上是应用程序,可以执行保存在字节码文件中的指令。JVM是Java语言可移植特性的基础。任何操作系统安装上JVM后,字节码文件(.class)就可以在该系统上被执行。Java使用JVM屏蔽掉操作系统的底层信息,使java编译程序编译成字节码文件,就可以在不同的操作系统上直接执行。

另外了解Java的内存结构:即JVM在执行程序的过程中会把它管理的内存划分为若干不同的数据区域。JVM运行时的数据区主要包括堆、栈、方法区和程序计数器等。JVM的优化问题主要在线程共享的数据区中,即堆和方法区。JVM提供一个后天线程对内存进行检测和控制,一般在CPU或者内存不足时自动进行垃圾回收。
JVM的常用参数:

  • -Xms 用于设置堆最小空间的大小,默认为物理内存的1/64,
  • -Xmx 用于设置堆最大空间的大小,默认为物理内存的1/4
  • -XX NewSize设置新生代最小内存空间
  • -XX MaxNewSize设置新生代最大内存空间
  • -XX PermSize设置永久代最小内存空间,默认为物理内存的1/64
  • -XX MaxPermSize设置永久代最大内存空间,默认为物理内存的1/4
  • -Xss 设置每个线程的堆栈大小

了解JVM常用的监控工具:

  • jps命令,获取当前JVM正在运行的java进程名和对应进程号
  • JConsole,是一款可视化的JVM监控软件,执行jconsole命令即可启动该软件

在一个项目中可以独立运行的程序片段叫做线程。例如最简单的售票问题,就要用到多线程解决。
多线程执行,本质是CPU快速的在多个线程之间切换执行。线程的5种状态:新建、就绪、运行、阻塞及死亡。

创建线程:3种常用方法,

  1. 继承java.lang包下的Thread类
// 使用java.lang下的Thread类
public class MyThread extends Thread();

public class TestMyThread {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}
  1. 实现java.lang下的Runnable接口,实现接口中的run()抽象方法。
public class MyRunnable implements Runnable {
    @Override
    public void run() {}
}

实现Runnable接口之后使用方法类似,

public class MyRunnable implements Runnable {
    @Override
    public void run() {}
}

public class TestMyThread {
public static void main(String[] args) {
// 利用Runnable接口实现类
MyRunnable myRunnable = new MyRunnable();
myRunnable.run();
}
}
3. 匿名类创建线程

public class TestMyThread {
    public static void main(String[] args) {
        // 匿名创建线程
        Thread thread = new Thread() {
            // 重写run方法
            @Override
            public void run() {
                super.run();
            }
        };
    }
}

调用run()方法只是普通方法,还是在主线程里执行。

Java的线程分为守护线程用户线程两大类:

  • 一般用Thread类创建的线程在默认情况在都属于用户线程
  • 启动线程之前执行setDaemon(true)会变成守护线程。守护线程是服务线程,常见的守护线程是GC垃圾回收器。

死锁的情形是这样的:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。Java死锁产生的4个必要条件:

  • 互斥使用
  • 不可抢占
  • 请求和保持
  • 循环等待
    当然,在发生死锁的情况下,打破上述任何一个条件,就可以让死锁消失。
    死锁现象出现后,不会出现异常,也不会出现提示,只是所有的线程都会出现阻塞状态,无法继续。
    死锁示例:
// 创建一个LockUtils接口
public interface LockUtils {
    // 创建一个China对象
    Object china = new Object();
    // 创建一个foreign对象
    Object foreign = new Object(); 
}

// 模拟死锁产生的类
class DieLock extends Thread {
    private boolean flag;
    
    public DieLock(boolean flag) {
        this.flag = flag;
    }
    
    @Override
    public void run() {
        if (flag) {
            // 同步代码快调整
            synchronized (LockUtils.china) {
                System.out.println("dieLock1 线程持 china 锁");
                synchronized (LockUtils.foreign) {
                    System.out.println("dieLock1 线程持 foreign 锁");
                }
            }
        }else {
            synchronized (LockUtils.foreign) {
                System.out.println("dieLock2 线程持 foreign 锁");
                synchronized (LockUtils.china) {
                    System.out.println("dieLock2 线程持 china 锁");
                }
            }
        }
    }
}

public class TestDieLock implements LockUtils {
    public static void main(String[] args) {
        DieLock dieLock1 = new DieLock(true);
        DieLock dieLock2 = new DieLock(true);
        dieLock1.start();
        dieLock2.start();
    }
}

线程池:最顶层接口是Executor,常用线程池接口是ExecutorService,提供了静态方法用于简化线程池的配置。

  • newSingleThreadExecutor()
  • newFixedThreadPool()
  • newCachedThreadPool()
  • newScheduledThreadPool()

之前的线程任务执行完毕后无法获取返回的结果。如果想要获取返回的结果,就需要实现callable和future接口。例如下面一个示例:利用Callable和Future接口模拟异步线程并获取返回值。

// 声明TestCallable类,创建求和子线程
public class TestCallable implements Callable<Integer> {
    private int sum;
    @Override
    // 实现call接口
    public Integer call() throws Exception {
        System.out.println("Callable子线程开始求和..");
        Thread.sleep(1000);
        // 求和
        for (int i = 0; i < 30000; i++) {
            sum = sum + i;
        }
        System.out.println("Callable 子线程求和结束");
        return sum;
    }
}

// 声明TestCallableFuture类,创建主线程,获取子线程返回的结果
public class TestCallableFuture {
    // 主线程
    public static void main(String[] args) {
        // 创建线程池
        ExecutorService executor = Executors.newSingleThreadExecutor();
        // 创建子线程
        TestCallable testCallable = new TestCallable();
        // 提交任务并获取执行结果
        Future<Integer> future = executor.submit(testCallable);
        // 关闭线程池
        executor.shutdown();
        try {
            Thread.sleep(1000);
            System.out.println("正在做其他事");
            if (future.get() != null) {
                // 输出获取的结果
                System.out.println("future.get() -->" + future.get());
            }else {
                System.out.println("future.get()未获取结果");
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("工作完成");
    }
}

多线程示例:

public class Main { 
    
        public static void main(String[] args) { 
                // 多线程执行
                // 使用列表存储多线程
                long start = System.currentTimeMillis();
                Vector<Thread> vectors = new Vector<Thread>();
                for (int i = 0; i < 10; i++) {
                    // 创建线程
                    Thread t = new MyThread(i+"");
                    t.start();
                    vectors.add(t);
                }
                // 主线程
                for (Thread thread : vectors) {
                    thread.join();
                }
                long end = System.currentTimeMillis();
                System.out.println("共执行了: " + (end - start) * 1.0 / 1000 + "秒");
        } 
}

class MyThread extends Thread {
    private String taskNum;
    MyThread (String taskNum) {
        this.taskNum = taskNum;
    }
    // 重写run方法
    @SneakyThrows
    @Override
    public void run() {
        TimeUnit.MILLISECONDS.sleep(2000);
        System.out.println(taskNum + "号机器生产鱼罐头");
    }
}

锁是用来管理和控制多线程访问共享资源的一种机制,在一般情况下,锁能够防止多个线程同时访问共享资源。很多锁,可重入锁,读写锁,互斥锁,悲观锁,乐观锁,公平锁,锁消除,独享锁,共享锁等。

可重入锁:一个线程获取锁之后,可以无限次执行该锁锁住的程序。ReentrantLock和Synchronized都是可重入锁。
例如利用synchronized模拟可重入锁,

package com.example.demo1.chapter1;

public class TestSynchronized implements Runnable{

    @Override
    public void run() {

    }
    // get或者set方法
    public synchronized void get() {
        // 获取当前线程的id
        System.out.println(Thread.currentThread().getId());
        set();
    }

    public synchronized void set() {
        System.out.println(Thread.currentThread().getId());
    }

    public static void main(String[] args) {
        //
        TestSynchronized reentrantLock = new TestSynchronized();
        // 创建3个线程,表示可重入
        new Thread(reentrantLock).start();
        new Thread(reentrantLock).start();
        new Thread(reentrantLock).start();
    }
}

不得不提一下:synchronized和reentrantlock的区别,
都是可重入锁,但定位和使用方式截然不同,核心区别查看:

维度synchronizedreentrantlock
底层实现JVM 层面,基于 Monitor(对象监视器)JDK 层面,基于 AQS(队列同步器)
加锁/解锁自动加锁与释放(代码块结束或异常时)必须手动调用 lock() 和 unlock()
公平性支持仅支持非公平锁支持公平锁与非公平锁(默认非公平)
高级特性无(仅基础互斥)支持可中断、超时获取、多条件变量(Condition)
使用复杂度简单,不易出错较复杂,需严格在 finally 中释放锁

下面深度讲解:

  1. 语法与底层实现的差异
  • synchronized:是 Java 的内置关键字。它的加锁和解锁完全由 JVM 在字节码层面通过 monitorenter 和 monitorexit 指令自动管理。无论代码正常执行完毕还是抛出异常,JVM 都会保证锁被正确释放,因此使用起来非常简洁且不容易出错。
  • ReentrantLock:是 java.util.concurrent.locks 包下的一个类。它需要开发者显式地调用 lock() 获取锁,并在业务逻辑结束后调用 unlock() 释放锁。特别注意:为了防止业务代码抛异常导致锁无法释放(进而引发死锁),unlock() 必须放在 finally 代码块中执行。(需要显式调用lock取锁以及释放锁)
  1. 灵活性与高级功能的碾压
    这是ReentrantLock最大的优势所在,提供了synchronized无法实现的高级特性
  • 公平锁支持:synchronized 只能是非公平锁(即抢锁时不按排队顺序,谁抢到算谁的,效率高但可能导致线程饥饿)。ReentrantLock 可以通过构造方法 new ReentrantLock(true) 来开启公平锁模式,让线程严格按照 FIFO(先进先出)的顺序来获取锁。
  • 响应中断(可中断锁):当线程在等待 synchronized 锁时,是无法被中断的,只能一直傻等。而 ReentrantLock 提供了 lockInterruptibly() 方法,允许正在等待锁的线程响应外部的中断信号,从而放弃等待,这在处理死锁或取消长时间任务时非常有用。
  • 超时获取锁:ReentrantLock 提供了 tryLock(long timeout, TimeUnit unit) 方法。线程可以尝试在指定时间内获取锁,如果超时还没拿到就自动放弃,避免了无限期的阻塞。
  • 多条件变量(Condition):synchronized 配合 wait()/notify() 只能有一个等待队列,唤醒时不够精准。ReentrantLock 可以绑定多个 Condition 对象,实现分组唤醒(例如在生产者-消费者模型中,可以精准地只唤醒“生产者”或只唤醒“消费者”),极大地提升了线程协作的灵活性。
  1. 性能表现
    在JDK1.6之后,官方对synchronized进行了大量的优化,(引入偏向锁、轻量级锁、锁升级机制等),使得两者在大多数常规并发场景下的性能已非常接近。

接这里机会,详细介绍一下synchronized锁,参考教程https://blog.csdn.net/2402_87277969/article/details/158318259

读写锁:允许在同一时刻多个线程进行访问,并且写线程访问时其他读线程和写线程都会被阻塞。
读写锁维护一对读锁和写锁,通过将读锁和写锁分离,提升并发性能。
使用读写锁:读的时候获取读锁;写的时候获取写锁,写锁获取时,后续其他操作(读写)都会被阻塞,在写锁释放后继续执行后续操作。

CAS:比较和交换,是实现无锁并发,乐观锁的核心机制。
CAS假设所有线程访问共享资源时不会出现冲突,线程不会出现阻塞状态。
CAS的优点是在竞争小的时候系统开销小。
CAS的缺点主要有以下两点,面试时候如果说3点是哪3点:

  • ABA问题
    最著名且隐蔽的缺陷。CAS在更新值时,只检查“当前内存中的值”是否等于“预期原值”。如果相等,则认为该值从未被修改过。
    • 例如:一个共享变量的初始值是A,线程1准备将其修改为C,但在执行CAS之前被挂起,此时线程2将该值从A修改为了B,紧接着线程3又将B改回了A,当线程1恢复执行时,发现当前值依然是A,于是CAS操作成功,实际上,这个值在后台已被其他线程偷偷修改过了2次。
    • 潜在风险:在某些业务场景下哎,这种中间状态的变化可能会导致严重的数据逻辑错误
    • 解决方案:引入版本号(或时间戳),每次变量更新时,不仅修改变量的值,还要把版本号加 1。这样上述过程就会变成 1A -> 2B -> 3A,线程1通过对比版本号就能察觉出值发生过变化。Java 的 JUC 包中提供了 AtomicStampedReference 类来专门解决此问题。
  • 循环时间长,CPU开销大
    CAS是一种典型的自旋锁spin lock机制,由于单次 CAS 操作不一定能成功,它通常需要配合死循环来实现。如果 CAS 更新失败,线程会不断进行自旋重试,直到竞争不激烈时才能修改成功。在高并发场景下,这可能导致长时间的重试,持续消耗 CPU 资源,带来极大的性能开销。
    即当线程数不断增加时,性能明显下降,因为所有线程的执行都需要占用CPU时间。
  • 只能保证一个共享变量的原子操作
    CAS 仅能对单个共享变量进行原子操作。当需要对多个共享变量或一整个代码块的逻辑同时进行操作以保证线程安全时,单纯的 CAS 无法提供原子性保障。在这种情况下,通常需要使用传统的互斥锁(如 synchronized),或者将这多个共享变量封装到一个新对象中,再利用 AtomicReference 类对这个整体对象进行 CAS 操作。

例如CAS规避ABA的问题示例:使用AtomicStampedReference解决CAS操作中的ABA问题。
引入版本号Stamp,每次修改不仅要比对值,还要比对版本号,只要版本号变了,哪怕值一样,CAS也会失败。

package com.example.demo1.chapter14;


import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * 模拟CAS规避ABA的问题
 */
public class TestCAS {
    public static void main(String[] args) {
        // 创建一个原子引用AtomicStampedReference对象,给定初始值创建一个对象,初始化了stamp版本号为1
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference(100, 1);
        new Thread(() -> {
            try {
                // 休眠1秒,确保Thread2先读取版本号,
                TimeUnit.SECONDS.sleep(1);
                
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
            //
            System.out.println(Thread.currentThread().getName() + "第一次版本号:" + atomicStampedReference.getStamp() + "; 当前值:" + atomicStampedReference.getReference());
            //
            atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            //
            System.out.println(Thread.currentThread().getName() + "第二次版本号:" + atomicStampedReference.getStamp() + "; 当前值:" + atomicStampedReference.getReference());
            atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "第三次版本号:" + atomicStampedReference.getStamp() + "; 当前值:" + atomicStampedReference.getReference());
        }, "thread1").start();
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            // 在Thread1休眠之前读取,这时版本号为1
            System.out.println(Thread.currentThread().getName() + "第一次版本号:" + stamp);
            try {
                // 休眠2秒,确保Thread1完成修改操作
                TimeUnit.SECONDS.sleep(2);
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 尝试将100修改为102,版本号从1改为2,得到b为false
            boolean b = atomicStampedReference.compareAndSet(100, 102, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName() + "修改结果:" + b);
            System.out.println(Thread.currentThread().getName() + "修改后的版本号:" + atomicStampedReference.getStamp());
            System.out.println(Thread.currentThread().getName() + "修改后的值:" + atomicStampedReference.getReference());
        }, "thread2").start();
    }
}

AQS,AbstractQueueSynchonizer,队列同步器,用来构建锁或者其他同步的基础,使用一个int成员变量来表示同步状态,通过内置的FIFO队列完成竞争资源的线程排列工作。提供了管理同步状态和阻塞线程排队的框架。要实现自己的锁,只需要重写它的几个核心方法。
手动实现一个可重入的独占锁,示例如下:
我们知道可重入锁例如ReentrantLock,底层就是基于AQS实现的,下述代码就是剥离了复杂逻辑后,对AQS核心原理的最简复刻。

package com.example.demo1.chapter14;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * TestAQS实现Lock接口,
 * 这个类相当于一个“门面”,它将底层的 AQS 包装成了标准的 Lock 接口供业务代码使用
 */
public class TestAQS implements Lock {
    static class SyncQueued extends AbstractQueuedSynchronizer {
        /**
         * 重写ryAcquire,尝试获取锁
         * @param arg the acquire argument. This value is always the one
         *        passed to an acquire method, or is the value saved on entry
         *        to a condition wait.  The value is otherwise uninterpreted
         *        and can represent anything you like.
         * @return
         */
        @Override
        protected boolean tryAcquire(int arg) {
            // 锁第一次被获取
            if (compareAndSetState(0, 1)) {
                // 设置当前锁为独占锁
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }else if (Thread.currentThread() == getExclusiveOwnerThread()) {
                // 锁被多次读取
                // 将获取锁的次数进行累加
                setState(getState() + 1);
                return true;
            }
            return false;
        }

        /**
         * 尝试释放锁
         * @param arg the release argument. This value is always the one
         *        passed to a release method, or the current state value upon
         *        entry to a condition wait.  The value is otherwise
         *        uninterpreted and can represent anything you like.
         * @return
         */
        @Override
        protected boolean tryRelease(int arg) {
            if (Thread.currentThread() != getExclusiveOwnerThread()) {
                // 不是当前线程释放锁
                throw new IllegalMonitorStateException();
            }

            if (getState() == 0) {
                throw new IllegalMonitorStateException();
            }
            // 则将锁的获取次数减1
            setState(getState() - 1);
            // 如果锁的获取次数减为0,则释放锁
            if (getState() == 0) {
                // 清空持有线程标记,唤醒队列中的下一个线程
                setExclusiveOwnerThread(null);
            }
            return true;
        }

        @Override
        protected boolean isHeldExclusively() {
            return getState() > 0;
        }

        /**
         * 创建条件变量,
         * 内部调用了 AQS 的内部类 ConditionObject,用于实现类似 wait/notify 的高级线程等待与唤醒机制。
         * @return
         */
        Condition newCondition() {
            // 创建一个Condition对象
            return new ConditionObject();
        }
    }

    private final SyncQueued syncQueued = new SyncQueued();

    public void lock() {
        // 调用AQS的方法acquire(int arg)
        System.out.println(Thread.currentThread().getName() + "准备获取锁");
        syncQueued.acquire(1);
        System.out.println(Thread.currentThread().getName() + "已经获取锁");
    }

    public boolean tryLock() {
        return syncQueued.tryAcquire(1);
    }

    public void unlock() {
        // 调用AQS的模板方法release(int arg)
        System.out.println(Thread.currentThread().getName() + "准备释放锁");
        syncQueued.release(1);
        System.out.println(Thread.currentThread().getName() + "已经释放锁");
    }

    public Condition newCondition() {
        return syncQueued.newCondition();
    }

    public boolean isLocked() {
        return syncQueued.isHeldExclusively();
    }

    public boolean hasQueuedThreads() {
        return syncQueued.hasQueuedThreads();
    }

    public void lockInterruptibly()  throws InterruptedException {
        syncQueued.acquireInterruptibly(1);
    }

    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
        return syncQueued.tryAcquireNanos(1, unit.toNanos(timeout));
    }
}

然后测试自定义锁的可重入

package com.example.demo1.chapter14;

import java.util.concurrent.locks.Lock;


/**
 * 测试验证自定义锁是否能重入
 */
public class TestAQSMain {
    static final Lock lock = new TestAQS();

    /**
     * 递归方法,每进入一次就调用一次lock.lock
     * 在finally中调用lock.unlock
     * @param deep
     */
    public static void reenter(int deep) {
        // 递归获取锁
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + ":递归深度:" + deep);
            int currentDeep = deep - 1;
            if (currentDeep == 0) {
                return;
            }
            else {
                reenter(currentDeep);
            }
        }finally {
            lock.unlock();
        }
    }

    static class WorkerThread extends Thread {
        public void run() {
            // 初始深度为2,
            reenter(2);
        }
    }

    public static void main(String[] args) {
        // 启动2个子线程去争抢锁
        for (int i = 0; i < 2; i++) {
            Thread thread = new WorkerThread();
            thread.start();
        }
    }
}
  • 多线程竞争:同时启动了 2 个线程去争抢同一把锁。当一个线程正在执行这 2 层递归时,另一个线程会因为拿不到锁而被 AQS 阻塞排队,直到第一个线程完成所有递归并彻底释放锁。
    如下输出所示:
Thread-1准备获取锁
Thread-0准备获取锁
Thread-1已经获取锁
Thread-1:递归深度:2
Thread-1准备获取锁
Thread-1已经获取锁
Thread-1:递归深度:1
Thread-1准备释放锁
Thread-1已经释放锁
Thread-1准备释放锁
Thread-1已经释放锁
Thread-0已经获取锁
Thread-0:递归深度:2
Thread-0准备获取锁
Thread-0已经获取锁
Thread-0:递归深度:1
Thread-0准备释放锁
Thread-0已经释放锁
Thread-0准备释放锁
Thread-0已经释放锁

原子类是java.util.concurrent.atomic包下的类。
例如利用AtomicInteger原子类编码实现多线程求1-5的和,

package com.example.demo1.chapter14;

import java.util.concurrent.atomic.AtomicInteger;

public class TestAtomicInteger {
    // 声明atomicInteger对象
    static AtomicInteger atomicInteger = new AtomicInteger();

    public static void main(String[] args) {
        TestAtomicInteger testAtomicInteger = new TestAtomicInteger();
        Thread[] threads = new Thread[3];
        for (int i = 0; i < 3; i++) {
            // 设置当前线程
            threads[i] = new Thread(() -> {
                try {
                    for (int j = 0; j < 5; j++) {
                        // incrementAndGet()自增
                        System.out.println(atomicInteger.incrementAndGet());
                        Thread.sleep(500);
                    }
                }catch (Exception e) {
                    e.printStackTrace();
                }
            });
            threads[i].start(); // 线程启动
        }
    }
}

最终可以得到和为15.

数据库操作
Java语言为关系型数据库和非关系型数据库系统都提供了良好的访问接口。

后端开发技术
根据后端服务的演变过程,可以将其架构模式发展分为4个阶段。

  1. Servlet阶段
  2. SSH阶段
  3. SSM阶段
    Spring + Spring MVC + MyBatis三个框架集成的一种开发模式,是继SSH框架之后较为主流的企业级框架组合,目前也处于被淘汰的边缘
  4. 微服务阶段
    它将系统中的每个微服务独立部署,所有微服务之间松耦合,每个微服务只关注于一个任务,每个任务代表一个较小的业务功能。

spring框架对Java的各种应用项目提供全面支持,常用的Spring功能模块主要有Spring MVC, Spring JDBC, Spring AOP等。
Spring Boot在Spring基础上进行了扩展,提供了更简单的项目环境搭建方法,让开发人员可以把更多的精力放在业务逻辑开发上

MVC是软件开发的一种架构模式,它将应用软件的系统代码分为模型(Model)、视图(View)和控制器(Controller)三部分,如图17.13所示。需要注意的是,在实际开发过程中,一般会在此基础上将该模式进一步细分为更多层的架构。
MVC架构模式的优点是项目的不同层次之间低耦合,可重用性高,在生命周期中成本较低,有利于工程化管理项目。

ORM,Object Relational Mapping对象关系映射,可以将关系数据库中表的数据映射成对象,这样开发人员就可以把对数据库的操作转化为对对象的操作。目前有很多ORM框架,例如Hibernate, MyBatis, Spring MVC, Log4j等。
可以提高项目开发效率,ORM可以自动对实体对象与数据库中的表进行字段与属性映射,而不再需要一个复杂的数据访问层。
MyBatis是一款优秀的持久层框架,它支持自定义SQL、存储过程和高级映射。MyBatis几乎免除了所有的JDBC代码编写,以及参数设置和结果集获取等工作。它可以通过配置简单的XML或注解,将原始类型、接口和JavaPOJO(Plain Old Java Objects,普通老式的Java对象)映射为数据库中的记录。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

所谓远行Misnearch

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值