Java学习第十天

1、多线程

程序:计算机中正在运行的应用的一个静态的概念

进程:一个在运行的程序中,就对应了一个进程,进程运行在计算机的后台,是一个正在动态运行的程序

线程:线程是进程中执行任务的个体,一个进程中至少包含一个线程

比如:听音乐的时候,可以同时听歌,显示歌词 ,都是线程在工作

看直播间的时候,直播的画面、直播弹幕、点赞

线程和进程的关系:

进程是调度单位

线程是执行单位

目前学习多线程,主要是在项目中处理业务的时候,有的时候多个需求需要同时处理,需要多线程

1.1多线程的实现

常见的实现方式分为四种;

1、继承Thread类,重写run方法,把需要执行的任务写在run方法中

2、实现Runnable接口,重写run方法,把需求功能写在run方法中

3、实现Callable接口,重写Call方法

4、使用线程池

默认情况下,一个Java程序中只有一个线程,main线程,也称为主线程

1.2继承Thread类

构造方法

Thread 类的构造方法

Thread() 分配一个新的 Thread对象。

Thread(Runnable target) 分配一个新的 Thread对象。

Thread(Runnable target, String name) 分配一个新的 Thread对象。

普通方法

static Thread currentThread() 返回对当前正在执行的线程对象的引用。

String getName() 返回此线程的名称。

void setName(String name) 将此线程的名称更改为等于参数 name

static void sleep(long millis) 使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。

void start() 导致此线程开始执行; Java虚拟机调用此线程的run方法。

继承 Thread 类实现多线程

复制代码
public class Test {
    public static void main(String[] args) throws InterruptedException {
        //获取当前程序正在执行的线程,主线程main,第一部分
        Thread thread = Thread.currentThread();
        thread.setName("主线程main");
        System.out.println("这是程序主线程的名称"+thread.getName());//名称:main


        //创建Thread类对象,传入创建的MyThread类对象,第二部分
        Thread t1 = new Thread(new MyThread());
        t1.setName("子线程-1");
        //调用start方法,表示让JVM启动线程,执行线程中的run方法
        t1.start();
        //t1.run();//直接run会被当做普通方法

        Thread t2 = new Thread(new MyThread(),"子线程-2");
        t2.start();

        //让线程等待,线程休眠
        Thread.sleep(5000);
        System.out.println("程序结束");
    }
}

public class MyThread extends Thread{
    @Override
    public void run(){//run方法自动执行
        Thread thread = Thread.currentThread();
        String name=thread.getName();
        System.out.println("另一个线程执行,线程名是:"+name);//线程名称是Thread-1
    }
}

线程中执行后输出子线程1或2顺序不一定,可能12,可能21

sleep的倒计时写法:

复制代码
for (int i = 5; i >=1; i--) {
            System.out.println("倒计时"+i);
            thread.sleep(1000);
        }

线程对象调用start()和run()方法的区别:

调用start()方法,表示让JVM去获取CPU分配线程执行的时间片,线程进入执行就绪状态,拿到时间片就开始执行任务,调用run()方法

对象调用run()方法,JVM会把run()方法当成一个普通方法去加载执行

1.3Thread 类中的方法

void join() 等待这个线程死亡(可以实现插队效果)

复制代码
package com.day15.test1;

import com.day15.test.MyThread;

public class Test3 {
    public static void main(String[] args) throws InterruptedException {


        //子线程
        Thread t1 = new Thread(new MyThread2());
        t1.start();

        //主线程
        String name = Thread.currentThread().getName();
        for (int i = 0; i < 10; i++) {
            if (i == 5){
                t1.join(); //插队效果
            }
            System.out.println("线程:" + name + "==》" + i);
        }
    }
}

1.4通过实现Runnable接口

步骤:

1、实现接口Runnable

2、重写run()方法

3、创建 Thread 类,把上面的实现类对象作为参数

4、调用 start 方法

复制代码
import com.text1.text1.MyThread;

public class test {
    public static void main(String[] args) {
//将myThread同时传入Thread对象,myThread两个对象同时执行(同时执行数字总共会到20)
        MyThread2 myThread = new MyThread2();
        Thread t1 = new Thread(myThread,"线程1");
        t1.start();

        Thread t2 = new Thread(myThread,"线程2");
        t2.start();

//将myThread1和myThread2传入Thread对象,myThread两个对象分别执行(线程1、2都是到10)
        MyThread2 myThread1 = new MyThread2();
        MyThread2 myThread2 = new MyThread2();

        new Thread(myThread1,"线程1").start();
        new Thread(myThread2,"线程2").start();
    }
}

public class MyThread2 implements Runnable{
    int number=1;
    @Override
    public void run() {
        Thread thread = Thread.currentThread();
        String name=thread.getName();
        for (int i = 0; i < 10; i++) {
            System.out.println("这是子线程执行的代码,子线程名为"+name+"==>"+number++);
        }
    }
}

1.4.1实现方案的区别

Thread类和Runnable的区别

实现 Runnable 接口,可以避免单继承的局限性,使用更加灵活,可以更加方便使用同一个对象共享,将来一般都会使用 Runnable 接口

1.4.2匿名内部类写法--通过 join 交替打印A、B

匿名内部类写法可以只创建这一个类,不需要再创建继承Runnable的类

复制代码
public class Demo03 {
    public static void main(String[] args) throws InterruptedException{
        //通过join方法实现线程的交替输出,A,B,A,B,A,B,A,B,A,B交替实现5次
        //子线程

        for (int i = 0; i < 5; i++) {
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("A");
                }
            });//匿名内部类写法

            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("B");
                }
            });
            //匿名内部类简化写法
            //Thread t3 = new Thread(() -> System.out.println("B"));

            t1.start();
            t1.join();
            t2.start();
            t2.join();
        }
    }
}

1.5线程安全问题

在多线程的运行环境下,程序运行的结果和我们预期的结果不一样(排除错误问题),这种情况就是线程安全问题,每次运行的结果可能都是不一样的,问题不容易复现,解决比较困难。

复制代码
package com.day15.test2;

public class ThreadA {
    //一个类中,具有一个可以改变的属性,这个属性可能被多个线程访问
    //这种属性也被称为 临界资源
    int num;

    public int getNum() {
        return num;
    }
    public int updateNum() {
        return num++;
    }
}

package com.day15.test2;

public class ThreadB extends Thread{
    //创建ThreadA 对象
    ThreadA ta = new ThreadA();

    @Override
    public void run() {
        while (true){
            System.out.println("当前线程名:" + Thread.currentThread().getName()
                               +"num == " + ta.updateNum());
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }


    public static void main(String[] args) {

        ThreadB tb = new ThreadB();

        new Thread(tb,"A").start();
        new Thread(tb,"B").start();
        new Thread(tb,"C").start();
        new Thread(tb,"D").start();
    }
}

找到编译好的 class 文件 ,反编译

通过反编译查看,一个方法其实在执行的时候是被拆分成很多个步骤的

每个线程过来,没有执行完全部步骤,其他线程也同时执行了,就会产生线程安全问题

线程安全问题发生的条件:

1,多线程环境下

2,存在临界资源,多个线程会去共享临界资源

3,存在多个线程并发修改临界资源的情况

比如 : 优惠券秒杀、商品抢购、门票抢购......

怎么解决线程安全问题?

使用线程同步

1.5.1线程同步

并发编程 :指的是多个线程同时操作特定的任务 ,并发编程可以提高程序的执行效率,运行速度,但是可能会产生线程安全问题

解决线程安全问题,使用线程同步,主要围绕并发三要素去实现 ,保证线程安全运行

并发编程三要素

1、原子性 : 原子表示不可再分,一个方法中的多个操作,要么一起执行成功,要么一起执行失败

2、可见性 : 一个线程对于共享变量的修改,另一个线程能够马上看到

3、有序性 : 执行程序的过程中,按照代码的先后顺序执行。

只要保证以上三个问题不出现,那么就能避免线程安全问题,Java 中提供了一些写好的类、关键字来保证这些问题不会发生

1、JDK 中的 Atomic 开头的原子类、synchronized 、 Lock 可以保证原子性

2、synchronized、volatile 、Lock 可以解决可见性问题

3、Happens-Before 规则可以保证有序性

1.5.2使用synchronized解决线程安全问题

synchronized 的用法

synchronized 可以用来修饰方法、代码块 、类

1、直接在synchronized 写在方法体上 ,这种方法也称为同步方法,将来执行的时候,每个对象执行方法的时候,都会获得一把锁。用完方法后,才把锁释放

2、synchronized 用在静态方法上,静态资源是属于类的,也就是给当前的类加锁

3、synchronized 可以用来修饰代码块 ,也称为同步代码块,synchronized(obj){ }

复制代码
public synchronized int updateNum(){
        return num++;
    }

线程安全问题------抢票系统

复制代码
package com.day15.test1;
/*
* 模拟抢票的使用
* */
public class ThreadDemo implements Runnable{
    //票
    private int ticketNumber = 10;

    private boolean flag = true;

    Object object = new Object();

    @Override
    public void run() {
        while (flag){
            buyTicket();
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

    }
//写一个抢票的方法,第一种方法
//    public synchronized void buyTicket(){
//
//            if (ticketNumber <= 0){
//                flag = false;
//                return;
//            }
//            try {
//                Thread.sleep(50);
//            } catch (InterruptedException e) {
//                throw new RuntimeException(e);
//            }
//            System.out.println(Thread.currentThread().getName() + "抢到了第" + ticketNumber-- + "张票!");
//    }


    public void buyTicket(){
        //使用代码块解决线程安全问题,第二种方法
        synchronized (object){
            if (ticketNumber <= 0){
                flag = false;
                return;
            }

            System.out.println(Thread.currentThread().getName() + "抢到了第" + ticketNumber-- + "张票!");
        }

    }

}

public class Test2 {
    public static void main(String[] args) {
        ThreadDemo t = new ThreadDemo();
        //创建线程执行对象,传入同一个ThreadDemo对象
        new Thread(t,"张三").start();
        new Thread(t,"李四").start();
        new Thread(t,"王五").start();
    }
}

1.5.3 Lock锁

Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作,它们允许更灵活的结构化,可能具有完全不同的属性

Lock 是用于通过多个线程控制对共享资源的访问的工具。 通常,锁提供对共享资源的独占访问:一次只能有一个线程可以获取锁,并且对共享资源的所有访问都要求首先获取锁。

但是,一些锁可能允许并发访问共享资源,如ReadWriteLock的读锁。

Lock l = ...;

l.lock();

try { // access the resource protected by this lock }

finally {

l.unlock();

}

void lock() 获得锁。

void unlock() 释放锁。

lock.lock()加锁

使用 Lock 接口的子类 ReentrantLock 来完成具体的加锁功能

一个可重入互斥Lock具有与使用synchronized方法和语句访问的隐式监视锁相同的基本行为和语义,但具有扩展功能。

一个 ReentrantLock由线程拥有 ,最后成功锁定,但尚未解锁。 调用lock的线程将返回,成功获取锁,当锁不是由另一个线程拥有。 如果当前线程已经拥有该锁,该方法将立即返回。

写法 :

class X {

private final ReentrantLock lock = new ReentrantLock();

// ...

public void m() {

lock.lock();

// block until condition holds

try { // ...

method body

}

finally {

lock.unlock()

}

}

}

复制代码
package com.day15.test1;

import java.util.concurrent.locks.ReentrantLock;

/*
* 模拟抢票的使用
* */
public class ThreadDemo implements Runnable{
    //票
    private int ticketNumber = 10;

    private boolean flag = true;

    Object object = new Object();
    //声明锁
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (flag){
            buyTicket();
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

    }

    //写一个抢票的方法
//    public synchronized void buyTicket(){
//
//            if (ticketNumber <= 0){
//                flag = false;
//                return;
//            }
//            try {
//                Thread.sleep(50);
//            } catch (InterruptedException e) {
//                throw new RuntimeException(e);
//            }
//            System.out.println(Thread.currentThread().getName() + "抢到了第" + ticketNumber-- + "张票!");
//    }

//    public void buyTicket(){
//        //使用代码块解决线程安全问题
//        synchronized (object){
//            if (ticketNumber <= 0){
//                flag = false;
//                return;
//            }
//
//            System.out.println(Thread.currentThread().getName() + "抢到了第" + ticketNumber-- + "张票!");
//        }
//
//    }

//使用Lock
    public void buyTicket(){
        //加锁
        lock.lock();
        try {
            if (ticketNumber <= 0){
                flag = false;
                return;
            }
            System.out.println(Thread.currentThread().getName() + "抢到了第" + ticketNumber-- + "张票!");
        } finally {
            lock.unlock(); //释放锁
        }

    }
}

public class Test2 {
    public static void main(String[] args) {
        ThreadDemo t = new ThreadDemo();
        //创建线程执行对象,传入同一个ThreadDemo对象
        new Thread(t,"张三").start();
        new Thread(t,"李四").start();
        new Thread(t,"王五").start();
    }
}

1.5.4 synchronized和lock锁的区别

1、synchronized 是关键字 ,ReentrantLock 是一个类

2、synchronized 自动加锁释放锁 ,ReentrantLock 需要手动的加锁释放锁

3、synchronized 使用在方法上、代码块前 ,ReentrantLock 只能用于代码块锁

将来使用就使用synchronized 就可以了

1.5.5volatile 关键字

volatile 也是一个关键字,一般用来修饰变量 ,保证可见性 和 有序性,不能保证原子性,仍然可能出现线程安全问题

复制代码
volatile int Number;

可见性测试(了解):

复制代码
package com.day15.test3;

public class Thread1 {
    int num = 0;
    volatile boolean flag = false;

    public boolean getFlag(){
        return flag;
    }
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
    public int getNum(){
        return num;
    }
    public void addNum(){
        num++;
    }


    public static void main(String[] args) {

        Thread1 t1 = new Thread1();

        //线程1执行循环
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    t1.addNum();
                    System.out.println("addNum的次数:" + i);
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                t1.flag = true; //把变量值设为true
                System.out.println("flag设为true了");
            }
        }).start();

        //线程2执行一个循环,如果flag值改了,线程2想拿到,flag 必须加volatile
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (!t1.flag){

                }
                System.out.println("线程2获取num的值是:" + t1.getNum());
            }
        }).start();


    }

}

有序性测试

复制代码
public class VolatileReOrderSample {
    //定义四个静态变量
    private static int x = 0, y = 0;
    private static volatile int a = 0, b = 0;

    public static void main(String[] args) throws InterruptedException {
        int i = 0;
        while (true) {
            i++;
            x = 0;
            y = 0;
            a = 0;
            b = 0;
            //开两个线程,第一个线程执行a=1;x=b;第二个线程执行b=1;y=a
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    //线程1会比线程2先执行,因此用nanoTime让线程1等待线程2 0.01毫秒
                    shortWait(10000);
                    a = 1;
                    x = b;
                }
            });
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    b = 1;
                    y = a;
                }
            });
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
            //等两个线程都执行完毕后拼接结果
            String result = "第" + i + "次执行x=" + x + "y=" + y;
            //如果x=0且y=0,则跳出循环
            if (x == 0 && y == 0) {
                System.out.println(result);
                break;
            } else {
                System.out.println(result);
            }
        }
    }

    //等待interval纳秒
    private static void shortWait(long interval) {
        long start = System.nanoTime();
        long end;
        do {
            end = System.nanoTime();
        } while (start + interval >= end);
    }
}

1.5.6 volatile 和 synchronized 区别

1、volatile 只能修饰变量 ,synchronized 修饰方法、变量、类

2、volatile 保证可见性和有序性,不能保证原子性, synchronized 保证原子性和可见性

3、volatile 不会造成线程阻塞 ,synchronized 会造成线程阻塞

1.6实现 Callable 接口,创建多线程

实现步骤

1、创建Callable接口的实现类

2、以实现类为参数创建FutureTask对象

3、将FutureTask对象作为参数,创建 Thread 类的对象

4、调用 start 方法, 通过FutureTask 对象获取返回值

复制代码
package com.day15.test4;

import java.util.concurrent.Callable;

public class MyCallable implements Callable {
    @Override
    public Object call() throws Exception {
        System.out.println(Thread.currentThread().getName()+"线程正在执行call方法");
        return 10;
    }
}

package com.day15.test4;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //如果要使用Callable,需要创建一个FutureTask对象
        FutureTask futureTask = new FutureTask<>(new MyCallable());
        //想要使用多线程,必须创建线程对象,传入futureTask对象
        Thread t1 = new Thread(futureTask);
        t1.start();
        //获取返回值
        Object o = futureTask.get();
        System.out.println("返回值结果:" + o);

    }
}

1.6.1Callable和Runnable的区别

1、call方法有返回值,run方法没有返回值

2、call方法可以抛出异常,run方法不能抛出只能捕获处理

3、Runnable 的实现类对象可以直接传入 Thread 创建线程对象,Callable 必须要通过FutureTask 对象完成线程对象的创建

1.7使用线程池实现

线程池:线程池是一种池子思想,开辟一块内存空间,里面存放众多的(未死亡)的线程,由池子的管理器来管理池子,当有线程任务需要执行,就从池中取出线程,执行任务,执行完成后,把线程归还到线程池

这样做可以避免反复创建线程对象所带来的性能开销,节省系统资源

使用 Executors 来创建线程池:

常用方法 :

static ExecutorService newCachedThreadPool()

创建一个**根据需要创建新线程(可缓存)**的线程池,但在可用时将重新使用以前构造的线程。

static ExecutorService newFixedThreadPool(int nThreads)

创建一个线程池,该线程池重用**固定数量(固定大小)**的从共享无界队列中运行的线程。

static ExecutorService newSingleThreadExecutor()

创建一个使用从无界队列运行的**单个工作线程(单例)**的执行程序。

复制代码
public class MyThreadPool extends Thread{
    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        System.out.println(name + "正在执行任务!");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

public class Demo01 {
    public static void main(String[] args) {

        //创建几个线程任务
        MyThreadPool t1 = new MyThreadPool();
        MyThreadPool t2 = new MyThreadPool();
        MyThreadPool t3 = new MyThreadPool();
        MyThreadPool t4 = new MyThreadPool();
        MyThreadPool t5 = new MyThreadPool();
        MyThreadPool t6 = new MyThreadPool();

        //通过Executors创建一个单例的线程池对象
        ExecutorService executorService = Executors.newSingleThreadExecutor();
                executorService.execute(t1);
                executorService.execute(t2);
                executorService.execute(t3);
                executorService.execute(t4);
        //通过Executors创建一个固定大小的线程池对象
        //ExecutorService executorService1 = Executors.newFixedThreadPool(5);
        //        executorService1.execute(t1);
        //        executorService1.execute(t2);
        //        executorService1.execute(t3);
        //        executorService1.execute(t4);
        //        executorService1.execute(t5);
        //        executorService1.execute(t6);
        //可缓存的线程池,不固定数量
        ExecutorService executorService2 = Executors.newCachedThreadPool();
        executorService2.execute(t1);
        executorService2.execute(t2);
        executorService2.execute(t3);

        //直接把线程任务传入线程池的方法,匿名内部类
        executorService2.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程任务被执行!");
            }
        });

    }

}

1.7.1 ThreadPoolExecutor 类的说明

这个类可以直接通过构造方法,创建线程池对象

常用的构造方法

public ThreadPoolExecutor(int corePoolSize,

int maximumPoolSize,

long keepAliveTime,

TimeUnit unit,

BlockingQueue<Runnable> workQueue,

RejectedExecutionHandler handler)

参数

corePoolSize - 要保留在池中的线程数

maximumPoolSize - 池中允许的最大线程数

keepAliveTime - 当线程数大于核心时,这是多余的空闲线程在终止之前等待新任务的最大时间。

unit - keepAliveTime参数的时间单位

workQueue - 在执行任务之前用于保存任务的队列。 该队列将仅保存execute方法提交的Runnable任务。

handler - 任务被阻止执行的策略

工作流程:

1、首先线程池中保留最低的线程数量的线程在工作

2、有任务来了以后,直接被线程处理

3、如果线程处理时间较长,没有完成,有新的任务过来,任务先排队

4、如果队列中任务数量太多, 按照最大线程数创建线程对象,执行任务

  • 如果执行完成,没有太多任务,那么后创建的线程会在指定时间后,结束任务,保留最少的线程数
  • 如果执行过程,仍然有大量的任务过来,需要执行,那么如果超过队列可以容纳的数量,那么就会采用特定的策略去让后续的任务 按照策略执行

1.8线程交互

主要使用 Object 类中的 wait()方法 和 notify()方法

wait()方法 : 调用后,释放占有对象锁,线程进入等待状态,释放 cpu,其他的排队的线程会获取锁,执行后续的任务

wait()和 sleep 方法的不同是 :sleep 方法不会释放锁,只是休眠一段时间

notify()方法 : 唤醒因为调用 wait 方法而等待的线程对象,继续执行后续的任务

注意 :wait 方法和 notify 方法,必须在 synchronized 代码块中使用

例题:

复制代码
package com.day15.test6;

public class WaitThread1 implements Runnable {
    int count = 0;
    @Override
    public void run() {
        while (count < 10){
            synchronized (Demo01.obj){
                try {
                    if (count!= 0){ //除了第一次执行不用唤醒
                        Demo01.obj.notify();  //唤醒其他线程
                    }
                    System.out.println("A" + count);
                    Demo01.obj.wait(); //调用wait方法
                    count++;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

package com.day15.test6;

public class WaitThread2 implements Runnable {
    int count = 0;
    @Override
    public void run() {
        while (count < 10){
            synchronized (Demo01.obj){
                try {
                    Demo01.obj.notify(); //唤醒其他等待的线程
                    System.out.println("B" + count);
                    if (count !=9){
                        Demo01.obj.wait(); //调用wait方法
                    }
                    count++;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

package com.day15.test6;

public class Demo01 {
    public static final Object obj = new Object();

    public static void main(String[] args) {
        //创建线程对象
        new Thread(new WaitThread1()).start();
        new Thread(new WaitThread2()).start();
    }
}

2、JDBC新特性

2.1简化匿名内部类(lambda)

Lambda : 写法 () -> {}

简化匿名内部类的写法 ,括号中写参数 ,大括号中写代码体

只要是一个接口,里面有一个抽象方法,其他的方法中需要把这个接口作为对象传入的

都可以使用lambda表达式

普通方式实现多线程的步骤:

1,创建类,实现接口,重写方法

2,创建Thread对象,传入实现类对象,调用start方法

使用匿名内部类 可以去解决上面的问题

创建类以后,实现接口,重写方法的繁琐步骤

可以直接将接口,以new的方式,传入到方法的参数中

Lambda : 简化匿名内部类的写法

把需要重写的方法,改造成 ()->{}

复制代码
package com.day16.test1;
public class Demo01 {
    public static void main(String[] args) {

        //创建Thread类对象,第一种方法需要两个文件
        Thread t1 = new Thread(new MyThread());
        t1.start();
        //匿名内部类的写法,第二种方法写在一个 Demo01即可
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("这个是通过匿名内部类创建的线程!");
            }
        }).start();
        //lambda表达式的写法
        new Thread(()->{System.out.println("这是使用lambda表达式实现的线程");}
        ).start();
    }
}

2.2 Stream流操作:集合流式处理,中间操作 + 终止操作,惰性求值

Stream 是真正的把 lambda 使用起来的一个类,提供了很多的方法可以让我们更加方便的操作数据的处理

Stream 中分为两个操作 :

中间操作(方法) : 方法的返回值类型还是 Stream 对象的方法 ,调用方法后,可以继续在后面以点的方式调用其他方法

终止方法 :返回值类型不是 Stream 对象,一旦调用这些方法,后续就不能继续调用 Stream 流中的方法了

Stream 流 一般用来 集合中,处理集合中的数据 ,Collection 的所有子类都可以使用

Stream 流可以用在 Map 中 、数组中

常见的中间方法 :

Stream<T> filter (Predicate<? super T> predicate) 过滤筛选条件(布尔条件)

Stream<R> map(Function<? super T,? extends R> mapper) map改为新的值(转大/小写...)

Stream<T> sorted (Comparator<? super T> comparator) 排序作用

sorted((o1,o2)->o2-o1) //降序排序(数值写法)

sorted((o1, o2) -> o2.compareTo(o1)) //降序排序(非数值写法,无实义)

终止方法

void forEach(Consumer<? super T> action)

long count()

复制代码
package com.day16.test2;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class Demo01 {
    public static void main(String[] args) {

        ArrayList<String> list = new ArrayList<>();

        list.add("hello");
        list.add("mysql");
        list.add("Java");
        list.add("world");
        list.add("spring");
        list.add("oracle");
        System.out.println(list);
        //        //把集合中的数据包含o的字符串筛选
        //        ArrayList<String> list1 = new ArrayList<>();
        //        for (String s : list) {
        //            if (s.contains("o")){
        //                list1.add(s);
        //            }
        //        }
        //        System.out.println(list1);
        //        //在上面的基础上,获取长度大于5的字符串
        //        ArrayList<String> list2 = new ArrayList<>();
        //        for (String s : list1) {
        //            if (s.length() >5){
        //                list2.add(s);
        //            }
        //        }
        //        System.out.println(list2);

        //过滤一个条件,并将筛选过滤之后操作放到集合中(stream使用的时候会默认遍历作用,所以会有转到集合的操作)
        List<String> list1 = list.stream().
        filter((s) -> s.contains("o")).
        //forEach(System.out::println); //输出
        collect(Collectors.toList()); //转到集合
        System.out.println(list1);

        //同时过滤2个条件(可以过滤多个,也可以过滤1个)
        list.stream().
        filter((s)->s.contains("o")).
        filter((s)->s.length() > 5).
        forEach(System.out::println);
    }
}

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamDemo {
    public static void main(String[] args) {
        List<Integer> nums = Arrays.asList(1,2,3,4,5,6,7,8);

        List<Integer> res = nums.stream()
                .filter(n -> n > 3)         //过滤大于3
                .map(n -> n * 2)            //每个*2
                .sorted((o1,o2)->o2-o1)     //降序排序(数值写法)
                .limit(3)                   //取前3个
                .collect(Collectors.toList()); //收集为list

        System.out.println(res); // [16,14,12]

        //匹配
        boolean any = nums.stream().anyMatch(x -> x>5);
        System.out.println(any);

        //统计数量
        long count = nums.stream().filter(x->x>4).count();
        System.out.println(count);
    }
}

<? super T> : 向上限定,指定了 T 的类型后,可以传 T 或者 T 的父类

<? entendsT>: 向下限定,指定了 T 的类型后,可以传 T 或者 T 的子类

2.3java.time:全新不可变日期时间,线程安全

复制代码
import java.time.*;
import java.time.format.DateTimeFormatter;

public class TimeDemo {
    public static void main(String[] args) {
        // 当前日期
        LocalDate date = LocalDate.now();
        System.out.println(date);

        // 当前时间
        LocalTime time = LocalTime.now();
        System.out.println(time);

        // 日期+时间
        LocalDateTime dt = LocalDateTime.now();
        System.out.println(dt);

        // 指定时间
        LocalDate d2 = LocalDate.of(2026,8,4);
        System.out.println(d2);

        // 时间加减
        LocalDate nextDay = d2.plusDays(1);
        System.out.println(nextDay);

        //格式化
        DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        String dateStr = dt.format(fmt);
        System.out.println(dateStr);

        //字符串转时间
        LocalDateTime parse = LocalDateTime.parse("2026-08-04 12:30:00", fmt);
        System.out.println(parse);

        //Instant时间戳
        Instant instant = Instant.now();
        System.out.println(instant.toEpochMilli());

        //计算两个时间的时间差
        
    }
}

2.4Record 记录类(JDK16 正式,JDK21 自带)

复制代码
// 一行搞定数据实体,不用写get set toString
record User(Long id, String name, Integer age) {}

public class RecordDemo {
    public static void main(String[] args) {
        User u = new User(1L,"张三",20);
        // 直接拿属性,注意:是name(),不是getName()
        System.out.println(u.id());
        System.out.println(u.name());
        System.out.println(u); //自动toString User[id=1, name=张三, age=20]
    }
}

2.5var 局部变量(JDK10 正式,JDK21 自带)

复制代码
import java.util.ArrayList;

public class VarDemo {
    public static void main(String[] args) {
        //jdk8
        ArrayList<String> list = new ArrayList<>();

        //jdk21,var自动推断类型
        var list2 = new ArrayList<String>();
        var str = "hello jdk21";
        var num = 100;

        System.out.println(list2.getClass());
        System.out.println(str);
    }
}

2.6文本块 Text Blocks(JDK15 正式,JDK21 自带)

复制代码
public class TextBlockDemo {
    public static void main(String[] args) {
        //jdk8 写json,要转义引号,拼接换行
        String jsonOld = "{\n" +
                "  \"name\":\"张三\",\n" +
                "  \"age\":20\n" +
                "}";

        //jdk21文本块,直接复制粘贴
        String json = """
                {
                  "name":"张三",
                  "age":20
                }
                """;
        System.out.println(json);

        //写SQL
        String sql = """
                select id,name,age
                from t_user
                where age > 18
                """;
        System.out.println(sql);
    }
}

2.7虚拟线程 Virtual Threads【JDK21 正式】

复制代码
import java.util.concurrent.Executors;
import java.time.Duration;

public class VirtualThreadDemo {
    public static void main(String[] args) {
        //虚拟线程池:每一个任务分配一个虚拟线程
        try(var executor = Executors.newVirtualThreadPerTaskExecutor()){
            //直接提交 2000个任务,jdk8普通线程直接OOM,jdk21轻松跑
            for (int i = 0; i < 2000; i++) {
                int idx = i;
                executor.submit(() -> {
                    //模拟IO等待,比如调用接口、查数据库
                    try {
                        Thread.sleep(Duration.ofMillis(100));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("任务:"+idx + " 线程:"+Thread.currentThread());
                });
            }
        }
        //try-with-resources关闭executor,会自动等待全部任务完成
    }
}

2.8Switch 模式匹配(JDK21 正式)

复制代码
public class SwitchPatternDemo {
    public static void main(String[] args) {
        System.out.println(formatData(99));
        System.out.println(formatData(200));
        System.out.println(formatData("hello jdk21"));
        System.out.println(formatData(null));
    }

    static String formatData(Object obj){
        return switch (obj){
            //匹配Integer,并且数值大于100
            case Integer i when i>100 -> "大整数:" + i;
            case Integer i -> "普通整数:"+i;
            case String s when s.length()>5 -> "长文本:"+s;
            case String s -> "短文本:"+s;
            case null -> "传入空值";
            default -> "未知类型";
        };
    }
}

2.9Record 模式匹配(JDK21 正式)

复制代码
record Point(int x, int y){}

public class RecordPatternDemo {
    public static void main(String[] args) {
        Object obj = new Point(10,20);

        //jdk8写法
        if(obj instanceof Point){
            Point p = (Point) obj;
            int x = p.x();
            int y = p.y();
            System.out.println("jdk8 x="+x+",y="+y);
        }

        //jdk21,直接解构,不用手动强转
        if(obj instanceof Point(int x,int y)){
            System.out.println("jdk21 x="+x +",y="+y);
        }
    }
}

2.10密封类 Sealed Classes(JDK17 正式,JDK21 自带)

JDK8 痛点:父类谁都可以继承,控制子类非常麻烦。

sealed 密封类:指定哪些类可以继承我,其他类禁止继承。适合做状态枚举、领域模型。

复制代码
// 密封接口,只允许Dog Cat实现
sealed interface Animal permits Dog,Cat {
}

//允许的实现类,必须加final/sealed/non-sealed
final class Dog implements Animal{}
final class Cat implements Animal{}

// 下面这个类编译报错,不允许实现Animal
// class Bird implements Animal{}

public class SealedDemo {
    public static void main(String[] args) {
        Animal a = new Dog();
        System.out.println(a instanceof Dog);
    }
}
相关推荐
huibin1478523691 小时前
热缓解学习记录
android·学习
matlabgoodboy2 小时前
计算机毕设代做|Java Python Matlab APP 全套开发设计
java·python·课程设计
Nan_Shu_6142 小时前
学习:GSAP
学习
程序员雷欧3 小时前
环形缓冲区深度解析:从基础原理到Disruptor源码的全面剖析
java
xiaoqiMikko3 小时前
Dependabot 面板全绿,不代表你的 Tomcat 没洞
java·spring boot
前端开发张小七3 小时前
Java 学习笔记 · 第三课:多线程与并发编程(线程、同步、死锁、Lock、乐观锁与悲观锁)
java·后端·程序员
花生了什么事o4 小时前
JVM 垃圾回收:对象如何被判定和回收
java·jvm
evans在进步4 小时前
HashMap 为什么线程不安全?ConcurrentHashMap 如何解决?
java·spring boot·spring
我命由我123454 小时前
匈牙利命名法
java·服务器·后端·学习·java-ee·kotlin·学习方法