Java学习Day10

线程锁对象

wait  notify(notifyAll)  

使用wait方法使执行到代码的线程进入等待状态(线程池)

使用notify方法唤醒一条线程,唤醒被该锁对象wait的线程

使用notifyAll方法唤醒全部被该锁对象wait的线程

使用notify(notifyAll)唤醒被wait()方法挂起的线程时,必须使用与该线程等待时使用的对象锁相同的对象锁。

当一个线程调用对象的wait()方法时,它会释放持有的对象锁,并进入等待状态(等待其他线程调用对象的notify()或notifyAll()方法唤醒它)。

package com.easy725;

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 {
                Thread.sleep(1000);
                try {
                    System.out.println(Thread.currentThread().getName()+"进入等待");
                    OBJ.wait();//让执行到代码的线程进入等待状态(等待池)
                    System.out.println(Thread.currentThread().getName()+"进");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } 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();
        Thread b = new Thread(run);
        b.start();
        Thread c = new Thread(run);
        c.start();
    }
    
}

当使用notify()或notifyAll()方法唤醒等待中的线程时,必须持有与该线程等待时使用的对象锁相同的对象锁。这是因为,这些方法是作用在对象锁上的,只有持有相同对象锁的线程才能够执行这些唤醒操作。

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

Lock

Lock lock = new ReentrantLock();//创建锁对象,在参数中传入true即可成为公平锁,常用锁默认为非公平锁

创建锁对象lock,对锁对象进行加锁的操作:

lock.lock()加锁操作

lock.trylock()尝试加锁操作,加锁成功返回true,失败返回false

lock.unlock()解锁操作

package com.easy725;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class EasyThreadE {
    public static void main(String[] args) {
        EasyList list = new EasyList();
        Runnable runSize=()->{list.size();};
        Runnable runGet=()->{list.get(0);};
        Runnable runAdd=()->{list.add(12);};
        list.add(12);
        Thread a = new Thread(runSize);
        Thread b = new Thread(runGet);
        Thread c = new Thread(runAdd);
        a.start();b.start();c.start();
    }
}
class EasyList{
    private int[] values=new int[20];
    private int size=0;

    ReentrantReadWriteLock rwLock=new ReentrantReadWriteLock();

    public int size(){
        Lock readLock = rwLock.readLock();
        readLock.lock();
        System.out.println(Thread.currentThread().getName()+"Size开始");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread().getName()+"Size结束");
        readLock.unlock();
        return size;
    }

    public int get(int index){
        Lock readLock = rwLock.readLock();
        readLock.lock();
        System.out.println(Thread.currentThread().getName()+"Get开始");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        if(index>=size){
            throw new IndexOutOfBoundsException("index is "+index);
        }
        System.out.println(Thread.currentThread().getName()+"Get结束");
        readLock.unlock();
        return values[index];
    }

    public boolean add(int item){
        Lock writeLock= rwLock.writeLock();
        writeLock.lock();
        System.out.println(Thread.currentThread().getName()+"Add开始");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        if(size>=values.length){
            return false;
        }
        values[size++]=item;
        System.out.println(Thread.currentThread().getName()+"Add结束");
        writeLock.unlock();
        return true;
    }
}

ReentrantLock :ReentrantLock(可重入锁)是 Java 中提供的一种高级锁机制,它实现了 Lock 接口,允许更灵活地控制多线程对共享资源的访问。下面详细介绍 ReentrantLock 的特性和用法:

特性和优势:

ReentrantLock 支持线程重复获取同一个锁,即同一个线程可以多次获得该锁而不会死锁。这种特性与 synchronized 关键字类似,但是 ReentrantLock 提供了更高的灵活性和控制。

可以通过 ReentrantLock 的构造函数指定是否使用公平锁(fairness),公平锁尽量保证等待时间最长的线程优先获得锁。非公平锁(默认)可能会导致某些线程长时间等待,但整体吞吐量可能更高。

ReentrantLock 提供了与 Condition 接口配合使用的条件变量,可以让线程在等待某个条件时释放锁并进入等待状态,待条件满足后重新获取锁继续执行。

支持响应中断,即当一个线程在等待锁的过程中可以响应中断信号,这在需要提供线程中断支持的场景下非常有用。

提供了 tryLock() 方法,可以尝试获取锁而不会阻塞,可以根据方法的返回值来判断是否获取了锁。

提供了 tryLock(long time, TimeUnit unit) 方法,尝试在指定的时间内获取锁,超时则返回失败。

ReentrantReadWriteLock:

ReentrantReadWriteLock 是 Java 中提供的一种读写锁(ReadWrite Lock)实现,将锁分为读锁和写锁两种,它允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。

多个线程可以同时持有读锁,并发读取共享资源,不会阻塞彼此,从而提高程序的并发性能。

写锁是排他的,即当某个线程持有写锁时,其他线程无法获取读锁或写锁,直到写锁被释放。

线程池  

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

线程池的定义

ThreadPoolExecutor tpe = new ThreadPoolExecutor(5,10,
        10, TimeUnit.SECONDS,qu,Executors.defaultThreadFactory(),
        new ThreadPoolExecutor.AbortPolicy());

线程池对象执行

Runnable run = EasyExecuters::method;
tpe.execute(run);

public static void method(){
    System.out.println(Thread.currentThread().getName()+"执行代码");
}

1.说明线程池的7个参数

这些参数包括核心线程数、最大线程数、线程空闲时间、时间单位、工作队列、线程工厂、拒绝策略。

核心线程数(corePoolSize):

线程池中保持活动状态的最小线程数,即使它们处于空闲状态也不会被回收。在没有任务执行时,核心线程也会保持存活。如果提交的任务数超过了核心线程数,线程池可能会创建额外的线程来处理任务,直到达到最大线程数。

最大线程数(maximumPoolSize):

线程池允许创建的最大线程数。当工作队列已满,并且当前线程数小于最大线程数时,线程池会创建新的线程来处理任务,直到达到最大线程数。超过最大线程数的任务将会被拒绝,根据拒绝策略来处理。

线程空闲时间(keepAliveTime):

当线程池中的线程数量大于核心线程数时,空闲线程的存活时间。超过这个时间,多余的空闲线程会被销毁,直到线程池中的线程数不超过核心线程数。

时间单位(unit):

线程空闲时间的单位,通常是秒(TimeUnit.SECONDS)、毫秒(TimeUnit.MILLISECONDS)等。

工作队列(workQueue):

用于保存等待执行的任务的队列。线程池中的线程会从这个队列中取任务来执行。常见的队列类型包括有界队列(如 ArrayBlockingQueue)和无界队列(如 LinkedBlockingQueue)。

线程工厂(threadFactory):

用于创建新线程的工厂,可以自定义线程的名称、优先级、是否为守护线程等。

拒绝策略(handler):

当工作队列已满,并且线程池中的线程数达到最大线程数时,用于拒绝新任务的策略。

2.常见的四种拒绝策略有:

AbortPolicy:默认的策略,放弃该任务并抛出RejectedExecutionException 异常。

CallerRunsPolicy:将任务交给调用线程来执行。

DiscardPolicy:静默丢弃新任务。

DiscardOldestPolicy:丢弃队列中最旧的(时间最长的)任务,尝试再次提交当前任务。

3.线程池的工作原理:
任务放置在工作队列中
池中是否有空闲的线程,如果有线程让该线程执行任务。
如果池中没有空闲的线程,判断池中的线程数量有没有达到核心线程数
如果没有达到,创建新的线程执行任务,直到填满核心数,如果已经达到,优先在队列中存储直到队列填满
工作队列填满后再添加新的任务,判断是否达到最大线程数,如果没有创建新的线程执行任务直到填满最大线程数
已经填满最大线程数,队列也已经填满,没有空闲的空间,就执行回绝策略
线程池中的线程达到核心线程数,超出的数量会根据存活时间进行销毁,直到数量达到核心线程数
如果线程的数量少于核心线程数,不会消亡

Runnable 和 Callable

Runnable 是一个函数式接口,用于表示一个可以由线程执行的任务。它只有一个 run() 方法,没有返回值,也不能抛出受检查的异常。

特点:run() 方法没有返回值,因此任务执行完毕后不能直接获取执行结果。

不能抛出受检查的异常,只能通过捕获异常并在 run() 方法内部处理。

用法:可以通过实现 Runnable 接口或者使用 Lambda 表达式来创建一个需要执行的任务。通常通过 Thread 类的构造方法或者线程池执行器(ExecutorService)的 submit(Runnable task) 方法来执行。

Callable 接口与 Runnable 相似,但是它可以返回一个结果并且可以抛出受检查的异常。Callable 是一个泛型接口,返回值类型由泛型参数指定。

特点:call() 方法可以返回一个结果,并且可以抛出受检查的异常。

可以通过 Future 或者 ExecutorService 的 submit(Callable<V> task) 方法提交任务,并获取任务的执行结果。

用法:可以通过实现 Callable 接口或者使用 Lambda 表达式来创建一个需要执行的任务。调用 submit(Callable<V> task) 方法后会返回一个 Future<V> 对象,通过该对象可以获取任务的执行结果。

@FunctionalInterface

public interface Callable<V> {

    V call() throws Exception;

}

Future

在Java中,Future 是一个接口,用于表示异步计算的结果。它允许你在一个任务(线程)中提交一个任务(Callable 或 Runnable 对象),并在稍后的某个时候获取计算的结果。

主要特点和用途

Future 接口的主要作用是表示一个异步计算的结果。当你提交一个任务给线程池或者使用其他的执行框架时,会得到一个 Future 对象,该对象可以用来获取任务的执行结果或者取消任务的执行。

通过 Future 对象的 get() 方法可以获取异步任务的计算结果。如果任务还没有完成,get() 方法会阻塞调用线程直到任务完成并返回结果。

可以调用 cancel() 方法来取消任务的执行。如果任务还没有开始执行,或者在取消任务时设置了 mayInterruptIfRunning 参数为 true,则会尝试中断执行任务的线程。

可以使用 isDone() 方法来查询任务是否已经完成,使用 isCancelled() 方法来查询任务是否被取消。

Future 接口还提供了 get(long timeout, TimeUnit unit) 方法,允许在指定的时间范围内等待任务完成,超时则抛出 TimeoutException 异常。

如果任务执行过程中抛出了异常,那么 get() 方法将会抛出 ExecutionException,可以通过 getCause() 方法获取到实际的异常。

四种内置线程池

Java中内置线程池对象 可以根据工作任务创建线程
如果没有空闲的线程就创建新的线程,线程存活时间60s,
Executors.newCachedThreadPool();
设定最大线程数量
Executors.newFixedThreadPool(10);
提供定时运行的处理方案
Executors.newScheduledThreadPool(10);
创建一个具有单个线程的线程池 保障任务队列完全按照顺序执行
Executors.newSingleThreadExecutor();

死锁

死锁(Deadlock)是指在多线程或多进程环境中,由于各个线程(或进程)之间互相持有对方所需的资源而导致的一种无限等待的状态。

如果没有外部干预,这些进程将无法向前推进。这种状态被称为系统死锁或死锁产生。这些相互等待的进程被称为死锁进程。

产生死锁的四个必要条件

1. 互斥条件: 一个资源每次只能被一个进程使用

2. 占有且等待:一个进程因请求资源而阻塞时,对已获得的资源保持不放

3. 不可强行占有: 进程已获得的资源,在末使用完之前,不能强行剥夺。

4. 循环等待条件: 若干进程之间形成一种头尾相接的循环等待资源关系

可以通过以下几种方法来尽量减少死锁的发生:

加锁顺序:尽量按照固定的顺序获取多个资源的锁。

加锁和超时:尝试使用 tryLock() 方法来获取锁,并设置超时时间,避免无限等待。

资源分配图:分析线程之间的依赖关系,避免循环等待条件的出现。

死锁检测:定期检测系统中是否存在死锁,并采取相应的措施来解决。

一旦发生死锁,可以通过以下方法来处理:

资源剥夺:中断一个或多个线程,释放其持有的资源。

撤销和重试:撤销一些或者所有线程的操作,并重试执行。

忽略死锁:在某些情况下,可以选择忽略死锁,等待系统自行恢复(例如超时)。

  • 22
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值