热门标签 | HotTags
当前位置:  开发笔记 > 人工智能 > 正文

JAVA多线程编程实例详解

这篇文章主要介绍了JAVA多线程编程,结合实例形式总结分析了多线程、锁、线程池等相关原理及使用技巧,需要的朋友可以参考下

本文实例讲述了JAVA多线程编程。分享给大家供大家参考,具体如下:

  • 进程是系统进行资源调度和分配的一个独立单位。
  • 进程的特点
    独立性:进程是系统中独立存在的实体,拥有自己的独立资源和私有空间。在没有经过进程本身允许的情况下,不能直接访问其他进程。
    动态性:进程与程序的区别在于,前者是一个正在系统中活动的指令,而后者仅仅是一个静态的指令集合
    并发性:多个进程可以在单个处理器上并发执行,而不受影响。

并发性和并行性的区别:
并行性:在同一时刻,有多条指令在多个处理器上同时执行(多个CPU)
并发性:在同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,使得宏观上具有多个进程同时执行的效果(单核)。

  • 通过继承Thread类来创建并启动多线程
public class Deom_1 extends Thread{

  public void run(){
      super.run();
      System.out.println("MyThread01");
    }


  public static void main(String[] args){
    Deom_1 demo=new Deom_1();
    demo.setName("Demo_1");
    demo.start();
    System.out.println("当前线程的名字:"+Thread.currentThread().getName()+" 运行结束");
  }
}

多次调用start会抛出java.lang.IllegalThreadStateException异常
如果只调用run(),不调用start()方法,就相当于调用了一个普通的函数,实际上还是在同一个线程中运行的run()方法。

  • 多线程编程时不要忘记了Java程序运行时默认的主线程,main方法的方法体就是主线程的线程执行体
  • 使用继承Thread类的方法来创建线程类,多条线程之间无法共享线程的实例变量
  • 实现Runnable接口创建线程类
//通过实现Runnable接口来创建线程类
public class SecondThread implements Runnable
{
  private int i ;
  //run方法同样是线程执行体
  public void run()
  {
    for ( ; i <20 ; i++ )
    {
      //当线程类实现Runnable接口时,
      //如果想获取当前线程,只能用Thread.currentThread()方法。
      System.out.println(Thread.currentThread().getName() + " " + i);
    }
  }

  public static void main(String[] args) 
  {
    for (int i = 0; i <50; i++)
    {
      System.out.println(Thread.currentThread().getName() + " " + i);
      if (i == 20)
      {
        SecondThread st = new SecondThread();
        //通过new Thread(target , name)方法创建新线程
        new Thread(st , "新线程1").start();
        new Thread(st , "新线程2").start();
      }
    }
  }
}
  • 采用实现继承方式的多线程:
    编程相对简单
    多条线程之间无法共享线程类实例变量
    继承Thread类之后就不能继承其他的父类

  • 采用实现接口的方式的多线程:
    线程类只是实现了Runnable接口,还可以继承其他类
    在这种方式下,可以多个线程共享同一个对象,非常适合多个相同线程来处理同一份资源的情况。
    编程稍稍复杂一点

  • 线程的生命周期:新建(NEW)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、死亡(Dead)

  • 当程序使用new关键字创建一个线程后,该线程就处于新建状态;当线程对象调用了start()方法之后,该线程就处于就绪状态;如果处于就绪状态的线程获得了CPU,开始执行run方法的线程执行体,则该线程就处于运行状态;但是它不可能一直处于运行状态,可能会被中断进入阻塞状态;线程运行结束后就会进入到死亡状态。

  • 线程进入阻塞状态的情况:
    线程调用了sleep方法主动放弃所占有的资源
    线程调用了一个阻塞式IO方法,在该方法返回的时候被阻塞
    线程尝试获取一个同步监听器,但是被其他线程占有
    在等待某个通知
    调用线程suspend方法挂起,不推荐使用容易造成死锁。

  • 线程进入就绪状态的情况:
    调用sleep方法的线程经过了指定时间
    线程调用的阻塞式IO方法已经返回
    线程成功获取试图取得同步监听器
    线程在等待某个通知,其他线程发出一个通知
    处于挂起状态的线程调用了resume恢复方法

进入阻塞状态的线程在获得执行机会后重新进入就绪状态,而不是运行状态

  • 线程进入死亡状态的情况:
    run()方法执行完成,线程正常结束
    线程抛出一个未捕获的异常或者错误
    直接调用该线程的stop方法来结束该线程——该方法容易导致死锁,通常不推荐。

当主线程结束的时候,其他线程不受影响。一旦子线程启动它就拥有和主线程一样的地位。

  • 不要试图对一个已经死亡的线程调用start()方法使它重新启动,会抛出IllegalThreadStateException异常。调用线程的isAlive()方法可以测试某条线程是否死亡。

  • JAVA中控制线程的方法:
    join线程
    后台线程
    线程睡眠:sleep
    线程让步:yield
    改变线程优先级

  • join线程
    join():等待被join的线程执行完成
    join(long millis):等待被join的线程时间最长millis毫秒
    join(long millis, int nanos):等待被join的线程的时间最长millis毫秒加上nanos微秒

  • 后台线程:任务是给其他线程提供服务,成为“后台线程”、“守候线程”。如果说有的前台线程死亡,后台线程会自动死亡。

调用Thread类的setDaemon(true)方法可以将指定线程设置为后台线程。该方法一定要在启动线程之前设置,否则会发生异常。同时提供isDaemon()方法判断是否是后台线程。主线程一般默认为前台线程前台线程创建的子线程默认是前台,后台线程创建的子线程默认是后台。

  • 改变线程的优先级
PriorityTest low = new PriorityTest("低级");
low.start();
System.out.println("创建之初的优先级:" + low.getPriority());
//设置该线程为最低优先级
low.setPriority(Thread.MIN_PRIORITY);
PriorityTest high = new PriorityTest("高级");
high.start();
System.out.println("创建之初的优先级:" + high.getPriority());
//设置该线程为最高优先级
high.setPriority(Thread.MAX_PRIORITY);

每个线程的默认优先级都与创建它的父线程具有相同的线程,在默认的情况下,main线程具有普通优先级。

  • 线程让步
    yield()方法是Thread提供的一个静态方法,可以让当前正在执行的线程暂停转入就绪状态。等待下一次的重新调度。
    实际上,当某个线程调用了yield()方法后只有优先级相同或者高于当前线程的其他就绪状态的线程才会获得执行的机会。

  • sleep和yield方法的区别
    1、sleep方法暂停当前线程后,会给其他线程机会执行,不会理会其他线程的优先级。但是yield方法只会给优先级相同或者更高的线程
    2、sleep方法会将线程转入阻塞状态,直到经过阻塞时间才会转入就绪状态。而yield方法直接转入就绪状态
    3、sleep方法会抛出InterruptedException异常,所以调用时需要显示捕获异常,yield方法不会抛出任何异常
    4、sleep方法比yield方法具有更多的移植性,通常不依靠yield方法控制并发线程执行。

  • 如果多个线程共同访问1个对象中的实例变量,则有可能出现”非线程安全”

class SelfPrivateNum {
  private int num = 0;

  public void addI(String username) {
    try {

      if (username.equals("a")) {
        num = 100;
        System.out.println("a set over!");
        Thread.sleep(2000);
      } else {
        num = 200;
        System.out.println("b set over!");
      }
      System.out.println(username + " num=" + num);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }

}

class ThreadAA extends Thread {

  private SelfPrivateNum numRef;

  public ThreadAA(SelfPrivateNum numRef) {
    super();
    this.numRef = numRef;
  }

  @Override
  public void run() {
    super.run();
    numRef.addI("a");
  }

}

class ThreadBB extends Thread {

  private SelfPrivateNum numRef;

  public ThreadBB(SelfPrivateNum numRef) {
    super();
    this.numRef = numRef;
  }

  @Override
  public void run() {
    super.run();
    numRef.addI("b");
  }

}

public class RunUnsafe {

  public static void main(String[] args) {

    SelfPrivateNum numRef = new SelfPrivateNum();

    ThreadAA athread = new ThreadAA(numRef);
    athread.start();

    ThreadBB bthread = new ThreadBB(numRef);
    bthread.start();

  }

}
  • 同步方法:就是使用synchronized关键字来修饰某个方法。当多个线程调用这个方法的时,以排队的方式进行处理。同步方法不具有继承性
class SelfPrivateNum2 {
  private int num = 0;

  public synchronized void addI(String username) {
    try {

      if (username.equals("a")) {
        num = 100;
        System.out.println("a set over!");
        Thread.sleep(2000);
      } else {
        num = 200;
        System.out.println("b set over!");
      }
      System.out.println(username + " num=" + num);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }

}

关键字synchronized取的是锁都是对象锁,而不是代码或者是方法当作锁。当多个线程访问的是同一个对象的同步方法的时候是排队的,而当多个线程访问多个对象的同步方法的时候运行的顺序是异步的。

  • A线程先持有object对象的锁,B线程可以以异步的方式调用object对象总的非synchronizaed类型的方法
  • A线程先持有object对象的锁,B线程如果调用object的synchronizaed类型的方法则需要等待,也就是同步。

  • 脏读

为了避免数据出现交叉的情况,使用synchronized关键字来进行同步。虽然在赋值时进行了同步,但是可能在取值的时候出现脏读(dirtyRead)的现象。发生脏读的情况是在读取实例变量时。出现脏读是应为getValue方法不是同步方法,解决方法可以定义为同步方法。

  • synchronized方法是对当前对象进行加锁,而synchronized代码块是对某一个对象进行加锁。
//线程开始执行同步代码块之前,必须先获得对同步监控器的锁定
synchronized (Obj){
  ………
  //此处的代码就是同步代码块  
}

通常使用可能被并发访问的共享资源充当同步监视器。

  • 对同步监视器释放的情况:
    当前线程的同步方法或代码块执行结束
    当前线程的同步方法或代码块中遇到break、return终止了该代码块、方法的继续执行
    当前线程的同步方法或代码块中出现未处理的Error或Exception
    当前线程的同步方法或代码块中执行了同步监控器对象的wait()方法
  • 以下情况不会释放同步锁:
    当前线程的同步方法或代码块调用 Thread.sleep(),Thread.yield()方法来暂停当前线程执行
    当前线程的同步方法或代码块时,其他线程调用了该线程的suspend方法让该线程挂起

  • LOCK锁

Class A{
   private final ReentrantLock lock= new ReentrantLock ();
   //需要保证线程安全的方法
   public void m(){
    //加锁
    lock.lock();
    try{  
      ……………
    }
    finally{
     lock.unlock();
    }
  }
}   

同步方法的比较
1、同步方法和同步代码块使用与竞争资源相关的、隐式的同步监视器,并且强制要求加锁和释放锁要出现在同一个块结构中,而且当获取多个锁的时候,他们必须按照相反的顺序依次释放。
2、Lock方法不仅可以使用与非结构快中,还可以试图中断锁和再次加锁的功能。被Lock加锁的代码中还可以嵌套调用。
3、资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍。

  • Volatile关键字
    强制性从公共堆栈中(存放实例)中取得修饰的变量的值,而不是从线程的私有数据栈中取得变量的值。不能保证不会出现脏读的情况,需要用关键字synchronized来解决。

  • ThreadLocal类
    ThreadLocal在每个线程中对该变量会创建一个副本,即每个线程内部都会有一个该变量,且在线程内部任何地方都可以使用,线程之间互不影响。ThreadLocal提供资源的共享对象!
    T get():返回此线程局部变量中当前线程副本的值
    void remove():删除此线程局部变量中当前副本的值
    void set(T value):设置此线程局部变量中当前线程副本的值

如果需要进行多个线程之间共享资源,以达到线程之间的通信功能,就使用同步机制;如果仅仅需要隔离多个线程之间的共享冲突,可以使Threadlocal。

  • 死锁:当两个线程相互等待对象释放同步监视器的时候就会发生死锁。

    使用一种称为资源排序的简单技术可以轻易避免死锁。

  • 等待通知机制

方法wait()的作用是使当前执行代码线程进行等待,是Object类的方法,该方法用来将当前线程置于“阻塞队列”中,并在wait()所在的代码处停止执行,直到接到通知被唤醒。

在调用wait()之前,线程必须持有该对象的对象级别锁,只能在同步方法或者是同步块中调用此方法。在执行wait方法后,当前线程释放锁。进入和其他线程竞争重新获得锁。如果调用wait方法没有持有锁,则会抛出异常。

方法notify()用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随便选择一个呈wait状态的线程。

方法nofity()也是在同步方法或者同步块中调用,调用前同样要获得对象的对象级别所,否则抛出异常。在执行notify方法后,当前线程不会马上释放该对象锁,要等到执行notify方法的线程将程序执行完才能会释放锁。

方法notifyAll()方法可以使正在等待队列中等待同一共享资源的”全部”线程从等待状态退出,进入可运行状态。

public class Test3 {
  //main方法中有三个等待线程,一个唤醒线程,一个唤醒线程只能唤醒一个等待线程,程序出现阻塞
  public static void main(String[] args) throws InterruptedException {

    Object lock = new Object();

    ThreadA a = new ThreadA(lock);
    new ThreadA(lock).start();
    new ThreadA(lock).start();
    new ThreadA(lock).start();

    Thread.sleep(1000);

    NotifyThread notifyThread = new NotifyThread(lock);
    notifyThread.start();

  }

}

class ThreadA extends Thread {
  private Object lock;

  public ThreadA(Object lock) {
    super();
    this.lock = lock;
  }

  @Override
  public void run() {
    Service service = new Service();
    service.testMethod(lock);
  }

}

class NotifyThread extends Thread {
  private Object lock;

  public NotifyThread(Object lock) {
    super();
    this.lock = lock;
  }

  @Override
  public void run() {
    synchronized (lock) {
      //notify()只能唤醒一个线程,nofiyAll()唤醒所有的等待线程
      lock.notify();
//     lock.notifyAll();
    }
  }

}

class Service {

  public void testMethod(Object lock) {
    try {
      synchronized (lock) {
        System.out.println("begin wait() ThreadName="
            + Thread.currentThread().getName());
        lock.wait();
        System.out.println(" end wait() ThreadName="
            + Thread.currentThread().getName());
      }
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }

}
  • 使用条件变量协调线程

在没有使用synchronized关键字保证同步,而采用Lock的程序。Java提供了condition类来保持协调。Lock替代synchronized,Condition替代同步监视器功能。
await: 类似于wait。
signal:唤醒在此Lock对象上等待的单个线程,选择是任意的。
signalAll:唤醒在此Lock对象上等待的所有线程,选择是任意的。

public class Account
{
  //显示定义Lock对象
  private final Lock lock = new ReentrantLock();
  //获得指定Lock对象对应的条件变量
  private final Condition cOnd= lock.newCondition(); 

  private String accountNo;
  private double balance;

  //标识账户中是否已经存款的旗标
  private boolean flag = false;

  public Account(){}

  public Account(String accountNo , double balance)
  {
    this.accountNo = accountNo;
    this.balance = balance;
  }

  public void setAccountNo(String accountNo)
  {
    this.accountNo = accountNo;
  }
  public String getAccountNo()
  {
     return this.accountNo;
  }

  public double getBalance()
  {
     return this.balance;
  }
  public void draw(double drawAmount)
  {
    //加锁
    lock.lock();
    try
    {
      //如果账户中还没有存入存款,该线程等待
      while(!flag)
      {
        cond.await();
      }
        //执行取钱操作
        System.out.println(Thread.currentThread().getName() + 
          " 取钱:" + drawAmount);
        balance -= drawAmount;
        System.out.println("账户余额为:" + balance);
        //将标识是否成功存入存款的旗标设为false
        flag = false;
        //唤醒该Lock对象对应的其他线程
        cond.signalAll();
    }
    catch (InterruptedException ex)
    {
      ex.printStackTrace();
    }
    //使用finally块来确保释放锁
    finally
    {
      lock.unlock();
    }
  }
  public void deposit(double depositAmount)
  {
    lock.lock();
    try
    {
      //如果账户中已经存入了存款,该线程等待
      while(flag)
      {
        cond.await();        
      }

        //执行存款操作
        System.out.println(Thread.currentThread().getName() + 
          " 存款:" + depositAmount);
        balance += depositAmount;
        System.out.println("账户余额为:" + balance);
        //将标识是否成功存入存款的旗标设为true
        flag = true;
        //唤醒该Lock对象对应的其他线程
        cond.signalAll();
    }
    catch (InterruptedException ex)
    {
      ex.printStackTrace();
    }
    //使用finally块来确保释放锁
    finally
    {
      lock.unlock();
    }
  }

  public int hashCode()
  {
    return accountNo.hashCode();
  }
  public boolean equals(Object obj)
  {
    if (obj != null && obj.getClass() == Account.class)
    {
      Account target = (Account)obj;
      return target.getAccountNo().equals(accountNo);
    }
    return false;
  }
}

使用notify()/notifyAll()方法进行通知时,被通知的线程却是JVM随机选择的。当notifyAll()通知所有WAITING线程,没有选择权,会出现相当大的效率问题。但是ReentrantLock结合Condition类可以”选择性通知”。Condition可以实现唤醒部分指定线程,这样有助于程序运行的效率。

  • Callable和Future

从JDK1.5之后,Java提供了Callable接口,实际上就是Runnable接口的增强版。提供call方法作为线程执行体,但是功能更加强大。
Callable接口不是Runnable接口,不能直接作为Thread的target,有返回值得call方法也不能直接运行。这里需要一个包装器Future。

import java.util.concurrent.*;

class RtnThread implements Callable
{
  //实现call方法,作为线程执行体
  public Integer call()
  {
    int sum = 0;
    int i=0;
    for ( ; i <10 ; i++ )
    {
      System.out.println(Thread.currentThread().getName()+ " 的循环变量i的值:" + i); 
      sum+=i;
    }
    //call()方法可以有返回值

    return sum;
  }
} 

public class CallableTest
{
  public static void main(String[] args) 
  {
    //创建Callable对象
    RtnThread rt = new RtnThread();
    //使用FutureTask来包装Callable对象
    FutureTask task = new FutureTask(rt);
    for (int i = 0 ; i <10 ; i++)
    {
      System.out.println(Thread.currentThread().getName()
        + " 的循环变量i的值:" + i);
      if (i == 5)
      {
        //实质还是以Callable对象来创建、并启动线程
        new Thread(task , "有返回值的线程").start();
      }
    }
    try
    {
      //获取线程返回值
      System.out.println("子线程的返回值:" + task.get());          
    }
    catch (Exception ex)
    {
      ex.printStackTrace();
    }
  }
}
  • 线程池
import java.util.concurrent.*;

//实现Runnable接口来定义一个简单的
class TestThread implements Runnable
{
  public void run()
  {
    for (int i = 0; i <10 ; i++ )
    {
      System.out.println(Thread.currentThread().getName()
        + "的i值为:" + i);
    }
  }
}

public class ThreadPoolTest
{
  public static void main(String[] args) 
  {
    //创建一个具有固定线程数(6)的线程池
    ExecutorService pool = Executors.newFixedThreadPool(6);
    //向线程池中提交3个线程
    pool.execute(new TestThread());
    Future f1=pool.submit(new TestThread());
    Future f2=pool.submit(new TestThread());

    Future f3=pool.submit(new RtnThread());

    try
    {
      if(f1.get()==null&&f2.get()==null&&f3.get()!=null){
        System.out.println("执行完毕!");
        System.out.println(f3.get());

      }
      //获取线程返回值

    }
    catch (Exception ex)
    {
      ex.printStackTrace();
    }
    //关闭线程池
    pool.shutdown();
  }
}

更多java相关内容感兴趣的读者可查看本站专题:《Java进程与线程操作技巧总结》、《Java数据结构与算法教程》、《Java操作DOM节点技巧总结》、《Java文件与目录操作技巧汇总》和《Java缓存操作技巧汇总》

希望本文所述对大家java程序设计有所帮助。


推荐阅读
  • 生成对抗式网络GAN及其衍生CGAN、DCGAN、WGAN、LSGAN、BEGAN介绍
    一、GAN原理介绍学习GAN的第一篇论文当然由是IanGoodfellow于2014年发表的GenerativeAdversarialNetworks(论文下载链接arxiv:[h ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 无线认证设置故障排除方法及注意事项
    本文介绍了解决无线认证设置故障的方法和注意事项,包括检查无线路由器工作状态、关闭手机休眠状态下的网络设置、重启路由器、更改认证类型、恢复出厂设置和手机网络设置等。通过这些方法,可以解决无线认证设置可能出现的问题,确保无线网络正常连接和上网。同时,还提供了一些注意事项,以便用户在进行无线认证设置时能够正确操作。 ... [详细]
  • 本文详细介绍了相机防抖的设置方法和使用技巧,包括索尼防抖设置、VR和Stabilizer档位的选择、机身菜单设置等。同时解释了相机防抖的原理,包括电子防抖和光学防抖的区别,以及它们对画质细节的影响。此外,还提到了一些运动相机的防抖方法,如大疆的Osmo Action的Rock Steady技术。通过本文,你将更好地理解相机防抖的重要性和使用技巧,提高拍摄体验。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 近年来,大数据成为互联网世界的新宠儿,被列入阿里巴巴、谷歌等公司的战略规划中,也在政府报告中频繁提及。据《大数据人才报告》显示,目前全国大数据人才仅46万,未来3-5年将出现高达150万的人才缺口。根据领英报告,数据剖析人才供应指数最低,且跳槽速度最快。中国商业结合会数据剖析专业委员会统计显示,未来中国基础性数据剖析人才缺口将高达1400万。目前BAT企业中,60%以上的招聘职位都是针对大数据人才的。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • qt学习(六)数据库注册用户的实现方法
    本文介绍了在qt学习中实现数据库注册用户的方法,包括登录按钮按下后出现注册页面、账号可用性判断、密码格式判断、邮箱格式判断等步骤。具体实现过程包括UI设计、数据库的创建和各个模块调用数据内容。 ... [详细]
  • “你永远都不知道明天和‘公司的意外’哪个先来。”疫情期间,这是我们最战战兢兢的心情。但是显然,有些人体会不了。这份行业数据,让笔者“柠檬” ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • 本文介绍了游戏开发中的人工智能技术,包括定性行为和非定性行为的分类。定性行为是指特定且可预测的行为,而非定性行为则具有一定程度的不确定性。其中,追逐算法是定性行为的具体实例。 ... [详细]
  • JavaScript设计模式之策略模式(Strategy Pattern)的优势及应用
    本文介绍了JavaScript设计模式之策略模式(Strategy Pattern)的定义和优势,策略模式可以避免代码中的多重判断条件,体现了开放-封闭原则。同时,策略模式的应用可以使系统的算法重复利用,避免复制粘贴。然而,策略模式也会增加策略类的数量,违反最少知识原则,需要了解各种策略类才能更好地应用于业务中。本文还以员工年终奖的计算为例,说明了策略模式的应用场景和实现方式。 ... [详细]
  • 本文介绍了PhysioNet网站提供的生理信号处理工具箱WFDB Toolbox for Matlab的安装和使用方法。通过下载并添加到Matlab路径中或直接在Matlab中输入相关内容,即可完成安装。该工具箱提供了一系列函数,可以方便地处理生理信号数据。详细的安装和使用方法可以参考本文内容。 ... [详细]
author-avatar
小啊小刺猬0801_302
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有