高性能编程-Lock篇(一)

目标

  • 学习模板设计模式
  • 学会使用Lock

模板设计模式

  • 模板方法模式结构通常有两个类:
    AblstractClass(抽象类):在抽象类中定义了一系列的操作PrimitiveOperation,每个操作可以使具体的,也可以是抽象的,每个操作对应一个算法的步骤,在子类中可以重新定义或实现这些步骤。TmplateMethod()这个方法用于定义一个算法结构,模板方法不仅可以调用在抽象类中实现的基本方法,也可以调用在抽象类的子类中实现的基本方法,还可以调用其他对象中的方法。
    ConcreteClass(具体子类):用于实现在父类中声明的抽象基本操作,也可以覆盖在父类中已经实现的具体基本操作。
/**
 * @ClassName TemplateMethod
 * @Description
 * @Author 夕
 * @Date 2019-09-23 19:05
 * @Version V1.0
 **/
public class TemplateMethod {
    public static void main(String[] args) {
        PPT02 ppt02 = new PPT02();
        ppt02.show();
    }
}

//PPT母版
class MotherMash{
    //请填入主题
    void title(){throw  new UnsupportedOperationException();};
    //请给出正文内容
    void content(){throw new UnsupportedOperationException();};
    //请填补页脚
    void end(){throw new UnsupportedOperationException();};

    public final void show(){
        System.out.println("");
        title();
        System.out.println("  (标题=黑体、36号、绿色)");

        System.out.println("###正文开始(华文行楷、28号、黑色)##########\n");
        content();
        System.out.println("\n正文结束 ##############################");

        System.out.println("--------页脚:");
        end();
        System.out.println("--------------------------");
    }
}


class PPT01 extends MotherMash{

    @Override
    void title(){
        System.out.println("Lock接口的使用");
    };

    @Override
    void content(){
        System.out.println("手写ReadWriteLock");
        System.out.println("手写ReentrantLock");
        System.out.println("wait notify");
    };

    @Override
    void end(){
        System.out.println("kbody..........");
    }
}

class PPT02 extends MotherMash{
    @Override
    void title(){
        System.out.println("年中工作总结");
    };

    @Override
    void content(){
        System.out.println("1.完成的项目XXX");
        System.out.println("2.学习到的知识XXX");
        System.out.println("3.自我总结XXX");
    };

    @Override
    void end(){
        System.out.println("结束语....");
    }

}
  • 适用场景:
    一次性实现一个算法的不变部分,并将可变的行为留给子类来实现。
    各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。
    需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。

Lock

简介

锁是用于通过多个线程控制对共享资源的访问的工具。通常,锁提供对共享资源的独占访问:一次只能有一个线程可以获取锁,并且对共享资源的所有访问都要求首先获取锁。 但是,一些锁可能允许并发访问共享资源,如ReadWriteLock的读写锁。

在Lock接口出现之前,Java程序是靠synchronized关键字实现锁功能的。JDK1.5之后并发包中新增了Lock接口以及相关实现类来实现锁功能。

特性和常见方法

方法名称描述
boolean tryLock()只有在调用时才可以获得锁。如果可用,则获取锁定,并立即返回值为true;如果锁不可用,则此方法将立即返回值为false
void lock()获得锁。如果锁不可用,则当前线程将被禁用以进行线程调度,并处于休眠状态,直到获取锁。不死不休
boolean tryLock(long time, TimeUnit unit)超时获取锁,当前线程在一下三种情况下会返回: 1. 当前线程在超时时间内获得了锁;2.当前线程在超时时间内被中断;3.超时时间结束,返回false.
void unlock()释放锁。
Condition newCondition()获取等待通知组件,该组件和当前的锁绑定,当前线程只有获得了锁,才能调用该组件的wait()方法,而调用后,当前线程将释放锁。

Lock接口的实现类:

ReentrantLock , ReentrantReadWriteLock.ReadLock , ReentrantReadWriteLock.WriteLock

手写实现Lock接口


import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;

/**
 * @ClassName XiReentratLock
 * @Description
 * @Author 夕
 * @Date 2019-09-30 14:24
 * @Version V1.0
 **/
public class XiReentratLock implements Lock {

    //标记重入次数的count的值
    AtomicInteger count = new AtomicInteger(0);

    //锁的拥有者
    AtomicReference<Thread> owner = new AtomicReference<>();

    //等到队列
    private LinkedBlockingQueue<Thread> waiters = new LinkedBlockingQueue<>();

    @Override
    public boolean tryLock() {
        //判断count是否为0,若count!=0,说明锁被占用
        int ct = count.get();
        if(ct !=0 ){
            //判断锁是否被当前线程占用,若被当前线程占用,做重入操作,count+=1
            if(owner.get() == Thread.currentThread()){
                count.set(ct + 1);
                return true;
            }else {
                //如不是当前线程占用,互斥,抢锁失败,返回false
                return false;
            }
        }else{
            //若count=0,说明锁未被占用,通过CAS(0,1)来抢锁
            if(count.compareAndSet(ct,ct+1)){
                //若抢锁成功, 设置owner为当前线程的引用
                owner.set(Thread.currentThread());
                return true;
            }else {
                //CAS操作失败,说明抢锁失败,返回false
                return false;
            }
        }
    }

    @Override
    public void lock() {
        //尝试抢锁
        if(!tryLock()){
            //如果失败,进入等待队列
            waiters.offer(Thread.currentThread());
            //自选
            for(;;){
                //判断是否是队列头部,如果是
                Thread head = waiters.peek();
                if(head == Thread.currentThread()){
                    //再次抢锁
                    if(!tryLock()){
                        //若抢锁失败,挂起线程,继续等待
                        LockSupport.park();
                    }else{
                        //若成功,就出队列
                        waiters.poll();
                        return;
                    }
                }else {
                    //若果不是队列头部,就挂起线程
                    LockSupport.park();
                }
            }
        }
    }

    @Override
    public void unlock() {
        if(tryUnlock()){
            Thread th = waiters.peek();
            if(th != null){
                LockSupport.unpark(th);
            }
        }
    }

    //尝试释放锁
    public boolean tryUnlock(){
        //判断,是否是当前线程占有锁,若不是,抛异常
        if(owner.get() != Thread.currentThread()){
            throw new IllegalMonitorStateException();
        }else{
            //如果是,就讲count-1,若count变为0,则解锁成功
            int ct = count.get();
            int nextc = ct -1 ;
            count.set(nextc);

            //判断count值是否为0,若为0,则进行cas操作,将owner设置为null
            if(nextc == 0){
                owner.compareAndSet(Thread.currentThread(),null);
                return true;
            }else {
                return false;
            }
        }
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public Condition newCondition() {
        return null;
    }
}

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;

/**
 * @ClassName XiReentarntReadWriteLock
 * @Description
 * @Author 夕
 * @Date 2019-09-30 15:01
 * @Version V1.0
 **/
public class XiReadWriteLock {
    public static void main(String[] args) {
        Map<String,Object> map  = new HashMap<>();
        map.put("1",2);
    }

    volatile AtomicInteger readCount = new AtomicInteger(0);

    AtomicInteger writeCount = new AtomicInteger(0);

    //独占锁 拥有者
    AtomicReference<Thread> owner = new AtomicReference<>();

    //等待队列
    public volatile LinkedBlockingQueue<WaitNode> waiters = new LinkedBlockingQueue<WaitNode>();

    class WaitNode{
        int type=0;//0为想获取独占锁的线程,1为想获取共享锁的线程
        Thread thread = null;
        int arg = 0;

        public WaitNode(Thread thread,int type,int arg){
            this.thread = thread;
            this.type = type;
            this.arg = arg;
        }
    }

    //尝试获取独占锁
    public boolean tryLock(int acquires){
        //如果read count !=0 返回false
        if(readCount.get() != 0){
            return false;
        }

        int wct = writeCount.get();//拿到 独占锁 当前状态

        if(wct ==0){
            if(writeCount.compareAndSet(wct,wct + acquires)){
                owner.set(Thread.currentThread());
                return true;
            }
        }else if(owner.get() == Thread.currentThread()){
            writeCount.set(wct+ acquires);//修改count值
        }
        return false;
    }

    //获取独占锁
    public void lock(){
        int arg = 1;
        //尝试获取独占锁,若成功,退出方法
        if(!tryLock(arg)){
            //标记为独占锁
            WaitNode waitNode = new WaitNode(Thread.currentThread(),0,arg);
            waiters.offer(waitNode);//进入等待队列
            //循环尝试拿锁
            for(;;){
                WaitNode head = waiters.peek();
                if(head!=null && head.thread == Thread.currentThread()){
                    if(!tryLock(arg)){
                        LockSupport.park();
                    }else{
                        waiters.poll();
                        return;
                    }
                }else {
                    LockSupport.park();
                }

            }
        }
    }

    public boolean unlock(){
        int arg = 1;
        //尝试释放独占锁 若失败返回true,若失败...
        if(tryUnlock(arg)){
            WaitNode next = waiters.peek(); //取出队列头部的元素
            if (next !=null){
                Thread th = next.thread;
                LockSupport.unpark(th);     //唤醒队列头部的线程
            }
            return true;                //返回true
        }
        return false;
    }

    public boolean tryUnlock(int releases){
        //若当前线程没有 持有独占锁
        if(owner.get()!= Thread.currentThread()){
            throw new IllegalMonitorStateException();       //抛IllegalMonitorStateException
        }

        int wc= writeCount.get();
        int nextc = wc - releases;      //计算 独占锁剩余占用
        writeCount.set(nextc);      //不管是否完全释放,都更新count值

        if (nextc==0){  //是否完全释放
            owner.compareAndSet(Thread.currentThread(), null);
            return true;
        }else{
            return false;
        }
    }


    //获取共享锁
    public void lockShared() {
        int arg = 1;

        if (tryLockShared(arg) < 0){    //如果tryAcquireShare失败
            //将当前进程放入队列
            WaitNode node = new WaitNode(Thread.currentThread(), 1, arg);
            waiters.offer(node);  //加入队列

            for (;;){
                //若队列头部的元素是当前线程
                WaitNode head = waiters.peek();
                if (head!=null && head.thread == Thread.currentThread()){
                    if (tryLockShared(arg) >=0){    //尝试获取共享锁,  若成功
                        waiters.poll();      //将当前线程从队列中移除

                        WaitNode next = waiters.peek();
                        if (next!=null && next.type==1){    //如果下一个线程也是等待共享锁
                            LockSupport.unpark(next.thread);    //将其唤醒
                        }
                        return;     //退出方法
                    }else{                      //若尝试失败
                        LockSupport.park();     //挂起线程
                    }
                }else{  //若不是头部元素
                    LockSupport.park();
                }

            }
        }
    }

    //解锁共享锁
    public boolean unLockShared() {
        int arg = 1;

        if (tryUnLockShared(arg)){     //当read count变为0,才叫release share成功
            WaitNode next = waiters.peek();
            if (next!=null){
                LockSupport.unpark(next.thread);
            }
            return true;
        }
        return false;
    }

    //尝试获取共享锁
    public int tryLockShared(int acquires) {
        for (;;){
            if (writeCount.get()!=0 &&
                    owner.get() != Thread.currentThread())
                return -1;

            int rct = readCount.get();
            if (readCount.compareAndSet(rct, rct + acquires)){
                return 1;
            }
        }
    }

    //尝试解锁共享锁
    public boolean tryUnLockShared(int releases) {
        for(;;){
            int rc = readCount.get();
            int nextc = rc - releases;
            if (readCount.compareAndSet(rc, nextc)){
                return nextc==0;
            }
        }
    }
}

Lock锁的简单使用


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

/**
 * @ClassName Demo1_GetLock
 * @Description
 * @Author 夕
 * @Date 2019-09-19 00:04
 * @Version V1.0
 **/
public class Demo1_GetLock {
    //非公平锁
    static Lock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        //主线程 拿到锁
        lock.lock();

        Thread th = new Thread(new Runnable() {
            @Override
            public void run() {
                //不死不休,直到获取到锁
                /*System.out.println("begain to get lock...");
                lock.lock();
                System.out.println("intrrupted");*/

                //浅尝辄止
               /* boolean result = lock.tryLock();
                System.out.println("是否获取到锁:"+result);*/
                //子线程 获取锁 点到为止
                /*try {
                    boolean result = lock.tryLock(5, TimeUnit.SECONDS);
                    System.out.println("是否获得锁:"+result);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }*/


                try {
                    System.out.println("start to get lock Interruptibly");
                    lock.lockInterruptibly();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println("dad asked me to stop ...");
                }

            }
        });

        th.start();
        th.interrupt();
        Thread.sleep(10000L);
        lock.unlock();

    }

}

这里目前先简单介绍Lock锁的基本使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值