多线程学习笔记

多线程学习笔记

一、概念

  • 线程是一个程序内部的一条执行流程。

  • 程序中如果只有一条执行流程,那这个程序就是单线程的程序。

  • 多线程是指从软硬件上实现的多条执行流程的技术(多条线程由CPU负责调度执行)。

  • Java虚拟机允许应用程序同时执行多个执行线程

  • 每个线程都有优先权。 具有较高优先级的线程优先于优先级较低的线程执行。

二、如何创建线程

  • 启动线程必须调用start方法而不是run方法,start方法可以开启一个线程,直接调用run方法会当初普通对象的方法执行,此时没有启动线程执行。
  • main方法是一条默认的主线程负责执行

方法一:继承Thread类

  1. 任意类继承线程类Thread。

  2. 重写run方法,描述线程任务。

    //继承Thread线程类
    public class MyThread extends Thread{//重写run方法,描述线程的执行任务@Overridepublic void run() {for (int i = 1; i < 5; i++) {System.out.println("子线程Mythread:"+i);}}
    }
    
  3. 主线程创建类对象,调用start方法启动线程。

    /*** 线程测试一:继承Thread创建线程* @author 鹿先生* @date 2023/08/29*/
    public class TreadTest1 {//main方法是一条默认的主线程负责执行public static void main(String[] args) {Thread thread = new MyThread();thread.start();//启动一个子线程//主线程的业务for (int i = 1; i < 5; i++) {System.out.println("主线程:"+i);}}
    }
    

优点:编码简单

缺点:线程类已经继承Thread,无法继承其他类,不利于功能的扩展,也就是具有单继承的问题。

方法二:实现Runnable接口

基础版

  1. 任务类实现Runnable接口。

  2. 重写run方法,描述 线程任务。

    //任务类
    public class MyRunnable implements Runnable{//重写run方法,描述线程任务@Overridepublic void run() {for (int i = 1; i <= 5; i++) {System.out.println("子线程MyRunable:"+i);}}
    }
    
  3. 主线程中创建任务类,并作为参数创建线程对象,启动子线程。

    /*** 线程测试二:实现Runnable接口* @author 鹿先生* @date 2023/08/29*/
    public class TreadTest2 {//main方法是一条默认的主线程负责执行public static void main(String[] args) {Runnable runable = new MyRunnable();//创建任务类对象Thread thread = new Thread(runable);//使用任务类创建线程thread.start();//启动线程//主线程的业务for (int i = 1; i <= 5; i++) {System.out.println("主线程:"+i);}}
    }
    

简写版

/*** 线程测试二:实现Runnable接口(使用匿名内部类简写)* 三种简写方法* @author 鹿先生* @date 2023/08/29*/
public class TreadTest2_2 {public static void main(String[] args) {//简写方法一:先使用Runnable创建匿名内部类,然后创建线程传入Runnable对象启动Runnable runnable = new Runnable() {@Overridepublic void run() {for (int i = 1; i <= 5; i++) {try {Thread.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程1:"+i);}}};new Thread(runnable).start();//简写方法二:创建线程时,Runnable接口创建匿名内部类,然后直接启动。new Thread(new Runnable(){@Overridepublic void run() {for (int i = 1; i <= 5; i++) {try {Thread.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程2:"+i);}}}).start();//简写方法三:由于Runnable是函数式接口,所以我们可以使用lambda表达式简写new Thread(() -> {for (int i = 1; i <= 5; i++) {try {Thread.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程3:"+i);}}).start();}
}

优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强。

缺点:需要多创建一个Runnable对象。

方法三:实现Callable接口

  • 这个方法可以获取线程执行完后的返回值,但是编码稍微复杂一点点。
  1. 任何一个类实现Callable接口,指定返回值泛型,然后重写call方法,描述线程任务。

  2. 主线程创建该类对象,封装到FutrueTask未来任务对象中,然后创建线程对象,直接启动线程。

    //实现Callable接口,指定返回值类型
    public class MyCallable implements Callable<String> {private int n;public MyCallable(int n) {this.n = n;}//重写call方法,描述线程任务@Overridepublic String call() throws Exception {int sum = 0;for (int i = 1; i <= n; i++) {sum+=i;}return "1-"+n+"的累加和结果为:"+sum;}
    }
    
  3. 使用FutrueTask的get方法获取线程执行完成后的返回结果。

    /*** 线程测试三:实现Callable接口* @author 鹿先生* @date 2023/08/29*/
    public class TreadTest3 {public static void main(String[] args) throws ExecutionException, InterruptedException {Callable callable = new MyCallable(100);//创建Callable对象,描述线程任务FutureTask<String> futureTask = new FutureTask<String>(callable);//使用Callable对象封装成未来任务对象new Thread(futureTask).start();//启动线程执行未来任务对象的线程任务//注意:当线程未执行完时,futureTask.get()会阻塞在这,等待线程执行完毕,直到线程执行完,才能获取到结果System.out.println(futureTask.get());//打印线程执行完后的返回结果}
    }
    

优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强;可以在线程执行完毕后获取线程执行的结果。

缺点:编码复杂一点。

三、线程的常用方法

1.方法

常用方法

// 1.线程的任务方法
public void run()
// 2.启动线程
public void start()
// 3.获取当前线程的名称,线程名称默认是Thread-索引
public String getName()
// 4.为线程设置名称
public void setName(String name)
// 5.获取当前执行的线程对象
public static Thread currentThread()
// 6.让当前执行的线程休眠多少毫秒后,再继续执行
public static void sleep(long time)
// 7.让调用这个方法的线程先执行完,再继续执行其他代码
public final void join()

构造方法

// 1.可以为当前线程指定名称
public Thread(String name)
// 2.封装Runnable对象成为线程对象
public Thread(Runnable target)
// 3.封装Runnable对象成为线程对象,并指定线程名称
public Thread(Runnable target,String name)

2.案例

线程类

//继承Thread线程类
public class MyThread extends Thread{//无参构造public MyThread() {}//有参构造public MyThread(String name) {super(name);//调用父类构造函数Thread(String name)创建线程时指定线程名称}//重写run方法,描述线程的执行任务@Overridepublic void run() {for (int i = 1; i <= 5; i++) {//打印线程名称System.out.println(Thread.currentThread().getName()+"线程:"+i);}}
}

主线程类

/*** 线程常用方法案例* @author 鹿先生* @date 2023/08/30*/
public class TreadTest1 {public static void main(String[] args) throws Exception {MyThread thread1 = new MyThread();thread1.setName("1号线程");thread1.start();//启动一个子线程MyThread thread2 = new MyThread("2号线程");thread2.start();//启动一个子线程thread2.join();//必须等到2号线程执行完,程序才会往下走(也就是说3号线程永远在2号线程后面)MyThread thread3 = new MyThread("3号线程");thread3.start();//启动一个子线程//获取主线程名String mainName = Thread.currentThread().getName();//主线程的业务for (int i = 1; i <= 5; i++) {if(i==5)Thread.sleep(5000);//当i等于5时,线程休息5秒钟System.out.println(mainName+"线程:"+i);}}
}

注意:Thread类还提供了诸如:yield、interrupt、守护线程、线程优先级等线程的控制方法,在开发中很少使用。

四、线程安全

1. 线程安全问题

  • 多个线程同时操作同一个共享资源的时候,可能会出现业务安全的问题。

  • 线程安全问题出现的原因

    • 存在多个线程在同时执行
    • 同时访问一个共享资源
    • 存在修改该共享资源

2. 用程序模拟线程安全问题

  • 小明和小红同时取钱,银行会亏十万,这就发生了线程安全问题。

1.账户类

//账户类
public class Account {private String cardId;//卡号private double money;//余额public Account() {}public Account(String cardId, double money) {this.cardId = cardId;this.money = money;}public String getCardId() {return cardId;}public void setCardId(String cardId) {this.cardId = cardId;}public double getMoney() {return money;}public void setMoney(double money) {this.money = money;}//取钱public void toriMoney(Double wantMoney) {String name = Thread.currentThread().getName();//获取线程名if (money >= wantMoney) {//余额大于十万元,就取走System.out.println(name + "来取钱了,要取十万元");money -= wantMoney;System.out.println(name + "取钱成功了,还剩余额:" + money);} else {System.out.println(name + "取钱失败:余额不足!");}}
}

2.线程类

//继承Thread线程类
public class MyThread extends Thread {private Account acc;//构造函数传入待操作账户和线程名public MyThread(Account acc, String name) {super(name);//利用父类Thread构造方法指定线程名this.acc = acc;}//重写run方法,描述线程的执行任务@Overridepublic void run() {acc.toriMoney(100000.0);//取钱}
}

3.两个账户同时取钱

/*** 测试线程安全问题*/
public class ThreadTest {public static void main(String[] args) {//创建一个账户,余额十万元Account account = new Account("ICBC-110", 100000);//小明小红线程同时取钱new MyThread(account, "小明").start();new MyThread(account, "小红").start();}
}

五、线程同步(线程安全的解决办法)

1.线程思想概述

  • 线程同步是解决线程安全问题的方案(放生线程安全问题时就是因为线程时异步的,所以我们选择同步就没问题了)。
  • 思想;让多个线程实现先后依次访问共享资源,这样就解决了安全问题。
  • 线程同步的原理:就是加锁,每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动释放锁,然后其他线程才能再加锁进来。

2.方式一:同步代码块

原理:把访问共享资源的核心代码给上锁,以此保证线程安全。

  • 对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug。

  • 建议使用共享资源作为锁对象,对于实例方法建议使用this作为锁对象

  • 对于静态方法建议使用字节码(类名.class)对象作为锁对象。

  • 字符串常量在常量池只会保留一份。

synchronized(同步锁){访问共享资源的核心代码
}

案例:

//账户类
public class Account {......//静态代码块实现线程同步,对于静态方法,锁对象使用类的字节码public static void count(){synchronized (Account.class){System.out.println("我是静态方法。");}}......//取钱public void toriMoney(Double wantMoney) {//静态代码块实现线程同步,建议使用共享资源作为锁对象(这个地方小明和小红的共享资源就是账户,也就是当前的账户对象)//而且这样锁的范围小一些,不会出bug锁住其他账户//多个线程操作同一个共享资源,同一时刻只有一个线程能获取锁,也就是一个一个来。synchronized (this) {String name = Thread.currentThread().getName();//获取线程名if (money >= wantMoney) {//余额大于十万元,就取走System.out.println(name + "来取钱了,要取十万元");money -= wantMoney;System.out.println(name + "取钱成功了,还剩余额:" + money);} else {System.out.println(name + "取钱失败:余额不足!");}}}
}

3.方式二:同步方法(推荐)

原理:把访问共享资源的核心方法给上锁,以此保证线程安全。

  • 同步方法其实底层也是有隐式锁对象的,也就是默认有锁对象,锁的范围是整个方法代码。
  • 如果是普通方法,锁对象就是当前对象;如果是静态方法,锁对象就是当前类的字节码。
修饰符 synchronized 返回值类型 方法名称(形参列表){操作共享资源的代码
}

案例:

//账户类
public class Account {......//同步方法实现线程同步,对于静态方法,锁对象默认使用类的字节码public synchronized static void count() {System.out.println("我是静态方法。");}......//取钱public synchronized void toriMoney(Double wantMoney) {//同步方法实现线程同步,synchronized对于实例方法,锁对象默认就是当前对象String name = Thread.currentThread().getName();//获取线程名if (money >= wantMoney) {//余额大于十万元,就取走System.out.println(name + "来取钱了,要取十万元");money -= wantMoney;System.out.println(name + "取钱成功了,还剩余额:" + money);} else {System.out.println(name + "取钱失败:余额不足!");}}
}

优点:可读性比同步代码块好。

缺点:同步方法锁的范围比同步代码块的范围更大,类似提前排队,性能略差,但是对于当今社会的计算机性能而言,可以忽略不记。

4.方式三:Lock锁

  • Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大。
  • Lock是接口,不能直接实例化,可以采用它的实现类ReentranLock来构建Lock锁对象。
  • 手动加锁和解锁。

案例:

//账户类
public class Account {......//使用final修饰表示锁唯一,不能二次修改;并且在对象的属性里创建lock对象属性表示一个对象一个锁。private final Lock lk = new ReentrantLock();......//取钱public void toriMoney(Double wantMoney) {try {lk.lock();//加锁String name = Thread.currentThread().getName();//获取线程名if (money >= wantMoney) {//余额大于十万元,就取走System.out.println(name + "来取钱了,要取十万元");money -= wantMoney;System.out.println(name + "取钱成功了,还剩余额:" + money);} else {System.out.println(name + "取钱失败:余额不足!");}} catch (Exception e) {e.printStackTrace();} finally {lk.unlock();//无论业务是否成果执行,都会解锁,代码健壮性更强}}
}

六、线程通信(了解)

  • 线程通信:当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,以相互协调,并避免无效的资源争夺。
  • 线程通信的常见模型(生产者与消费者模型)
    • 生产者线程负责生产数据
    • 消费者线程负责消费生产者生产的数据。
    • 注意:生产者生产完数据应该等待自己,通知消费者;消费者消费完数据也应该等待自己,再通知生产者生产!(等待就是释放锁,不再竞争cpu资源)
  • 线程通信的前提是保证线程安全。
  • Object类的等待和唤醒方法(这些方法必须使用锁对象调用):
//让当前线程等待并释放所占锁,直到另一个线程调用notify()方法或notifyAll()方法
void wait()
//唤醒正在等待的单个线程
void notyfy()
//唤醒正在等待的所有线程
void notifyAll()
  • 线程等待和唤醒是操作系统中线程同步的重要机制之一。线程等待是指线程在执行过程中暂停执行,等待某个事件发生后继续执行;而唤醒则是等待的相反操作,它让一个处于等待状态的线程重新获得执行权
  • 当一个线程获取到锁后,其他线程会进入等待状态(锁释放后会自动唤醒其他线程)。

案例

桌子类

//桌子实体类
public class Desk {private List<String> list = new ArrayList<>();//桌子上放包子的地方//put和get两个方法都添加了synchronized,它们锁的是同一个对象,就是桌子对象,会同时锁住三个厨师和两个吃货。//先唤醒其他线程,然后再等待//完成线程任务就需要释放锁,所以需要先唤醒其他线程,然后等待当前线程。//生产包子public synchronized void put() {String name = Thread.currentThread().getName();//获取线程名if (list.isEmpty()){//没有包子,需要做包子list.add(name+"做的肉包子!");System.out.println(name+"做了一个肉包子!");try {Thread.sleep(3000);this.notifyAll();//唤醒所有等待线程this.wait();//当前线程释放锁,等待} catch (Exception e) {e.printStackTrace();}}else {//有包子,不用做this.notifyAll();//唤醒所有等待线程try {this.wait();//当前线程释放锁,等待} catch (Exception e) {e.printStackTrace();}}}//吃包子public synchronized void get() {String name = Thread.currentThread().getName();//获取线程名if (!list.isEmpty()){//有包子,可以吃System.out.println(name+"吃了"+list.get(0));list.clear();try {Thread.sleep(1000);this.notifyAll();//唤醒所有等待线程this.wait();//当前线程释放锁,等待} catch (Exception e) {e.printStackTrace();}}else {//没有包子this.notifyAll();//唤醒所有等待线程try {this.wait();//当前线程释放锁,等待} catch (Exception e) {e.printStackTrace();}}}
}

主线程类

//包含五个线程:三个生产者线程和两个消费者线程
//线程通信案例
public class ThreadTest {public static void main(String[] args) {//创建一个桌子对象Desk desk = new Desk();// 启动五个线程new Thread(() -> {while (true) {desk.put();//生产包子}}, "厨师1").start();new Thread(() -> {while (true) {desk.put();//生产包子}}, "厨师2").start();new Thread(() -> {while (true) {desk.put();//生产包子}}, "厨师3").start();new Thread(() -> {while (true) {desk.get();//吃包子}}, "吃货1").start();new Thread(() -> {while (true) {desk.get();//吃产包子}}, "吃货2").start();}
}

注意

  • 这个案例咱们手动唤醒和等待其他线程,更好的理解线程通信。(其实每次任务执行完,锁释放后,其他线程就会自动被唤醒,不需要我们手动唤醒;线程的等待和唤醒都是自动的)。
  • 下面的写法跟上面的写法效果一样。
//桌子实体类
public class Desk {private List<String> list = new ArrayList<>();//桌子上放包子的地方//put和get两个方法都添加了synchronized,它们锁的是同一个对象,就是桌子对象,会同时锁住三个厨师和两个吃货。//先唤醒其他线程,然后再等待//完成线程任务就需要释放锁,所以需要先唤醒其他线程,然后等待当前线程。//生产包子public synchronized void put() {String name = Thread.currentThread().getName();//获取线程名if (list.isEmpty()){//没有包子,需要做包子list.add(name+"做的肉包子!");System.out.println(name+"做了一个肉包子!");try {Thread.sleep(3000);} catch (Exception e) {e.printStackTrace();}}}//吃包子public synchronized void get() {String name = Thread.currentThread().getName();//获取线程名if (!list.isEmpty()){//有包子,可以吃System.out.println(name+"吃了"+list.get(0));list.clear();try {Thread.sleep(1000);} catch (Exception e) {e.printStackTrace();}}}
}

七、线程池

1.概述

  • 线程池是一个可以复用线程的技术。
  • 不使用线程池的问题:用户每发起一个请求,后台就需要创建一个新线程来处理,下次新任务来了肯定又要创建新线程处理的,而创建新线程的开销是很大的,并且请求过多时,肯定会产生大量的线程出来,这样会严重影响系统的性能
  • 在线程池中,咱们的线程叫工作线程(WorkThread),需要处理的任务会排队进入任务队列(WorkQueue)依次被工作线程处理,这个任务必须要实现Runnable接口或者callable接口。
  • 线程池创建后会一直存活,除非手动关闭,核心线程只要被创建,就会一直存在。

2.线程池的创建

  • JDK5.0起提供了代表线程池的接口:ExecutorService;常用的实现类是ThreadPoolExecutor。

  • 对于核心线程数量如何选择

    • 计算密集型的任务:核心线程数量 = CPU的的核数(电脑逻辑处理器个数) + 1;

    • IO密集型的任务: 核心线程数量 = CPU核数 * 2;

  • 如何得到线程池对象

    • 方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象。

      //创建线程池
      ExecutorService pool = new ThreadPoolExecutor(//核心线程数3,最大线程数5,临时线程剔除8秒剔除3, 5, 8, TimeUnit.SECONDS,//任务队列采用数组的阻塞队列,大小为4(也可以创建链表的阻塞队列,那样任务可以无限存放);使用默认的线程工厂new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),//任务拒绝策略是:任务队列满了,无法处理该任务时,抛出异常new ThreadPoolExecutor.AbortPolicy());
      
    • 方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象。

      //创建线程池
      ExecutorService pool = Executors.newFixedThreadPool(3);
      
  • 构造器

    ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 
    
    • 参数一:corePoolSize:指定线程池的核心线程的数量
    • 参数二:maximumPoolSize:指定线程池的最大线程数量
    • 参数三:keepAliveTime:指定临时线程的存活时间
    • 参数四:unit:指定临时线程存活的时间单位(秒、分、时、天)
    • 参数五:workQueue:指定线程池的任务队列
    • 参数六:threadFactory:指定线程池的线程工厂
    • 参数七:handler:指定线程池的任务拒绝策略(线程都很忙,任务队列也满了的时候,新任务来了该怎么处理)
  • 线程池的注意事项

    • 线程池中临时线程的创建时间:新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。
    • 线程池中拒绝新任务的时间:核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务。

3.处理Runnable任务

1.ExecutorService的常用方法

//1.执行Runnable任务
void execute(Runnable command)
//2.执行Callable任务,返回未来任务对象,用于获取线程返回的结果
Future<T> submit(Callable<T> task)
//3.等全部任务执行完毕后,再关闭线程池
void shutdown()
// 4.立即关闭线程池,停止正在执行的任务,并返回队列中未执行的任务
List<Runnable> shutdownNow()

2.任务拒绝策略

//1. 丢弃任务时并抛出RejectedExecution异常。是默认的策略
ThreadPoolExecutor.AbortPolicy
//2. 丢弃任务,但是不抛出异常这是不推荐的做法
ThreadPoolExecutor.DiscardPolicy
//3. 抛弃队列中等待最久的任务,然后把当前任务加入队列中
ThreadPoolExecutor.DiscardOldestPolicy
//4. 由主线程负责调用任务的run()方法从而绕过线程池的直接执行
ThreadPoolExecutor.CallerRunsPolicy

3.案例:

Runnable类

public class MyRunnable implements Runnable{@Overridepublic void run() {System.out.println("打印线程名称:"+Thread.currentThread().getName());
//        try {
//            Thread.sleep(Integer.MAX_VALUE);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }}
}

主线程类

//创建线程池
public class ThreadTest {public static void main(String[] args) {//创建线程池ExecutorService pool = new ThreadPoolExecutor(//核心线程数3,最大线程数5,临时线程剔除8秒剔除3, 5, 8, TimeUnit.SECONDS,//任务队列采用数组的阻塞队列,大小为4(也可以创建链表的阻塞队列,那样任务可以无限存放);使用默认的线程工厂new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),//任务拒绝策略是:任务队列满了,无法处理该任务时,抛出异常new ThreadPoolExecutor.AbortPolicy());MyRunnable runnable = new MyRunnable();//线程池启动后,会一直存活,除非手动关闭//创建3个核心线程处理任务pool.execute(runnable);pool.execute(runnable);pool.execute(runnable);//这4个任务加入任务队列等待pool.execute(runnable);pool.execute(runnable);pool.execute(runnable);pool.execute(runnable);//创建两个临时线程pool.execute(runnable);pool.execute(runnable);//这个时候根据任务拒绝策略来处理任务,这里是抛出异常
//        pool.execute(runnable);//        pool.shutdown();//等线程执行完再关闭线程池List<Runnable> list = pool.shutdownNow();//立即关闭线程池list.stream().forEach(System.out::println);}
}

4.处理Callable任务

callable类

//实现Callable接口,指定返回值类型
public class MyCallable implements Callable<String> {private int n;public MyCallable(int n) {this.n = n;}//重写call方法,描述线程任务@Overridepublic String call() throws Exception {int sum = 0;for (int i = 1; i <= n; i++) {sum+=i;}return Thread.currentThread().getName()+"求出1-"+n+"的累加和结果为:"+sum;}
}

主线程类

//线程池处理Callable任务
public class ThreadTest2 {public static void main(String[] args) throws ExecutionException, InterruptedException {//创建线程池ExecutorService pool = new ThreadPoolExecutor(//核心线程数3,最大线程数5,临时线程剔除8秒剔除3, 5, 8, TimeUnit.SECONDS,//任务队列采用数组的阻塞队列,大小为4(也可以创建链表的阻塞队列,那样任务可以无限存放);使用默认的线程工厂new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),//任务拒绝策略是:任务队列满了,无法处理该任务时,抛出异常new ThreadPoolExecutor.AbortPolicy());//线程池处理Callable任务返回未来任务对象Future<String> future1 = pool.submit(new MyCallable(100));Future<String> future2 = pool.submit(new MyCallable(200));Future<String> future3 = pool.submit(new MyCallable(300));Future<String> future4 = pool.submit(new MyCallable(400));//这个地方会复用线程//调用未来任务对象获取线程返回结果System.out.println(future1.get());System.out.println(future2.get());System.out.println(future3.get());System.out.println(future4.get());}
}

5.使用Executors得到线程池

  • Executors是一个线程池的工具类,提供了很多静态方法用于返回不同特点的线程池对象。
  • 这些方法的底层,都是通过线程池的实现类ThreadPoolExecutor创建的线程池对象。
//1.创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它。
public static ExecutorService newFixedThreadPool(int nThreads)
//2.创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新的线程。
public static ExecutorService newSingleThreadExecutor()
//3.线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了60s则会被回收掉
public static ExecutorService newCachedThreadPool()
//4.创建一个线程池,可以实现再给定的延迟后运行任务,或者定期执行任务。
public static ScheduleExecutorService newScheduleThreadPool(int corePoolSize)

案例:

//创建线程池
ExecutorService pool = Executors.newFixedThreadPool(3);

注意:

  • 大型并发系统环境中使用Executors如果不注意可能会出现系统风险,出现OOM内存溢出(因为newFixedThreadPool和newSingleThreadExecutor的最大任务数默认是Integer.MAX_VALUE,newCachedThreadPool的最大线程数也是Integer.MAX_VALUE,容易内存溢出)。

八、并发、并行

  • 正在运行的程序(软件)就是一个独立的进程
  • 线程是属于进程的,一个进程中可以同时运行很多个线程。
  • 进程中的多个线程其实是并发和并行执行的。
  • 进程中的线程是由CPU负责调度执行的,但CPU能同时处理线程的数量有限,为了保证全部线程都能往前执行,CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发
  • 在同一时刻上,同时有多个线程在被CPU调度执行,这就是并行
  • 多线程到底是怎样执行的:并发和并行同时执行的。

九、线程的生命周期

  • 线程的生命周期从生到死的过程中,经历的各种状态及状态转换。
  • java总共定义了6种状态,6种状态都定义在Thread的内部枚举类中。
线程状态说明
NEW(新建)线程刚被创建,但并未启动
Runnable(可运行)线程已经调用了start(),等待CPU调度
Blocked(锁阻塞)线程在执行的时候未竞争到锁对象,则该线程进入Blocked状态。
Waiting(无限等待)一个线程进入Waiting状态,另一个线程调用notify或者notifyAll方法才能够唤醒。
Time Waiting(计时等待)同waiting状态,有几个方法(sleep,wait)有超时参数,调用它们将进入Timed Waiting状态。
Teminated(被终止)因为run方法正常退出而死亡,或者因为没有捕获的异常终止run方法而死亡。

在这里插入图片描述

十、乐观锁、悲观锁

  • 悲观锁:一上来就加锁,没有安全感,每次只能一个线程进入访问完毕后,再解锁。线程安全,性能较差。
  • 乐观锁:一开始不上锁,认为是没有问题的,大家一起跑,等到要出现线程安全问题的时候才开始控制。线程安全,性能较好。
  • CAS算法(乐观锁的原理):Compare and set,比较并且修改。
  • 内置乐观锁是由原子类实现的,比如整数修改的乐观锁,我们就用AtomicInteger作为整数变量类型,然后使用incrementAndGet()方法自增并返回自增后的值。

Runnable类

//任务类
public class MyRunnable implements Runnable{//原子类整数类型实现了乐观锁private AtomicInteger count = new AtomicInteger();//普通int类型
//    private int count;//重写run方法,描述线程任务@Overridepublic void run() {for (int i = 1; i <= 100; i++) {System.out.println(Thread.currentThread().getName()+"增加后的数字:"+count.incrementAndGet());
//            System.out.println(Thread.currentThread().getName()+"增加后的数字:"+ (++count));}}
}

主线程类

/*** 原子类内置乐观锁* @author 鹿先生* @date 2023/09/1*/
public class TreadTest {public static void main(String[] args) {//创建任务类对象MyRunnable runnable = new MyRunnable();//调用一百个线程同时自增for (int i = 1; i <= 100; i++) {new Thread(runnable).start();}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/120866.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysql、MHA高可用配置即故障切换

MHA概述 一套优秀的MySQL高可用环境下故障切换和主从复制的软件 MHA的出现就是解决MySQL 单点的问题 MySQL故障过程中&#xff0c;MHA能做到0-30秒内自动完成故障切换 MHA能在故障切换的过程中最大程度上保证数据的一致性以达到真正意义上的高可用 MHA的组成&#xff08;核…

RHCA之路---EX280(9)

RHCA之路—EX280(9) 1. 题目 Scale the application greeter in the project samples to a total of 5 replicas 2. 解题 2.1 切换项目 [rootmaster ex280]# oc project samples Now using project "samples" on server "https://master.lab.example.com&qu…

centos7环境使用yum源安装docker

目录 1.检查内核相关信息 2.完善yum源 3.开始安装docker 4.docker使用前最后的准备 5.最后运行一下hello-world 1.检查内核相关信息 cat /etc/*release*&#xff1a;查看centos版本&#xff0c;docker支持centos7及以上版本。 uname -a&#xff1a;查看linux的指令集&…

Linux gdb单步调试的原理

文章目录 一、demo演示二、原理分析参考资料 一、demo演示 .section .data message:.string "Hello, World!\n" len . - message.section .text .globl _start _start:# 调用 write() 函数输出 "Hello, World!"mov $1, %rax # 系统调用号为 1…

呜呜呼呼无无话

姓名和手机号脱敏 function nameDesen(value) {if (!value) return return value.substring(0, 1) new Array(value.length).join(*) } const bklnameDesen(宝矿力) console.log(bkl) //宝**function telephoneDesen(value) {if (!value) return value value.toString()ret…

低代码平台:IVX 重新定义编程

目录 &#x1f36c;一、写在前面 &#x1f36c;二、低代码平台是什么 &#x1f36c;三、为什么程序员和技术管理者不太可能接受“低代码”平台&#xff1f; &#x1f36d;1、不安全&#xff08;锁定特性&#xff09; &#x1f36d;2、不信任 &#x1f36c;四、IVX低代码平台 &a…

Jenkins 持续集成:Linux 系统 两台机器互相免密登录

背景知识 我们把public key放在远程系统合适的位置&#xff0c;然后从本地开始进行ssh连接。 此时&#xff0c;远程的sshd会产生一个随机数并用我们产生的public key进行加密后发给本地&#xff0c;本地会用private key进行解密并把这个随机数发回给远程系统。 最后&#xf…

猜拳游戏小程序源码 大转盘积分游戏小程序源码 积分游戏小程序源码

简介&#xff1a; 猜拳游戏大转盘积分游戏小程序前端模板源码&#xff0c;一共五个静态页面&#xff0c;首页、任务列表、大转盘和猜拳等五个页面 图片&#xff1a;

泛微OA流程表单中代码块获取URL的参数

获取URL的参数 需要编辑自定义函数 function getUrlParam(key){var url decodeURI(window.location.href);var paramMap {};var paramStr url.split("?")[2];if (paramStr && paramStr ! "") {var paramStrArr paramStr.split("&&qu…

Java on VS Code 8月更新|反编译器用户体验优化、新 Maven 项目工作流、代码高亮稳定性提升

作者&#xff1a;Nick Zhu 排版&#xff1a;Alan Wang 大家好&#xff0c;欢迎来到 Visual Studio Code for Java 的 8 月更新&#xff01;在这篇博客中&#xff0c;我们将为您提供有关反编译器支持的更多改进。此外&#xff0c;我们将展示如何创建没有原型的 Maven 项目以及一…

数据结构前言

一、什么是数据结构&#xff1f; 数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。 上面是百度百科的定义&#xff0c;通俗的来讲数据结构就是数据元素集合与数据元素集合或者数据元素与数据元素之间的组成形式。 举个…

Web安全——信息收集下篇

Web安全 一、网络空间搜索引擎二、扫描敏感目录/文件1、御剑2、7kbstorm3、bbscan4、dirmap5、dirsearch6、gobuster7、网站文件 三、扫描网页备份四、网站头信息收集五、敏感文件搜索1、GitHub搜索2、Google-hacking3、wooyun漏洞库4、网盘搜索5、社工库6、网站注册信息7、js敏…

【C++进阶(四)】STL大法--list深度剖析list迭代器问题探讨

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:C从入门到精通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习C   &#x1f51d;&#x1f51d; 链表list 1. 前言2. list的使用2.1 list的构造函…

通过 Keycloak 结合 OAuth2.0协议进行 Amazon API Gateway 鉴权

1. 简介 本文介绍了如何通过 Keycloak&#xff0c;并结合 Amazon API Gateway 内置的授权功能&#xff0c;完成对 Amazon 资源请求的鉴权过程。API Gateway 帮助开发者安全的的创建、发布、维护并管理 API 的访问。在中国区&#xff0c;由于Cognito 仍未上线&#xff0c;因此使…

Groovy 下载安装

Groovy 简介 在某种程度上&#xff0c;Groovy 可以被视为Java 的一种脚本化改良版,Groovy 也是运行在 JVM 上&#xff0c;它可以很好地与 Java 代码及其相关库进行交互操作。它是一种成熟的面向对象编程语言&#xff0c;既可以面向对象编程&#xff0c;又可以用作纯粹的脚本语…

【python零基础入门学习】python基础篇之文件对象open、模块以及函数的使用(三)

本站以分享各种运维经验和运维所需要的技能为主 《python》&#xff1a;python零基础入门学习 《shell》&#xff1a;shell学习 《terraform》持续更新中&#xff1a;terraform_Aws学习零基础入门到最佳实战 《k8》暂未更新 《docker学习》暂未更新 《ceph学习》ceph日常问题解…

leetcode 922. 按奇偶排序数组 II

2023.9.4 本题较为简单&#xff0c;构造一个和nums相同大小的数组ans&#xff0c;然后遍历判断nums中的元素&#xff0c;若为奇数则放在ans中的奇数索引位置&#xff0c;偶数则放在ans中的偶数索引位置。 代码如下&#xff1a; class Solution { public:vector<int> sor…

智能安全帽~生命体征检测与危险气体检测一体化集成设计还是蓝牙无线外挂式方式好?

生命体征&#xff08;心率、血氧等&#xff09;检测&上报平台&#xff0c;危险气体采集&上报平台&#xff0c;是智能安全帽产品中常见的两种选配件&#xff0c;它们的实现有两种典型的模式&#xff1a; 1&#xff09;将传感器集成到主板上&#xff0c;做成一体化的智能…

三维模型OBJ格式轻量化的数据压缩与性能平衡分析

三维模型OBJ格式轻量化的数据压缩与性能平衡分析 三维模型的OBJ格式轻量化数据压缩在保持性能的同时&#xff0c;可以减小文件大小、提高加载速度和节省存储空间。然而&#xff0c;在进行数据压缩时&#xff0c;需要权衡压缩比率和模型质量之间的关系&#xff0c;并考虑不同应用…

AIGC+思维导图:提升你的学习与工作效率的「神器」

目录 一、产品简介 二、功能介绍 2.1 AI一句话生成思维导图 2.2百万模版免费用 2.3分屏视图&#xff0c;一屏读写 2.4团队空间&#xff0c;多人协作 2.5 云端跨平台化 2.6 免费够用&#xff0c;会员功能更强大 2.7 支持多种格式的导入导出 三、使用教程 3.1 使用AI…