JAVA小白学习日记Day10

1.线程锁

使用Runnable接口和Lambda表达式:

在 EasyThreadA 类的 mainA 方法中,通过创建 Runnable 实例 run,并使用Lambda表达式。 EasyThreadA::method 绑定到 run 上。然后创建两个线程 a 和 b,分别启动它们,它们会并发地执行 method 方法向共享的 list 中添加元素。这里的 list 是一个静态的 ArrayList,可能存在线程安全问题。
继承Thread类:

ThreadA 类继承自 Thread 类,重写了 run 方法,在其中向自己的 list 中添加元素。在 mainB 方法中创建了两个 ThreadA 实例 a 和 b,启动它们分别执行。每个线程拥有独立的 list,不存在直接的线程安全问题。
实现Runnable接口:

RunA 类实现了 Runnable 接口,在 run 方法中也向自己的 list 中添加元素。在 main 方法中创建了两个 RunA 实例作为 Thread 的任务,分别启动它们。每个 RunA 实例拥有独立的 list,不存在直接的线程安全问题。
需要注意的知识点:
多线程的实现方式:

可以通过实现 Runnable 接口或者继承 Thread 类来创建线程。推荐优先使用实现 Runnable 接口,因为Java中类只能单继承,而实现接口可以更灵活地组合多个接口实现不同的功能。
线程同步和等待:

使用 Thread.sleep(1000) 来模拟线程执行过程中的等待时间。在实际应用中,需要根据具体需求使用合适的线程同步机制,如 synchronized 关键字、Lock 接口、Atomic 类等来确保线程安全性。
Lambda表达式:

在 mainA 方法中使用了Lambda表达式 EasyThreadA::method,简化了匿名内部类的写法,提升了代码的简洁性和可读性。

package com.easy725;

import java.util.ArrayList;
import java.util.List;

public class EasyThreadA {
    static List list=new ArrayList();
    public static void method(){
        for (int i = 0; i <10 ; i++) {
            list.add(i+"A"+Thread.currentThread().getName());
        }
    }

    public static void mainA(String[] args) {
        Runnable run=EasyThreadA::method;
        Thread a=new Thread(run);
        Thread b=new Thread(run);
        a.start();
        b.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(list.size());
    }

    public static void mainB(String[] args) {
        ThreadA a=new ThreadA();
        ThreadA b=new ThreadA();
        a.start();
        b.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(a.list);
        System.out.println(b.list);
    }

    public static void main(String[] args) {
        RunA run=new RunA();
        Thread a=new Thread(run);
        Thread b=new Thread(run);
        a.start();
        b.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(run.list.size());
    }

}
class ThreadA extends Thread{
    public List list=new ArrayList();
    @Override
    public void run() {
        for (int i = 0; i <10 ; i++) {
            list.add("a");
        }
    }
}
class RunA implements Runnable{
    public List list=new ArrayList();
    @Override
    public void run() {
        for (int i = 0; i <10 ; i++) {
            list.add("a");
        }
    }
}

Lock对象的创建和使用:

Lock lock = new ReentrantLock();:创建了一个 ReentrantLock 类型的锁对象 lock。ReentrantLock 是可重入锁,支持公平和非公平性选择,默认是非公平锁(false)。

public void method() 方法中的逻辑:
if (lock.tryLock()) {:尝试获取锁。如果获取成功(返回 true),否则打印当前线程名并输出 "进入方法",然后输出 "结束方法",最后解锁 lock。
else {:如果尝试加锁失败,输出 "加锁未成功-----去执行别的代码",然后通过 Thread.sleep(1000) 模拟执行其他代码的情况,随后递归调用 method() 方法再次尝试获取锁。
main方法中的线程创建和启动:

Runnable run = new EasyThreadB()::method;:创建一个 Runnable 实例,通过方法引用绑定到 EasyThreadB 的 method() 方法上。
Thread a = new Thread(run); 和 Thread b = new Thread(run);:创建两个线程 a 和 b,它们共享同一个 run 实例,即同一个 method() 方法。
a.start(); 和 b.start();:启动两个线程并发执行 method() 方法。
需要注意的知识点:
Lock接口与ReentrantLock类:

Lock 接口提供了比传统的 synchronized 块和方法更广泛的锁定操作。ReentrantLock 是 Lock 接口的实现类,具有可重入特性,允许线程在同一个线程中多次获取同一个锁,避免死锁情况。
tryLock() 方法是 Lock 接口的一部分,尝试获取锁,如果成功则返回 true,否则立即返回 false,不会阻塞线程。这在避免线程长时间等待锁的情况下很有用。最后要unLock()方法解锁。
多线程同步与并发控制:

使用 ReentrantLock 可以更精确地控制多线程的并发访问,可以在需要的时候尝试获取锁,也可以实现公平性或非公平性的锁分配策略。
锁的释放应该始终放在 try 块的 finally 块中,以确保即使在获取锁期间发生异常,锁也能够被安全地释放。

死锁:

进程死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

线程死锁是指由于两个或者两个以上的线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。

死锁的产生还涉及到一些具体的条件,这些条件可以被看作是死锁产生的必要条件,包括:

互斥条件:资源不能被多个进程或线程同时访问,即资源是互斥的。
请求保持条件:进程或线程在请求资源时,已经持有其他资源,并且不愿意释放已经持有的资源。
不可剥夺条件:已经分配给进程或线程的资源,在未使用完之前不能被其他进程或线程剥夺。
循环等待条件:多个进程或线程之间形成一个循环等待链,每个进程或线程都在等待链中下一个进程或线程释放资源。

package com.easy725;

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

public class EasyThreadB {
    //锁对象  Lock
    Lock lock=new ReentrantLock();//创建锁对象,在()中选择是否是公平锁,默认false(非公平锁)。
    public void method(){
        //lock.lock();//加锁
        //lock.trylock()  尝试加锁,加锁成功返回true,失败返回false。
        if (lock.tryLock()) {
            System.out.println(Thread.currentThread().getName() + "进入方法");

            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();
    }
}

读写锁 (ReentrantReadWriteLock):
ReentrantReadWriteLock 是一个锁容器,包含了读锁和写锁,能够提供比普通的互斥锁(如 ReentrantLock)更高的并发性。
读锁允许多个线程同时获取,适合对共享资源进行读取操作。
写锁是排他的,只允许一个线程获取,用于对共享资源进行写操作。
在代码中,通过 rrwl.readLock() 获取读锁,通过 rrwl.writeLock() 获取写锁,确保对共享资源的安全访问。

线程的启动和执行:
使用 Thread 类和 Runnable 接口创建多线程,通过 Thread.start() 启动线程,实现并发执行多个方法。
在 main 方法中,创建了多个读线程和写线程,分别调用 method() 和 methodWrite() 方法。
线程同步和锁的释放:
使用 lock.lock() 和 lock.unlock() 进行锁的获取和释放,确保在多线程环境下对共享资源的安全访问。
一般使用 try-finally 块确保在出现异常时能够正确释放锁,避免死锁或资源泄漏问题。

package com.easy725;

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

public class EasyThreadC {
    //ReentrantReadWriteLock是个锁容器
    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()+"加锁成功----读锁");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"方法结束");
        lock.unlock();
    }
    public static void methodWrite(){
        System.out.println(Thread.currentThread().getName()+"进入方法");
        Lock lock= rrwl.writeLock();
        lock.lock();
        System.out.println(Thread.currentThread().getName()+"加锁成功----写锁");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        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 b=new Thread(run);
        b.start();
        Thread c=new Thread(run);
        c.start();
        Thread d=new Thread(run);
        d.start();
        Thread e=new Thread(run);
        e.start();
        Thread f=new Thread(runWrite);
        f.start();
        Thread g=new Thread(runWrite);
        g.start();
        Thread h=new Thread(runWrite);
        h.start();
        Thread i=new Thread(runWrite);
        i.start();
        System.out.println("main线程结束");
    }
}

同步和对象锁
synchronized 同步块:使用 synchronized (OBJ) 对象锁,确保多个线程在访问共享资源时的安全性。
wait 和 notify:通过 OBJ.wait() 和 OBJ.notify() 方法实现线程的等待和唤醒机制。
wait():使当前线程进入等待状态,并释放对象锁,直到其他线程调用对象的 notify() 或 notifyAll() 方法唤醒它。
notify() 和 notifyAll():唤醒等待在该对象上的一个或多个线程,使它们从等待池中进入就绪状态。
线程生命周期和状态
线程等待和重新运行:展示了线程如何在 wait() 被调用后进入等待状态,并在被唤醒后重新运行。
wait和sleep的区别:
wait是Object中定义的方法,可以由锁对象调用,让执行到该代码的线程进入到等待状态。
sleep是Thread类中定义的静态方法,也可以让执行到该行的线程进入等待状态。
区别:
1.sleep需要传入一个毫秒数,到达时间后会自动唤醒。wait不能自动唤醒,必须调用notify或者notifyALL方法。
2.sleep方法保持锁状态进入等待状态。wait方法会解除锁状态,其他线程可以进入运行。

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 {
                try {
                    System.out.println(Thread.currentThread().getName()+"进入等待状态");
                    OBJ.wait();//让执行到改代码的线程进入到等待状态,在等待池中。
                    System.out.println(Thread.currentThread().getName()+"重新运行");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            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();
        Thread d=new Thread(run);
        d.start();
    }
    //wait和sleep的区别:
    //wait是Object中定义的方法,可以由锁对象调用,让执行到该代码的线程进入到等待状态。
    //sleep是Thread类中定义的静态方法,也可以让执行到该行的线程进入等待状态。
    //区别:
    //1.sleep需要传入一个毫秒数,到达时间后会自动唤醒。wait不能自动唤醒,必须调用notify或者notifyALL方法。
    //2.sleep方法保持锁状态进入等待状态。wait方法会解除锁状态,其他线程可以进入运行。
}

2.线程池

线程池:

ThreadPoolExecutor 是 Java 中用于管理线程池的类,通过它可以有效地重用线程,完成线程的创建管理和销毁工作。
在代码中,通过 ThreadPoolExecutor 的构造方法创建了一个线程池 tpe。
在这里,使用了一个 ArrayBlockingQueue 作为任务队列 qu,它限制了队列的容量为 12。
任务提交与执行:

线程池可以执行两种类型的任务:Runnable 和 Callable。
Runnable run = EasyExecuters::method; 定义了一个简单的 Runnable 任务,它会调用 method 方法。
tpe.execute(run); 提交 run 任务给线程池执行。
Callable<String> call = EasyExecuters::methodCall; 定义了一个 Callable 任务,它会调用 methodCall 方法,并返回一个结果。
Future<String> f = tpe.submit(call); 提交 call 任务给线程池执行,并获取一个 Future 对象,用于获取任务的执行结果。
任务执行结果获取:

System.out.println(f.get()); 使用 Future 对象的 get() 方法来获取 call 任务的执行结果。这是一个阻塞方法,会等待任务执行完毕并返回结果。
线程池的关闭:

tpe.shutdown(); 调用 shutdown() 方法关闭线程池。这会使线程池停止接受新的任务,并尝试将现有的任务执行完毕后关闭。

package com.easy725;

import java.util.Collection;
import java.util.Iterator;
import java.util.Queue;
import java.util.concurrent.*;

public class EasyExecuters {
    //线程池   池==重用
    //完成线程创建,管理,销毁工作
    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=EasyExecuters::method;
        tpe.execute(run);
        Callable<String> call=EasyExecuters::methodCall;
        Future<String> f=tpe.submit(call);
        //f.cancel(true);//是否取消任务,一种中断线程的方式
        //tpe.submit(run);
        System.out.println(f.get());//会等待线程执行完毕
        //关闭线程池对象
        tpe.shutdown();
    }
    public static void method(){
        System.out.println(Thread.currentThread().getName()+"执行代码");

    }
    public static String methodCall(){
        System.out.println(Thread.currentThread().getName()+"执行代码call");
        return "callResult";
    }
}

1. 线程池的七个参数解释
在代码中,通过 ThreadPoolExecutor 的构造方法指定了以下七个参数:

corePoolSize: 核心线程数,即线程池中保持活跃的线程数量,即使它们是空闲的也会保留在池中。
maximumPoolSize: 最大线程数,线程池中允许的最大线程数量。当活跃线程数达到核心线程数,并且工作队列已满时,会创建新的线程,直到达到这个最大值。
keepAliveTime: 空闲线程的存活时间。超过核心线程数的线程在空闲超过这个时间后会被销毁,直到线程池的大小重新变为核心线程数为止。
unit: keepAliveTime 的时间单位。
workQueue: 工作队列,用于保存等待执行的任务。在本例中使用了 ArrayBlockingQueue,其容量为 12。
threadFactory: 线程工厂,用于创建新线程。
handler: 回绝策略,用于处理无法执行的任务。在本例中使用了 DiscardPolicy,即直接丢弃新任务而不抛出异常。

2.四种回绝策略(可以自定义)
AbortPolicy(默认):放弃该任务并会抛出一个异常,RejectedExecutionException。
CallerRunsPolicy:调用者执行,让传递任务的线程执行此任务。
DiscardOldestPolicy:放弃队列中时间最长的任务,不会抛出异常。
DiscardPolicy:直接放弃新的任务,不会抛异常。


3. 线程池的工作原理
当任务提交给线程池时,线程池会按照以下步骤处理任务:
如果当前活跃线程数小于核心线程数,创建新的线程来执行任务。
如果当前活跃线程数等于核心线程数,将任务加入工作队列。
如果工作队列已满但未达到最大线程数,创建新线程来执行任务。(占最大线程数)
如果工作队列已满且线程数已达到最大值,执行指定的回绝策略来处理新任务。


4. 内置的线程池对象
Java 提供了几种常见的内置线程池对象,可以根据应用的需要选择合适的线程池:

Executors.newCachedThreadPool(): 可根据需要(没有空闲就创建新线程)的线程池,空闲线程会在 60 秒后被回收。
Executors.newFixedThreadPool(int n): 固定大小的线程池,可以控制最大线程数。
Executors.newScheduledThreadPool(int corePoolSize): 支持定时和周期性的处理方案。
Executors.newSingleThreadExecutor(): 只有一个线程的线程池,确保所有任务按顺序执行。


5. 任务执行与线程池关闭
在代码中,通过 execute() 方法提交 Runnable 任务给线程池执行。
最后调用 shutdown() 方法关闭线程池,这会使线程池停止接受新任务,并尝试将已有的任务执行完毕后关闭。

package com.easy725;

import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.*;

public class EasyExecutersA {
    public static void main(String[] args) {
        //1.说明线程池的七个参数
        //2.四种回绝策略(可以自定义)
        //AbortPolicy(默认):放弃该任务并会抛出一个异常,RejectedExecutionException。
        //CallerRunsPolicy:调用者执行,让传递任务的线程执行此任务。
        //DiscardOldestPolicy:放弃队列中时间最长的任务,不会抛出异常。
        //DiscardPolicy:直接放弃新的任务,不会抛异常。
        //3.线程池的工作原理:
        //  任务放置在工作队列中
        //1>池中是否有空闲的线程,如果有,让该线程执行任务。
        //2>如果池中没有空闲的线程,判断线程数量是否达到核心线程数。
        //3>如果没有达到,创建新的线程执行任务,直到填满核心数。如果已经达到,优先在队列中存储,直到队列填满。
        //4>工作队列填满之后再添加新的任务,判断是否达到最大线程数,如果没有,创建新的线程执行任务,直到填满最大线程数。
        //5>如果填满最大线程数,队列也已经填满,没有空闲的线程,就执行回绝策略。
        //线程池中的线程达到(超过)核心线程数,超出的数量会根据存活时间进行销毁。直到数量达到核心线程数。如果线程的数量少于核心线程数,不会消亡。

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


        BlockingQueue queue=new ArrayBlockingQueue(12);
        ThreadPoolExecutor tpe=new ThreadPoolExecutor(5,
                8,10,
                TimeUnit.SECONDS,queue,
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardPolicy());
        Runnable run =()->{
            System.out.println(Thread.currentThread().getName()+"执行run方法");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"执行结束");
        };
        for (int i = 0; i <21 ; i++) {
            tpe.execute(run);
        }
        tpe.shutdown();
    }
}

3.枚举

枚举类 默认继承Enum类
首行必须枚举所有的实例。Enum的实例是可比较的(根据实例出来的顺序)。但是不可序列化,不能被new,克隆等操作。
当枚举中只有一个实例时,这个类就是单例的。

package com.easy725;

public enum  EasyColor {
    //枚举类 默认继承Enum类
    //首行必须枚举所有的实例。Enum的实例是可比较的(根据实例出来的顺序)。但是不可序列化,不能被new,克隆等操作。
    //当枚举中只有一个实例时,这个类就是单例的。
    RED,YELLOW,GREEN,BLUE,PINK;
    public void printColor(){
        System.out.println(this.name());
        System.out.println(this.ordinal());
    }

}
class Test{
    public static void main(String[] args) {
        EasyColor.PINK.printColor();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值