JUC并发编程(JUC核心类、TimeUnit类、原子操作类、CASAQS)附带相关面试题

目录

1.JUC并发编程的核心类

2.TimeUnit(时间单元)

3.原子操作类

4.CAS 、AQS机制


1.JUC并发编程的核心类

虽然java中的多线程有效的提升了程序的效率,但是也引发了一系列可能发生的问题,比如死锁,公平性、资源管理以及如何面对线程安全性带来的诸多危害。为此,java就提供了一个专门的并发编程包java.util.concurrent(简称JUC)。此包能够有效的减少了竞争条件和死锁问题。

以下介绍JUC包中核心的类

类名描述
ExecutorExecutor 是一个接口,定义了一种执行任务的方式,其目的是将任务的提交与任务的执行解耦。
ExecutorServiceExecutorServiceExecutor 的子接口,提供了更丰富的功能,例如线程池管理和任务提交等。
ScheduledExecutorServiceScheduledExecutorServiceExecutorService 的子接口,可以按照计划(时间或延迟)来执行任务。
CompletionServiceCompletionService 是一个用于异步执行任务并获取已完成任务结果的框架。
CallableCallable 是一个代表可以返回结果或抛出异常的任务的接口。它类似于 Runnable 接口,但具有返回值。
FutureFuture 是一个可用于获取异步计算结果的接口。
ReentrantLockReentrantLock 是一个可重入锁,它提供了更灵活的同步控制和更高级别的功能。
BlockingQueueBlockingQueue 是一个支持阻塞操作的队列,提供了线程安全的生产者-消费者模式的实现。
CountDownLatchCountDownLatch 是一个同步辅助类,允许一个或多个线程等待其他线程完成操作后再继续执行。
CyclicBarrierCyclicBarrier 是一个同步辅助类,使得一组线程能够互相等待,直到所有线程都达到某个公共屏障点。


2.TimeUnit(时间单元)

这个类能够非常好的让我们实现各种时间之间的转换。TimeUnit类的是枚举类,里面有DAYS(天),HOURS(小时),MINUTES(分钟),SECONDS(秒),MILLISECONDS(毫秒),NANNOSECONDS(纳秒)

TimeUnit类中常用的方法:

方法签名描述
public long convert(long sourceDuration, long srcDuration)该方法用于将给定的时间源持续时间转换为目标持续时间。
public void sleep(long timeout) throws InterruptedException该方法使当前线程进入休眠状态,暂停执行一段指定的时间(以毫秒为单位)。如果在休眠期间中断了线程,则会抛出 InterruptedException 异常。

具体应用案例:

1.时间转换与输出一个月后的日期

package Example2101;

import java.util.Date;
import java.util.concurrent.TimeUnit;

public class javaDemo {
    public static void main(String[] args) {
//        五个小时时间
        long hours = 5;
//        通过SECONDS类将5个小时转为秒
        long seconds = TimeUnit.SECONDS.convert(hours,TimeUnit.HOURS);
        System.out.println(seconds);

//        获取当前时间
        long now = System.currentTimeMillis();
        long furture = now + TimeUnit.MILLISECONDS.convert(30,TimeUnit.DAYS);
        System.out.println("Now Time is"+new Date(now));
        Date futureDay = new Date(furture);
        System.out.println("after mounth time is"+futureDay);

    }
}

 

案例2:定义一个闹钟,这个闹钟在5天后会自动发送消息

这种闹钟形式可以通过线程的睡眠机制进行完成,但是一般情况下如果使用线程的睡眠Thread.sleep()里面放的是毫秒,如果要睡眠五天,那么需要设置的数值会非常非常大的,所以可以使用TimeUnit类的睡眠方法实现自定义睡眠。

package Example2102;

import java.util.concurrent.TimeUnit;

public class javaDemo {
    public static void main(String[] args) {
        new Thread(()->{
            try {
//                通过TimeUnit下的Days类的sleep函数定义五天时间
                TimeUnit.DAYS.sleep(5);
                System.out.println("闹钟响了!!!!!!");
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        },"闹钟").start();
    }
}

3.原子操作类

问题引出:一般情况下如果多线程进行竞争一个变量时候会引发数据错乱的问题。比如多线程下售票员售票案例,由于多个线程竞争,一张票可能已经被卖出去了,但是其他的售票员并不知道,继续售卖同一张票。在之前的时候我们通过了Sychronized()同步位解决了这个问题。但是用这个方法也有不小的弊端,那就是程序效率会大大下降。为此JUC提供了一个新的方式解决这个问题,那就是原子操作类。

首先理解原子性,原子是不可分割的最小物体,在编程中是指一种操作要么做了,要么不做。不可以中断的一种操作。原子操作类具有更高效率,更安全,更简单用法

原子操作类分为很多类,大致分为4类:

基本类型:AtomicInteger 、AtomicLong、AtomicBoolean

数组类型:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray

引用类型:AtomicReference、AtomicStampedReference、AtomicMarkableReference;

对象属性修改类型:

AtomicIntegerFieldUpdater;AtomicLongFiledUpdater;AtomicReferenceFieldUpdater;

 1.基本类型的原子操作类

基本类型:AtomicInteger 、AtomicLong、AtomicBoolean

基本类型之间的操作是差不多的,这里用AtomicLong举例

AtomicLong的常用方法

方法描述
AtomicLong(long initValue)创建一个新的AtomicLong实例,并设置初始值为initValue。
get()获取当前存储在AtomicLong中的值。
set(long newValue)将AtomicLong的值设置为newValue。
getAndIncrement()先获取当前存储在AtomicLong中的值,然后将AtomicLong的值增加1。返回先前的值。
setAndIncrement()将AtomicLong的值增加1。返回增加前的值。
decrementAndGet()将AtomicLong的值减少1,并返回减少后的值。

使用类方法的关键就在于熟悉add(增加) decrement(自减)increment(自增) set(设置值) get(获取类内部的数据) 方法就是这几个操作之间的组合

案例代码:多个售票员售卖100张票

package Example2103;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class javaDemo {
    public static void main(String[] args) {
//        创建原子操作类
        AtomicInteger ticket = new AtomicInteger(6);
        AtomicInteger flag = new AtomicInteger(1);//标志还有票

//        创建三个线程进行售票
        for (int i =0;i<3;i++){
            new Thread(()->{
                while (ticket.get()>0){
                    System.out.println("售票员"+Thread.currentThread().getName()+"售卖第"+ticket.decrementAndGet()+"张票");
                    try {
//                        设置两秒睡眠
                        TimeUnit.SECONDS.sleep(2);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
//                    如果没有票了就将标志位的值设置为0,表示没有票了
                    if (ticket.get() == 0){
                        flag.set(0);
                        System.out.println("卖完了");
                    }
                }
            }).start();
        }
    }
}

 

 可以看到即使没有使用同步机制也实现了同步的效果。

2.数组原子操作类

数组原子操作类有:AtomicArrayInteger AtomicLongArray AtomicReferenceArray(对象数组)

由于三者这件的使用区别不大,所以这里展示AtomicReferenceArray

AtomicReferenceArray常用方法:

方法描述
AtomicReferenceArray(int length)构造一个指定长度的AtomicReferenceArray对象。
AtomicReferenceArray(E[] array)使用给定数组初始化AtomicReferenceArray对象。
int length()返回AtomicReferenceArray的长度(即元素个数)。
boolean compareAndSet(int index, E expect, E update)将指定索引位置的元素与期望值进行比较,如果相等,则将其更新为新的值。该操作是原子性的,返回是否更新成功。
E get(int index)获取指定索引位置的元素的值。
void set(int index, E newValue)设置指定索引位置的元素的值为newValue。
E getAndSet(int index, E newValue)获取指定索引位置的元素的当前值,并将其设置为newValue。

 案例代码:

package Example2104;

import java.util.concurrent.atomic.AtomicReferenceArray;

public class javaDemo {
    public static void main(String[] args) {
        String data[] = new String[]{"王二狗","180","130"};
//        初始化
        AtomicReferenceArray<String> array = new AtomicReferenceArray<String>(data);
//        对象数组的操作
        System.out.println("身高是:"+array.get(1));
        array.set(2,"150");
        System.out.println(array.get(0)+"在拼命锻炼后体重变成:"+array.get(2));
//        筛选如果名字是王二狗的自动改名王二
        array.compareAndSet(0,"王二狗","王二");
        System.out.println("改名后名字叫"+array.get(0));
    }
}

 3.引用原子操作类

引用类型:AtomicReference、AtomicStampedReference、AtomicMarkableReference;

其中AtomicReference是可以直接引用数据类型的原子性操作

下面是AtomicReference的常用方法:

方法描述
AtomicReference()无参构造方法,创建一个初始值为null的AtomicReference对象。
V get()获取当前AtomicReference对象持有的值。
void set(V newValue)设置AtomicReference对象的值为newValue。
boolean compareAndSet(V expect, V update)将AtomicReference对象的值与期望值expect进行比较(==比较),如果相等,则将其更新为新值update。该操作是原子性的,返回是否更新成功。
V getAndSet(V newValue)先获取当前AtomicReference对象的值,然后将其设置为newValue,并返回原来的值。

案例代码:使用AtomicReference进行引用操作

package Example2106;

import java.util.concurrent.atomic.AtomicReference;
// 创建普通人类
class Person{
    private int age;
    private String name;
    private int id;
    Person(int age,String name,int id){
        this.age = age;
        this.name = name;
        this.id = id;
    }
}


public class javaDemo {
    public static void main(String[] args) {
        Person person1 = new Person(18,"张三",001);
        Person person2 = new Person(20,"王思",1002);
//        传入person1对象
        AtomicReference<Person> person = new AtomicReference<Person>(person1);
//        输出对象地址
        System.out.println(person.get());
//        更改引用对象
        person.set(person2);
        System.out.println(person.get());
    }
}

AtomicStampedReference 基于版本号的数据引用。其中版本号是自己定义的int数据类型

下面是AtomicStampedReference的常用方法:

方法描述
AtomicStampedReference(V initRef, int initStamp)构造一个AtomicStampedReference对象,初始引用值为initRef,初始标记值(戳)为initStamp。
V getReference()获取当前AtomicStampedReference对象持有的引用值。
void set(V newRef, int newStamp)设置AtomicStampedReference对象的引用值为newRef,标记值(戳)为newStamp。
boolean compareAndSet(V expectRef, V newRef, int expectStamp, int newStamp)将AtomicStampedReference对象的引用值与期望值expectRef、标记值(戳)与期望值expectStamp进行比较,如果相等,则将其更新为新值newRef和newStamp。该操作是原子性的,返回是否更新成功。
int attemptStamp(V expectedReference, int newStamp)如果当前引用值等于expectedReference,则尝试将标记值(戳)更新为newStamp。如果更新成功,返回新的标记值(戳),否则返回当前标记值。
int getStamp()获取当前AtomicStampedReference对象持有的标记值(戳)。

案例代码:

package Example2107;


import java.util.concurrent.atomic.AtomicStampedReference;

class Draw{
    private String content = "";
    private String autor = "";
    private String title ="";
    Draw(String content,String autor,String title){
        this.content =content;
        this.autor = autor;
        this.title = title;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getContent() {
        return content;
    }
}
public class javaDemo {
    public static void main(String[] args) {
        Draw  draw1= new Draw("","alphaMilk","JUC并发编程原子操作类");
//        初始化内容,版本号为1
        AtomicStampedReference<Draw> atomicDraw = new AtomicStampedReference<Draw>(draw1,1);
        System.out.println(atomicDraw.getReference());
//        更新内容,版本号更改
        draw1.setContent("Hello,word");
        atomicDraw.set(draw1,2);
//        获取当前版本
        System.out.println(atomicDraw.getStamp());
    }
}

AtomicMarkableReference与AtomicStampedReference的区别在于,一个是设置boolean类型的初始化标记,一个多设置的是int类型版本号

下面是AtomicMarkableReference的常用方法:

方法描述
AtomicMarkableReference(V initRef, boolean initMark)构造一个AtomicMarkableReference对象,初始引用值为initRef,初始标记值为initMark。
V getReference()获取当前AtomicMarkableReference对象持有的引用值。
boolean isMarked()判断当前AtomicMarkableReference对象是否被标记。
boolean compareAndSet(V expectRef, V newRef, boolean expectMark, boolean newMark)将AtomicMarkableReference对象的引用值与期望值expectRef、标记值与期望值expectMark进行比较,如果相等,则将其更新为新值newRef和newMark。该操作是原子性的,返回是否更新成功。
void set(V newRef, boolean newMark)设置AtomicMarkableReference对象的引用值为newRef,标记值为newMark。
boolean attemptMark(V expectedReference, boolean newMark)如果当前引用值等于expectedReference,则尝试将标记值更新为newMark。如果更新成功,返回true,否则返回false。

案例代码:

一个班统计同学是否交了班费

package Example2108;

import java.util.concurrent.atomic.AtomicMarkableReference;

class  Student{
    private String name;
    private int id;
    Student(String name,int id){
        this.name = name;
        this.id = id;
    }
}
public class javaDemo {
    public static void main(String[] args) {
        Student stu1 = new Student("王一",001);
        Student stu2 = new Student("张二蛋",002);
//        王一交过班费
        AtomicMarkableReference<Student> atoStu = new AtomicMarkableReference<Student>(stu1,true);
        System.out.println(atoStu.getReference());
        if (atoStu.isMarked()){
            System.out.println("该同学交过班费");
        }else System.out.println("该同学尚未交过班费");
//        张二蛋没有交班费
        atoStu.set(stu2,false);
        System.out.println(atoStu.getReference());
        if (atoStu.isMarked()){
            System.out.println("该同学交过班费");
        }else System.out.println("该同学尚未交过班费");
    }
}

4.对象属性修改原子类

 AtomicIntegerFieldUpdater;AtomicLongFiledUpdater;AtomicReferenceFieldUpdater;

这三个类的实现原理基本差不多,所以将用AtomicIntegerFieldUpdater举例:

以下是AtomicIntegerFieldUpdater类的常用方法:

int addAndGet(T obj, int data)将指定对象obj的字段值与data相加,并返回相加后的结果。
boolean compareAndSet(T obj, int expect, int update)将指定对象obj的字段值与期望值expect进行比较,如果相等,则将其更新为新值update。返回是否更新成功。
int get(T obj)获取指定对象obj的字段值。
int getAndSet(T obj, int newValue)获取指定对象obj的字段值,并将其设置为新值newValue。
int decrementAndGet(T obj)将指定对象obj的字段值减1,并返回减1后的结果。
int incrementAndGet(T obj)将指定对象obj的字段值加1,并返回加1后的结果。

 案例代码:

package Example2109;

import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

class Book{
     volatile long id;
     volatile String name;

    Book(long id, String name){
        this.id = id;
        this.name = name;
    }

}

public class javaDemo {
    public static void main(String[] args) {
        Book book1 = new Book(114514,"Java从入门到入土");
        AtomicReferenceFieldUpdater<Book,String> bookmanger = AtomicReferenceFieldUpdater.newUpdater(Book.class,String.class,"name");
        System.out.println("更新前书本名称为:"+bookmanger.get(book1));
        bookmanger.set(book1,"Java从入门到项目实战");
        System.out.println("更新后书本名称为:"+bookmanger.get(book1));
    }
}

 


4.CAS 、AQS机制

CAS是一条CPU并发原语。它的功能是判断某个内存某个位置的值是否相等,如果是则改为新的值,这个操作过程属于原子性操作。

CAS是乐观锁,是一种冲突重试机制,在并发竞争不是很剧烈的情况下,其操作性能会好于悲观锁机制(Synchronization同步处理)

*面试题为什么说 Synchronized 是一个悲观锁?乐观锁的实现原理又是什么?什么是 CAS,它有什么特性?

  1. Synchronized的并发策略是悲观的,不管是否产生竞争,任何数据的操作都必须加锁。
  2. 乐观锁的核心是CAS,CAS包括内存值、预期值、新值,只有当内存值等于预期值时,才会将内存值修改为新值。

*面试题:乐观锁一定就是好的吗?

  1. 乐观锁认为对一个对象的操作不会引发冲突,所以每次操作都不进行加锁,只是在最后提交更改时验证是否发生冲突,如果冲突则再试一遍,直至成功为止,这个尝试的过程称为自旋。
  2. 乐观锁没有加锁,但乐观锁引入了ABA问题,此时一般采用版本号进行控制;
  3. 也可能产生自旋次数过多问题,此时并不能提高效率,反而不如直接加锁的效率高;
  4. 只能保证一个对象的原子性,可以封装成对象,再进行CAS操作;

*面试题:volatile 关键字的作用

对于可见性,Java 提供了 volatile 关键字来保证可见性和禁止指令重排。 volatile 提供 happens-before 的保证,确保一个线程的修改能对其他线程是可见的。当一个共享变量被 volatile 修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。

从实践角度而言,volatile 的一个重要作用就是和 CAS 结合,保证了原子性,详细的可以参见 java.util.concurrent.atomic 包下的类,比如 AtomicInteger。

volatile 常用于多线程环境下的单次操作(单次读或者单次写)。
 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数学建模学习(10):遗传算法

遗传算法简介 • 遗传算法&#xff08;Genetic Algorithms&#xff09;是基于生物进化理论的原理发展起来的一种广为 应用的、高效的随机搜索与优化的方法。其主要特点是群体搜索策略和群体中个体之 间的信息交换&#xff0c;搜索不依赖于梯度信息。它是20世纪70年代初期由美国…

手上有大量精准的手机号码,如何把这些精准客户加到微信呢?

手上有大量精准的手机号码&#xff0c;如何把这些精准客户加到微信呢&#xff1f; 微信管理系统&#xff0c;有什么优势呢&#xff1f; ① 可批量导入手机号码、vx号码自动加好友 ② 设置自动加人的间隔时间&#xff0c;有效解决了加好友频繁被限制的问题 ③ 还可以多个微信…

企业服务器被devos勒索病毒攻击后怎么处理,devos勒索病毒如何攻击的

众所周知&#xff0c;科学技术是第一生产力&#xff0c;科学技术的发展给企业与人们的生活带来了极大变化&#xff0c;但随之而来的网络安全威胁也不断增加。最近&#xff0c;我们收到很多企业的求助&#xff0c;企业的计算机服务器遭到了devos勒索病毒的攻击&#xff0c;导致企…

软件测试工程师面试如何描述自动化测试是怎么实现的?

软件测试工程师面试的时候&#xff0c;但凡简历中有透露一点点自己会自动化测试的技能点的描述&#xff0c;都会被面试官问&#xff0c;那你结合你的测试项目说说自动化测试是怎么实现的&#xff1f;一到这里&#xff0c;很多网友&#xff0c;包括我的学生&#xff0c;也都一脸…

一篇文章,教你彻底掌握接口测试!

Part 01、什么是接口测试 所谓接口&#xff0c;是指同一个系统中模块与模块间的数据传递接口、前后端交互、跨系统跨平台跨数据库的对接。而接口测试&#xff0c;则是通过接口的不同情况下的输入&#xff0c;去对比输出&#xff0c;看看是否满足接口规范所规定的功能、安全以及…

从零开始:构建您自己的直播带货软件开发计划

1. 确定目标和需求 在开始开发之前&#xff0c;您需要明确您的目标和需求。考虑以下问题&#xff1a; 您的直播带货软件是面向哪个市场和用户群体&#xff1f; 您的软件需要支持哪些主要功能&#xff0c;如实时视频直播、商品展示、购买支付、实时互动等&#xff1f; 您是否需…

批处理处理退格符,一行里输出百分比

直接上例子程序&#xff1a; echo off&setlocal enabledelayedexpansion for /l %%a in (1,1,6) do set "str!str!" rem set /p0%<nul for /L %%i in (0,1,100) do (sleep 1 >nul set /p%str%<nul&set /p%%i%%<nul ) pause>nul 输出效果如…

通过anvt X6和vue3实现图编辑

通过anvt X6 X6地址&#xff1a;https://x6.antv.antgroup.com/tutorial/about&#xff1b; 由于节点比较复杂&#xff0c;使用vue实现的节点&#xff1b; x6提供了一个独立的包 antv/x6-vue-shape 来使用 Vue 组件渲染节点。 VUE3的案例&#xff1a; <template><div…

JavaWeb 手写Tomcat底层机制

目录 一、Tomcat底层整体架构 1.简介 : 2.分析图 : 3.基于Socket开发服务端的流程 : 4.打通服务器端和客户端的数据通道 : 二、多线程模型的实现 1.思路分析 : 2.处理HTTP请求 : 3.自定义Tomcat : 三、自定义Servlet规范 1. HTTP请求和响应 : 1 CyanServletRequest …

成集云 | 畅捷通采购单同步至钉钉 | 解决方案

源系统成集云目标系统 介绍 畅捷通是一家专业的金融科技公司&#xff0c;致力于为投资者提供便捷、高效的金融服务。通过畅捷通T的交易方式&#xff0c;投资者可以更加灵活地进行买卖交易&#xff0c;并且在交易完成后即可获得结算款项&#xff0c;无需等待T1的结算周期。 钉…

Styletron: 面向组件的样式设计工具包

styletron官网&#xff1a; styletron的GitHub链接&#xff1a; styletron-react 一. 介绍 Styletron是一个通用的component-oriented&#xff08;面向组件的&#xff09;样式工具。它属于css-in-js类别。Styletron可以很好地与React配合使用&#xff0c;但也可以与其他框架或…

Python弹球小游戏

给在校的小妹妹做个游戏玩&#xff1a;. 弹珠游戏主要是靠坐标xy&#xff0c;接板长度&#xff0c;球的半径等决定&#xff1a; # -*- coding: utf-8 -*- # Author : Codeooo # Time : 2022/04/29import sys import time import random import pygame as pgprint("&q…

ML类CFAR检测器在不同环境中检测性能的分析

摘要&#xff1a;该文是楼主翻阅书籍以及一些论文总结出来的关于ML(均值)类CFAR检测器在不同环境中的性能对比&#xff0c;以及优缺点的总结&#xff0c;可以帮助大家面对不同情形如何选择CFAR问题。由于楼主见识短浅&#xff0c;文中难免出现不足之处&#xff0c;望各位指出。…

校对软件助力公安公检:提高调查报告质量

校对软件可以为公安公检机关提供有力支持&#xff0c;帮助提高调查报告的质量。以下是校对软件在这方面的助力&#xff1a; 1.拼写和语法检查&#xff1a;校对软件可以自动检查调查报告中的拼写错误和语法问题。这可以避免由于疏忽或拼写错误而导致的报告不准确或难以理解的情况…

uniapp scroll-view 隐藏滚动条

/*清除滚动条 - 适配安卓*/::-webkit-scrollbar {width: 0;height: 0;color: transparent;}/*清除滚动条 - 适配IOS*/::-webkit-scrollbar {display: none;}

对强缓存和协商缓存的理解

浏览器缓存的定义&#xff1a; 浏览器缓存是浏览器在本地磁盘对用户最近请求过的文档进行存储&#xff0c;当访问者再次访问同一页面时&#xff0c;浏览器就可以直接从本地磁盘加载文档。 浏览器缓存分为强缓存和协商缓存。 浏览器是如何使用缓存的&#xff1a; 浏览器缓存…

oracle的管道函数

Oracle管道函数(Pipelined Table Function)oracle管道函数 1、管道函数即是可以返回行集合&#xff08;可以使嵌套表nested table 或数组 varray&#xff09;的函数&#xff0c;我们可以像查询物理表一样查询它或者将其赋值给集合变量。 2、管道函数为并行执行&#xff0c;在…

传统图像算法 - 运动目标检测之KNN运动背景分割算法

以下代码用OpenCV实现了视频中背景消除和提取的建模&#xff0c;涉及到KNN&#xff08;K近邻算法&#xff09;&#xff0c;整体效果比较好&#xff0c;可以用来进行运动状态分析。 原理如下&#xff1a; 背景建模&#xff1a;在背景分割的开始阶段&#xff0c;建立背景模型。 …

【ChatGPT 指令大全】怎么使用ChatGPT来辅助学习英语

在当今全球化的社会中&#xff0c;英语已成为一门世界性的语言&#xff0c;掌握良好的英语技能对个人和职业发展至关重要。而借助人工智能的力量&#xff0c;ChatGPT为学习者提供了一个有价值的工具&#xff0c;可以在学习过程中提供即时的帮助和反馈。在本文中&#xff0c;我们…

力扣63.不同路径II(动态规划)

/*** author Limg* date 2022/08/09* 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。* 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish”&#xff09;。* 现在考虑网…