java学习笔记第十天

Lock lock = new ReentrantLock();创建锁对象
默认创建的是非公平锁,小括号内加true就是公平锁

lock.lock();//加锁
lock.tryLock();尝试加锁 成功返回true 失败返回false

加锁之后要用lock.unlock();方法解锁,否则程序可能会出现死锁状态。
一个线程获得了锁但没有释放,其他线程在试图获取相同的锁时可能会被阻塞。

public class EasyThreadB {
    //锁对象 lock
    Lock lock = new ReentrantLock();//创建锁对象
    public void method() {
        //lock.lock();//加锁
        //lock.tryLock()尝试加锁  成功true 失败false
        if (lock.tryLock()) {
            System.out.println(Thread.currentThread().getName() + "进入方法");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName() + "结束方法");
            lock.unlock();//解锁
        }else{
            System.out.println("加锁未成功-----去执行别的代码");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            method();
        }
    }
    public static void main(String[] args) {
        Runnable run=new EasyThreadB()::method;
        Thread a=new Thread(run);
        Thread b=new Thread(run);
        a.start();
        b.start();
    }
}

ReentrantLock和ReentrantReadWriteLock

ReentrantLock:是一种独占锁(也称为互斥锁),同一时刻只允许一个线程持有锁。这意味着当一个线程获得了 ReentrantLock 的锁之后,其他线程必须等待该线程释放锁才能获取锁。
ReentrantReadWriteLock不是锁,是读-写锁的容器。它将锁分为读锁和写锁两种。多个线程可以同时持有读锁,但是写锁是独占的。这意味着读锁之间不会互斥,但读锁与写锁之间是互斥的,即在写锁被持有期间,任何读锁的获取都会被阻塞。

读锁可以同时被多个线程持有,这意味着多个线程可以同时获得读锁而不互斥。这种情况下,多个线程可以并发地读取共享资源,而不会阻塞彼此。

写锁则是独占锁,同一时刻只能有一个线程持有写锁。当一个线程持有写锁时,其他线程无法同时持有写锁或者读锁,以确保写操作的原子性和一致性。

以下代码可以验证这一点,

public class EasyThreadC {

    public static ReentrantReadWriteLock rrwl = new ReentrantReadWriteLock();
    public static ReentrantLock rl = new ReentrantLock();

    public static void method() {
        System.out.println(Thread.currentThread().getName() + "进入方法");
        Lock lock = rrwl.readLock();
        lock.lock();
        System.out.println(Thread.currentThread().getName() + "加锁成功--读锁"+System.currentTimeMillis());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread().getName() + "方法结束"+System.currentTimeMillis());
        lock.unlock();
    }

    public static void methodWrite() {
        System.out.println(Thread.currentThread().getName() + "进入方法");
        Lock lock = rrwl.writeLock();
        lock.lock();
        System.out.println(Thread.currentThread().getName() + "加锁成功--写锁"+System.currentTimeMillis());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread().getName() + "方法结束");
        lock.unlock();
    }

    public static void main(String[] args) {
        Runnable run = EasyThreadC::method;
        Runnable runWrite = EasyThreadC::methodWrite;
        Thread a = new Thread(run);
        a.start();
        ....
        此次省略部分进程创建代码,可以多复制几个上述代码

        Thread f = new Thread(runWrite);
        f.start();
		...
		此次省略部分进程创建代码,可以多复制几个上述代码
        System.out.println("main线程结束----");
    }
}

死锁

死锁(Deadlock)是指多个进程或线程在执行过程中,由于竞争资源而造成的一种互相等待的状态,导致它们都无法继续执行下去。在死锁状态下,每个进程或线程都在等待其他进程或线程释放它所需要的资源,同时又不释放自己已经占有的资源,使得所有相关的进程或线程都处于僵持状态,无法推进。

死锁的四个必要条件

互斥条件(Mutual Exclusion):至少有一个资源必须是不能共享的,即一次只能被一个进程或线程占用。
请求与保持条件(Hold and Wait):进程或线程至少已经占有一个资源,并且在请求其他资源时由于被其他进程或线程占有而等待。
不剥夺条件(No Preemption):资源只能由占有它的进程或线程释放,不能被系统强行剥夺。
循环等待条件(Circular Wait):存在一组进程或线程 {P1, P2, …, Pn},其中 P1 等待 P2 占有的资源,P2 等待 P3 占有的资源,依此类推,而 Pn 等待 P1 占有的资源,形成一个闭环。
当这四个条件同时满足时,就可能发生死锁。

如何避免死锁

破坏互斥条件:可以通过设计共享资源来避免互斥条件,或者使用可以共享的资源替代互斥资源。
破坏请求与保持条件:一次性获取所有需要的资源,而不是分批获取。
破坏不剥夺条件:允许系统剥夺某些资源。
破坏循环等待条件:对资源进行编号,并规定进程或线程只能按编号递增的顺序请求资源。
死锁是多线程和并发编程中常见的问题,解决死锁需要合理的资源分配策略和良好的设计思想,以确保系统在高并发和资源竞争的情况下能够稳定运行。

wait和sleep的区别

wait是Object中定义的方法,可以由锁对象调用,让执行到该代码的线程进入等待状态
sleep方法是Thread类中定义的静态方法,也可以让执行到该行代码的线程进入等待状态
区别:
1.sleep需要传入一个毫秒数,到达时间后会自动唤醒。wait不能自动唤醒,必须调用notify或notifyAll方法唤醒
2.sleep方法保持锁状态进入等待状态,wait方法解除锁状态,其他线程可以进入运行

进程被唤醒后变成就绪态

同时synchronized 关键字保证了同一时刻只有一个线程可以获取对象的锁,其他线程必须等待。

public class EasyThreadD {
    public static final Object OBJ = new Object();

    public static void method() {
        System.out.println(Thread.currentThread().getName() + "进入方法");

        synchronized (OBJ) {
            OBJ.notify();//唤醒一条被该锁对象wait的线程
            OBJ.notifyAll();//唤醒全部被锁对象wait的线程
            System.out.println(Thread.currentThread().getName() + "进入同步代码块");
            try {
                try {
                    System.out.println(Thread.currentThread().getName() + "进入等待状态");
                    OBJ.wait();//让执行到该行代码的线程进入等待状态(等待池)
                    //wait状态被唤醒后进入就绪状态
                    System.out.println(Thread.currentThread().getName() + "重新运行");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName() + "结束同步代码块");
            OBJ.notify();
        }
    }

    public static void main(String[] args) {
        Runnable run = EasyThreadD::method;
        Thread a = new Thread(run);
        a.start();
		...
		此次省略部分进程创建代码
    }
}

线程池 池==重用
完成线程创建和管理,销毁工作

public class EasyExecutors {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        BlockingQueue qu=new ArrayBlockingQueue(12);
        ThreadPoolExecutor tpe = new ThreadPoolExecutor(5,10,10,
                TimeUnit.SECONDS,qu,Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
        //线程任务:runnable,callable
        Runnable run=EasyExecutors::method;
        tpe.execute(run);
        Callable<String> call=EasyExecutors::methodCall;
        Future<String> f=tpe.submit(call);
        //tpe.submit(run);
        System.out.println(f.get());//会等待线程执行完毕

        //关闭线程池对象
        tpe.shutdown();
    }
    public static void method(){
        System.out.println(Thread.currentThread().getName()+"执行代码");
    }
    public static String methodCall() throws InterruptedException {
        System.out.println(Thread.currentThread().getName()+"执行代码call");
        Thread.sleep(2000);
        return "CallResult";
    }
}

以上代码中使用了ThreadPoolExecutor池管理工具类
并使用了Runnable 和 Callable两种多线程任务执行方式

ThreadPoolExecutor

是 Java 中用于管理线程池的一个强大类,它提供了灵活的线程池管理功能,允许开发者在应用程序中高效地管理和复用线程。

线程池基本组成

ThreadPoolExecutor 类继承自 ExecutorService 接口,它通过一组线程来执行被提交的任务。线程池中的线程在任务执行完毕后不会销毁,而是被重用,这样可以减少线程创建和销毁的开销。

核心参数:

corePoolSize

线程池的核心线程数,即保持活动状态的线程数,即使线程处于空闲状态也不会被回收。

maximumPoolSize

线程池的最大线程数,包括核心线程数和额外创建的线程数。

keepAliveTime

当线程池中的线程数大于核心线程数时,多余的空闲线程的存活时间。这些线程会在空闲一定时间后被回收,直到线程池中的线程数不大于核心线程数。

TimeUnit unit

这个参数用于指定线程池中各种时间参数的时间单位,例如设置超时时间或者任务等待时间等。TimeUnit 是一个枚举类,包括 NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS 等选项,可以根据实际需要选择合适的时间单位。

BlockingQueue workQueue

这是一个阻塞队列,用于保存等待执行的任务。当线程池的线程数达到 corePoolSize 后,新提交的任务会被放入这个队列中等待执行。常用的实现类包括
ArrayBlockingQueue,
LinkedBlockingQueue,
PriorityBlockingQueue 等,每种实现有不同的特性和适用场景。

ThreadFactory threadFactory

ThreadFactory 用于创建新的线程。它是一个工厂接口,定义了如何创建线程的方法。通过自定义 ThreadFactory,可以指定线程的名称、优先级、是否为守护线程等。这样可以更好地管理线程,便于监控和调试。

RejectedExecutionHandler handler

当线程池无法执行新提交的任务时(通常是由于线程池已经关闭或者达到了最大线程数并且队列已满),RejectedExecutionHandler 定义了一种策略来处理这种情况。常用的策略包括:
ThreadPoolExecutor.AbortPolicy:直接抛出 RejectedExecutionException 异常。
ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)执行这个任务。
ThreadPoolExecutor.DiscardPolicy:直接丢弃这个任务,不做任何处理。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃最老的一个任务,尝试再次提交当前任务。

线程池的生命周期管理

ThreadPoolExecutor 提供了方法来启动、关闭和终止线程池。可以通过 shutdown() 和 shutdownNow() 方法来关闭线程池,保证已提交的任务都被执行完毕或者被中断。

线程池的工作原理

任务提交

客户端通过 ExecutorService 的 submit() 或 execute() 方法提交任务到线程池。

核心线程处理:

当任务被提交到线程池后,看池中是否有空闲的线程,如果有,让该线程执行任务
如果池中没有空闲的线程,判断池中的线程数量是否达到核心线程数
如果当前运行的线程数少于 corePoolSize(核心线程数),则创建新线程来处理任务。

任务队列管理:

如果当前线程数达到 corePoolSize,则新的任务会被放入 workQueue 中等待执行,直到队列满。不同的队列类型有不同的策略(如有界队列会拒绝新任务)。

创建新线程:

workQueue 队列(任务队列)已满之后再添加新的任务,判断当前线程数是否达到maximumPoolSize(最大线程数),如果没有,创建新的线程执行新的任务,直到填满最大线程数

最大线程数限制:

如果线程池中的线程数已经达到 maximumPoolSize,并且任务队列也满了,则根据定义的 handler 策略来处理新提交的任务(可能会抛出异常、运行在调用线程中、丢弃任务或者丢弃最旧的任务)。

空闲线程处理:

如果某个线程在指定的 keepAliveTime 内没有执行任务,并且当前线程数超过 corePoolSize,则将其终止,直到线程数不超过 corePoolSize。

任务执行:

线程池中的线程从 workQueue 中取出任务执行,直到线程池关闭或者任务执行出错。

关闭线程池:

当不再接受新的任务时,可以调用 shutdown() 或 shutdownNow() 方法来关闭线程池。

java中内置的线程池对象

Executors.newCachedThreadPool();

可以根据工作任务创建线程 ,如果没有空闲的线程就创建新的线程,线程存活时间60秒

Executors.newFixedThreadPool(10);

设定最大线程数量

Executors.newScheduledThreadPool(10);

提供定时运行的处理方案

Executors.newSingleThreadExecutor();

创建一个具有单个线程的线程池 保障任务队列完全按照顺序执行

Runnable 和 Callable

Runnable 接口

Runnable 接口是Java中定义多线程任务的基本方式。它是一个函数式接口,其中只有一个 run() 方法需要实现。通常通过实现 Runnable 接口来定义一个可以在多线程环境下执行的任务。

Callable 接口

Callable 接口与 Runnable 类似,但它允许任务返回一个结果,并且可以抛出一个检查异常。它是一个泛型接口,需要实现 call() 方法来执行任务,并且可以返回一个泛型类型的结果。

区别和适用场景

返回值:

Runnable 接口的 run() 方法没有返回值,通常用于执行没有返回结果的任务。
Callable 接口的 call() 方法可以返回一个结果,适用于需要获取任务执行结果的场景。

异常处理:

Runnable 接口的 run() 方法不能抛出已检查异常,只能捕获处理或者在方法内部处理异常。
Callable 接口的 call() 方法可以抛出检查异常,需要在调用时进行异常处理。

并发控制:

使用 Callable 结合 Future 接口可以更好地控制任务的执行状态和获取任务执行结果。

package com.easy725;

import java.util.concurrent.*;

public class EasyExcutorsA {
    public static void main(String[] args) {
        BlockingQueue queue=new ArrayBlockingQueue(12);
        ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(5,8,10,
                TimeUnit.SECONDS, queue,Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardPolicy());
        Runnable run=()->{
            System.out.println(Thread.currentThread().getName()+"执行代码call");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName()+"执行完毕");
        };
        for (int i = 0; i < 21; i++) {
            threadPoolExecutor.execute(run);
        }
        threadPoolExecutor.shutdown();
    }
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值