多线程基础体系知识清单,秒杀面试官系列;

后端面试实战指南:从知识体系构建到AI辅助的系统化准备策略 在软件工程领域,构建扎实的知识体系和解决复杂问题的能力是工程师的核心竞争力。其原理在于将离散的技术点通过场景化思维进行深度关联与整合,形成可应对实际挑战的系统化认知网络。这一过程的技术价值在于能显著提升工程师的设计思维、排查能力和技术前瞻性,使其从被动的知识接收者转变为主动的问题解决者。在应用场景上,尤其在应对高并发系统设计、性能优化、分布式一致性等后端开发核心议题时,这种体系化能力至关重要。本文聚焦于如何高效准备后端面试,通过定义“最小可行知识体系”(MVKS)、将八股文知识点进行场景化升维、并系统化拆解 阅读详情

前言

多线程和并发就好像原子弹,你可以不用,但不能不会。

线程与进程的区别

在计算机发展初期,每台计算机是串行地执行任务的,如果碰上需要IO的地方,还需要等待长时间的用户IO,后来经过一段时间有了批处理计算机,其可以批量串行地处理用户指令,但本质还是串行,还是不能并发执行。

如何解决并发执行的问题呢?于是引入了进程的概念,每个进程独占一份内存空间,进程是内存分配的最小单位,相互间运行互不干扰且可以相互切换,现在我们所看到的多个进程“同时"在运行,实际上是进程高速切换的效果。

那么有了线程之后,我们的计算机系统看似已经很完美了,为什么还要进入线程呢?如果一个进程有多个子任务,往往一个进程需要逐个去执行这些子任务,但往往这些子任务是不相互依赖的,可以并发执行,所以需要CPU进行更细力度的切换。所以就引入了线程的概念,线程隶属于某一个进程,它共享进程的内存资源,相互间切换更快速。

进程与线程的区别:

  1. 进程是资源分配的最小单位,线程是CPU调度的最小单位。所有与进程相关的资源,均被记录在PCB中。
  2. 线程隶属于某一个进程,共享所属进程的资源。线程只由堆栈寄存器、程序计数器和TCB构成。
  3. 进程可以看作独立的应用,线程不能看作独立的应用。
  4. 进程有独立的地址空间,相互不影响,而线程只是进程的不同执行路径,如果线程挂了,进程也就挂了。所以多进程的程序比多线程程序健壮,但是切换消耗资源多。

Java中进程与线程的关系:

  1. 运行一个程序会产生一个进程,进程至少包含一个线程。
  2. 每个进程对应一个JVM实例,多个线程共享JVM中的堆。
  3. Java采用单线程编程模型,程序会自动创建主线程 。
  4. 主线程可以创建子线程,原则上要后于子线程完成执行。

线程的start方法和run方法的区别

区别

Java中创建线程的方式有两种,不管使用继承Thread的方式还是实现Runnable接口的方式,都需要重写run方法。调用start方法会创建一个新的线程并启动,run方法只是启动线程后的回调函数,如果调用run方法,那么执行run方法的线程不会是新创建的线程,而如果使用start方法,那么执行run方法的线程就是我们刚刚启动的那个线程。

程序验证


 
public class Main {     public static void main(String[] args) {         Thread thread = new Thread(new SubThread());         thread.run();         thread.start();     } } class SubThread implements Runnable{     @Override     public void run() {         // TODO Auto-generated method stub         System.out.println("执行本方法的线程:"+Thread.currentThread().getName());     } }

 

 


Thread和Runnable的关系

Thread源码

 

Runnable源码

 

区别

通过上述源码图,不难看出,Thread是一个类,而Runnable是一个接口,Runnable接口中只有一个没有实现的run方法,可以得知,Runnable并不能独立开启一个线程,而是依赖Thread类去创建线程,执行自己的run方法,去执行相应的业务逻辑,才能让这个类具备多线程的特性。

使用继承Thread方式和实现Runable接口方式分别创建子线程

使用继承Thread类方式创建子线程


 
public class Main extends Thread{     public static void main(String[] args) {         Main main = new Main();         main.start();     }     @Override     public void run() {         System.out.println("通过继承Thread接口方式创建子线程成功,当前线程名:"+Thread.currentThread().getName());     } }

运行结果:

 

使用实现Runnable接口方式创建子线程


 
public class Main{     public static void main(String[] args) {         SubThread subThread = new SubThread();         Thread thread = new Thread(subThread);         thread.start();     } } class SubThread implements Runnable{     @Override     public void run() {         // TODO Auto-generated method stub         System.out.println("通过实现Runnable接口创建子线程成功,当前线程名:"+Thread.currentThread().getName());     } }

运行结果:

 

使用匿名内部类方式创建子线程


 
public class Main{     public static void main(String[] args) {         Thread thread = new Thread(new Runnable() {             @Override             public void run() {                 // TODO Auto-generated method stub                 System.out.println("使用匿名内部类方式创建线程成功,当前线程名:"+Thread.currentThread().getName());             }         });         thread.start();     } }

运行结果:

 

关系

  1. Thread是实现了Runnable接口的类,使得run支持多线程。
  2. 因类的单一继承原则,推荐使用Runnable接口,可以使程序更加灵活。

如何实现处理多线程的返回值

通过刚才的学习,我们知道多线程的逻辑需要放到run方法中去执行,而run方法是没有返回值的,那么遇到需要返回值的状况就不好解决,那么如何实现子线程返回值呢?

主线程等待法

通过让主线程等待,直到子线程运行完毕为止。

实现方式:


 
public class Main{     static String str;     public static void main(String[] args) {         Thread thread = new Thread(new Runnable() {             @Override             public void run() {                 str="子线程执行完毕";             }         });         thread.start();         //如果子线程还未对str进行赋值,则一直轮转         while(str==null) {}         System.out.println(str);     } }

使用Thread中的join()方法

join()方法可以阻塞当前线程以等待子线程处理完毕。

实现方式:


 
public class Main{     static String str;     public static void main(String[] args) {         Thread thread = new Thread(new Runnable() {             @Override             public void run() {                 str="子线程执行完毕";             }         });         thread.start();         //如果子线程还未对str进行赋值,则一直轮转         try {             thread.join();         } catch (InterruptedException e) {             // TODO Auto-generated catch block             e.printStackTrace();         }         System.out.println(str);     } }

join方法能做到比主线程等待法更精准的控制,但是join方法的控制粒度并不够细。比如,我需要控制子线程将字符串赋一个特定的值时,再执行主线程,这种操作join方法是没有办法做到的。

通过Callable接口实现:通过FutureTask或者线程池获取

在JDK1.5之前,线程是没有返回值的,通常程序员需要获取子线程返回值颇费周折,现在Java有了自己的返回值线程,即实现了Callable接口的线程,执行了实现Callable接口的线程之后,可以获得一个Future对象,在该对象上调用一个get方法,就可以执行子线程的逻辑并获取返回的Object。

实现方式1(错误示例):


 
public class Main implements Callable<String>{     @Override     public String call() throws Exception {         // TODO Auto-generated method stub         String str = "我是带返回值的子线程";         return str;     }     public static void main(String[] args) {         Main main = new Main();         try {             String str = main.call();             System.out.println(str);         } catch (Exception e) {             // TODO Auto-generated catch block             e.printStackTrace();         }     } }

运行结果:

 

实现方式2(使用FutureTask):


 
public class Main implements Callable<String>{     @Override     public String call() throws Exception {         // TODO Auto-generated method stub         String str = "我是带返回值的子线程";         return str;     }     public static void main(String[] args) {         FutureTask<String> task = new FutureTask<String>(new Main());         new Thread(task).start();         try {             if(!task.isDone()) {                 System.out.println("任务没有执行完成");             }             System.out.println("等待中...");             Thread.sleep(3000);             System.out.println(task.get());         } catch (InterruptedException | ExecutionException e) {             // TODO Auto-generated catch block             e.printStackTrace();         }     } }

运行结果:

 

实现方法3(使用线程池配合Future获取):


 
public class Main implements Callable<String>{     @Override     public String call() throws Exception {         // TODO Auto-generated method stub         String str = "我是带返回值的子线程";         return str;     }     public static void main(String[] args) throws InterruptedException, ExecutionException {         ExecutorService newCacheThreadPool = Executors.newCachedThreadPool();          Future<String> future = newCacheThreadPool.submit(new Main());         if(!future.isDone()) {             System.out.println("线程尚未执行结束");         }         System.out.println("等待中");         Thread.sleep(300);         System.out.println(future.get());         newCacheThreadPool.shutdown();     } }

运行结果:

 


线程的状态

Java线程主要分为以下六个状态:新建态(new),运行态(Runnable),无限期等待(Waiting),限期等待(TimeWaiting),阻塞态(Blocked),结束(Terminated)。

新建(new)

新建态是线程处于已被创建但没有被启动的状态,在该状态下的线程只是被创建出来了,但并没有开始执行其内部逻辑。

运行(Runnable)

运行态分为Ready和Running,当线程调用start方法后,并不会立即执行,而是去争夺CPU,当线程没有开始执行时,其状态就是Ready,而当线程获取CPU时间片后,从Ready态转为Running态。

等待(Waiting)

处于等待状态的线程不会自动苏醒,而只有等待被其它线程唤醒,在等待状态中该线程不会被CPU分配时间,将一直被阻塞。以下操作会造成线程的等待:

  1. 没有设置timeout参数的Object.wait()方法。
  2. 没有设置timeout参数的Thread.join()方法。
  3. LockSupport.park()方法(实际上park方法并不是LockSupport提供的,而是在Unsafe中,LockSupport只是对其做了一层封装,可以看我的另一篇博客《锁》,里面对于ReentrantLock的源码解析有提到这个方法)。

 

锁:https://juejin.im/post/5d8da403f265da5b5d203bf4

限期等待(TimeWaiting)

处于限期等待的线程,CPU同样不会分配时间片,但存在于限期等待的线程无需被其它线程显式唤醒,而是在等待时间结束后,系统自动唤醒。以下操作会造成线程限时等待:

  1. Thread.sleep()方法。
  2. 设置了timeout参数的Object.wait()方法。
  3. 设置了timeout参数的Thread.join()方法。
  4. LockSupport.parkNanos()方法。
  5. LockSupport.parkUntil()方法。

阻塞(Blocked)

当多个线程进入同一块共享区域时,例如Synchronized块、ReentrantLock控制的区域等,会去整夺锁,成功获取锁的线程继续往下执行,而没有获取锁的线程将进入阻塞状态,等待获取锁。

结束(Terminated)

已终止线程的线程状态,线程已结束执行。


Sleep和Wait的区别

Sleep和Wait这两个方法都可以使线程进入限期等待的状态,那么这两个方法有什么区别呢?

  1. sleep方法由Thread提供,而wait方法由Object提供。
  2. sleep方法可以在任何地方使用,而wait方法只能在synchronized块或synchronized方法中使用(因为必须获wait方法会释放锁,只有获取锁了才能释放锁)。
  3. sleep方法只会让出CPU,不会释放锁,而wait方法不仅会让出CPU,还会释放锁。

测试代码:


 
public class Main{     public static void main(String[] args) {         Thread threadA = new Thread(new ThreadA());         Thread threadB = new Thread(new ThreadB());         threadA.setName("threadA");         threadB.setName("threadB");         threadA.start();         threadB.start();     }     public static synchronized void print() {         System.out.println("当前线程:"+Thread.currentThread().getName()+"执行Sleep");         try {             Thread.sleep(1000);         } catch (InterruptedException e) {             // TODO Auto-generated catch block             e.printStackTrace();         }         System.out.println("当前线程:"+Thread.currentThread().getName()+"执行Wait");         try {             Main.class.wait(1000);         } catch (InterruptedException e) {             // TODO Auto-generated catch block             e.printStackTrace();         }         System.out.println("当前线程:"+Thread.currentThread().getName()+"执行完毕");     } } class ThreadA implements Runnable{     @Override     public void run() {         // TODO Auto-generated method stub         Main.print();     } } class ThreadB implements Runnable{     @Override     public void run() {         // TODO Auto-generated method stub         Main.print();     } }

执行结果:

 

从上面的结果可以分析出:当线程A执行sleep后,等待一秒被唤醒后继续持有锁,执行之后的代码,而执行wait之后,立即释放了锁,不仅让出了CPU还让出了锁,而后线程B立即持有锁开始执行,和线程A执行了同样的步骤,当线程B执行wait方法之后,释放锁,然后线程A拿到锁打印了第一个执行完毕,然后线程B打印执行完毕。


notify和notifyAll的区别

notify

notify可以唤醒一个处于等待状态的线程,上代码:


 
public class Main{     public static void main(String[] args) {         Object lock = new Object();         Thread threadA = new Thread(new Runnable() {             @Override             public void run() {                 synchronized (lock) {                     try {                         lock.wait();                     } catch (InterruptedException e) {                         // TODO Auto-generated catch block                         e.printStackTrace();                     }                     print();                 }             }         });         Thread threadB = new Thread(new Runnable() {             @Override             public void run() {                 synchronized (lock) {                     print();                     lock.notify();                 }             }         });         threadA.setName("threadA");         threadB.setName("threadB");         threadA.start();         threadB.start();     }     public static void print() {             System.out.println("当前线程:"+Thread.currentThread().getName()+"执行print");             try {                 Thread.sleep(1000);             } catch (InterruptedException e) {                 // TODO Auto-generated catch block                 e.printStackTrace();             }             System.out.println("当前线程:"+Thread.currentThread().getName()+"执行完毕");     } }

执行结果:

 

代码解释:线程A在开始执行时立即调用wait进入无限等待状态,如果没有别的线程来唤醒它,它将一直等待下去,所以此时B持有锁开始执行,并且在执行完毕时调用了notify方法,该方法可以唤醒wait状态的A线程,于是A线程苏醒,开始执行剩下的代码。

notifyAll

notifyAll可以用于唤醒所有等待的线程,使所有处于等待状态的线程都变为ready状态,去重新争夺锁。


 
public class Main{     public static void main(String[] args) {         Object lock = new Object();         Thread threadA = new Thread(new Runnable() {             @Override             public void run() {                 synchronized (lock) {                     try {                         lock.wait();                     } catch (InterruptedException e) {                         // TODO Auto-generated catch block                         e.printStackTrace();                     }                     print();                 }             }         });         Thread threadB = new Thread(new Runnable() {             @Override             public void run() {                 synchronized (lock) {                     print();                     lock.notifyAll();                 }             }         });         threadA.setName("threadA");         threadB.setName("threadB");         threadA.start();         threadB.start();     }     public static void print() {             System.out.println("当前线程:"+Thread.currentThread().getName()+"执行print");             try {                 Thread.sleep(1000);             } catch (InterruptedException e) {                 // TODO Auto-generated catch block                 e.printStackTrace();             }             System.out.println("当前线程:"+Thread.currentThread().getName()+"执行完毕");     } }

执行结果:

 

要唤醒前一个例子中的线程A,不光notify方法可以做到,调用notifyAll方法同样也可以做到,那么两者有什么区别呢?

区别

要说清楚他们的区别,首先要简单的说一下Java synchronized的一些原理,在openjdk中查看java的源码可以看到,java对象中存在monitor锁,monitor对象中包含锁池和等待池。

锁池,假设有多个对象进入synchronized块争夺锁,而此时已经有一个对象获取到了锁,那么剩余争夺锁的对象将直接进入锁池中。

等待池,假设某个线程调用了对象的wait方法,那么这个线程将直接进入等待池,而等待池中的对象不会去争夺锁,而是等待被唤醒。

下面可以说notify和notifyAll的区别了:

notifyAll会让所有处于等待池中的线程全部进入锁池去争夺锁,而notify只会随机让其中一个线程去争夺锁。


yield方法

概念


 
    /**      * A hint to the scheduler that the current thread is willing to yield      * its current use of a processor. The scheduler is free to ignore this      * hint.      *      * <p> Yield is a heuristic attempt to improve relative progression      * between threads that would otherwise over-utilise a CPU. Its use      * should be combined with detailed profiling and benchmarking to      * ensure that it actually has the desired effect.      *      * <p> It is rarely appropriate to use this method. It may be useful      * for debugging or testing purposes, where it may help to reproduce      * bugs due to race conditions. It may also be useful when designing      * concurrency control constructs such as the ones in the      * {@link java.util.concurrent.locks} package.      */     public static native void yield();

yield源码上有一段长长的注释,其大意是说:当前线程调用yield方法时,会给当前线程调度器一个暗示,当前线程愿意让出CPU的使用,但是它的作用应结合详细的分析和测试来确保已经达到了预期的效果,因为调度器可能会无视这个暗示,使用这个方法是不那么合适的,或许在测试环境中使用它会比较好。

测试:


 
public class Main{     public static void main(String[] args) {         Thread threadA = new Thread(new Runnable() {             @Override             public void run() {                 System.out.println("ThreadA正在执行yield");                 Thread.yield();                 System.out.println("ThreadA执行yield方法完成");             }         });         Thread threadB = new Thread(new Runnable() {             @Override             public void run() {                 System.out.println("ThreadB正在执行yield");                 Thread.yield();                 System.out.println("ThreadB执行yield方法完成");             }         });         threadA.setName("threadA");         threadB.setName("threadB");         threadA.start();         threadB.start();     }

测试结果:

 

 

可以看出,存在不同的测试结果,这里选出两张。

第一种结果:线程A执行完yield方法,让出cpu给线程B执行。然后两个线程继续执行剩下的代码。

第二种结果:线程A执行yield方法,让出cpu给线程B执行,但是线程B执行yield方法后并没有让出cpu,而是继续往下执行,此时就是系统无视了这个暗示。


interrupt方法

中止线程

interrupt函数可以中断一个线程,在interrupt之前,通常使用stop方法来终止一个线程,但是stop方法过于暴力,它的特点是,不论被中断的线程之前处于一个什么样的状态,都无条件中断,这会导致被中断的线程后续的一些清理工作无法顺利完成,引发一些不必要的异常和隐患,还有可能引发数据不同步的问题。

温柔的interrupt方法

interrupt方法的原理与stop方法相比就显得温柔的多,当调用interrupt方法去终止一个线程时,它并不会暴力地强制终止线程,而是通知这个线程应该要被中断了,和yield一样,这也是一种暗示,至于是否应该中断,由被中断的线程自己去决定。当对一个线程调用interrupt方法时:

  1. 如果该线程处于被阻塞状态,则立即退出阻塞状态,抛出InterruptedException异常。
  2. 如果该线程处于running状态,则将该线程的中断标志位设置为true,被设置的线程继续运行,不受影响,当运行结束时由线程决定是否被中断。

线程池

线程池的引入是用来解决在日常开发的多线程开发中,如果开发者需要使用到非常多的线程,那么这些线程在被频繁的创建和销毁时,会对系统造成一定的影响,有可能系统在创建和销毁这些线程所耗费的时间会比完成实际需求的时间还要长。

另外,在线程很多的状况下,对线程的管理就形成了一个很大的问题,开发者通常要将注意力从功能上转移到对杂乱无章的线程进行管理上,这项动作实际上是非常耗费精力的。

利用Executors创建不同的线程池满足不同场景的需求

newFixThreadPool(int nThreads)指定工作线程数量的线程池。

newCachedThreadPool()处理大量中断事件工作任务的线程池,

  1. 试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程。
  2. 如果线程闲置的时间超过阈值,则会被终止并移出缓存。
  3. 系统长时间闲置的时候,不会消耗什么资源。

newSingleThreadExecutor()创建唯一的工作线程来执行任务,如果线程异常结束,会有另一个线程取代它。可保证顺序执行任务。

newSingleThreadScheduledExecutor()与newScheduledThreadPool(int corePoolSize)定时或周期性工作调度,两者的区别在于前者是单一工作线程,后者是多线程

newWorkStealingPool()内部构建ForkJoinPool,利用working-stealing算法,并行的处理任务,不保证处理顺序。

Fork/Join框架:把大任务分割成若干个小任务并行执行,最终汇总每个小任务后得到大任务结果的框架。

为什么要使用线程池

线程是稀缺资源,如果无限制地创建线程,会消耗系统资源,而线程池可以代替开发者管理线程,一个线程在结束运行后,不会销毁线程,而是将线程归还线程池,由线程池再进行管理,这样就可以对线程进行复用。

所以线程池不但可以降低资源的消耗,还可以提高线程的可管理性。

使用线程池启动线程


 
public class Main{     public static void main(String[] args) {         ExecutorService newFixThreadPool = Executors.newFixedThreadPool(10);         newFixThreadPool.execute(new Runnable() {             @Override             public void run() {                 // TODO Auto-generated method stub                 System.out.println("通过线程池启动线程成功");             }         });         newFixThreadPool.shutdown();     } }

新任务execute执行后的判断

要知道这个点首先要先说说ThreadPoolExecutor的构造函数,其中有几个参数:

  1. corePoolSize:核心线程数量。
  2. maximumPoolSize:线程不够用时能创建的最大线程数。
  3. workQueue:等待队列。

那么新任务提交后会执行下列判断:

  1. 如果运行的线程少于corePoolSize,则创建新线程来处理任务,即时线程池中的其它线程是空闲的。
  2. 如果线程池中的数量大于等于corePoolSize且小于maximumPoolSize,则只有当workQueue满时,才创建新的线程去处理任务。
  3. 如果设置的corePoolSize和maximumPoolSize相同,则创建的线程池大小是固定的,如果此时有新任务提交,若workQueue未满,则放入workQueue,等待被处理。
  4. 如果运行的线程数大于等于maximumPoolSize,maximumPoolSize,这时如果workQueue已经满了,则通过handler所指定的策略来处理任务。

handler 线程池饱和策略

  • AbortPolicy:直接抛出异常,默认。
  • CallerRunsPolicy:用调用者所在的线程来执行任务。
  • DiscardOldestPolicy:丢弃队列中靠最前的任务,并执行当前任务。
  • DiscardPolicy:直接丢弃任务
  • 自定义。

线程池的大小如何选定

这个问题并不是什么秘密,在网上各大技术网站均有文章说明,我就拿一个最受认可的写上吧

  • CPU密集型:线程数 = 核心数或者核心数+1
  • IO密集型:线程数 = CPU核数*(1+平均等待时间/平均工作时间)

当然这个也不能完全依赖这个公式,更多的是要依赖平时的经验来操作,这个公式也只是仅供参考而已。

END

转发+关注一波再走呗!

Java后端面试:从八股文到场景题的30天知识体系构建 在软件工程领域,构建高效、可扩展的系统是核心目标。这要求开发者不仅要理解数据结构、并发控制、内存管理等基础概念,更要掌握如何将这些原理应用于解决实际问题。例如,设计一个高并发本地缓存,会串联起集合框架、并发工具、JVM内存模型等多个知识点,这正是工程实践价值的体现。理解B+树索引原理、事务隔离级别与MVCC机制,能有效优化数据库性能和数据一致性。Spring框架的IoC和AOP思想,则通过控制反转和面向切面编程,实现了业务逻辑与横切关注点的解耦,提升了代码的可维护性。本文聚焦于如何将分散的Java后端知识, 阅读详情

相关推荐

面向多目标跟踪的数据关联方法研究综述

摘要:多目标跟踪技术在军事和民用领域具有广泛应用,其核心挑战在于数据关联问题。本文系统梳理了数据关联的主要解决方法,首次将经典算法分为确定性关联和概率性关联两大类。确定性关联(如NNDA、GNN、MHT)采用"硬分配"策略,计算效率高但易受噪声干扰;概率性关联(如PDA、JPDA、PMHT)通过"软分配"处理不确定性,更适合复杂场景。文章详细分析了各类算法的原理、适用条件和优缺点,并探讨了智能学习等新兴技术在视觉跟踪等领域的应用。研究指出,尽管随机集滤波等新方法出现,

qq_43416206的博客 1141

多线程基础体系知识清单

前言 本文会介绍Java中多线程与并发的基础,适合初学者食用。 线程与进程的区别 在计算机发展初期,每台计算机是串行地执行任务的,如果碰上需要IO的地方,还需要等待长时间的用户IO,后来经过一段时间有了批处理计算机,其可以批量串行地处理用户指令,但本质还是串行,还是不能并发执行。 如何解决并发执行的问题呢?于是引入了进程的概念,每个进程独占一份内存空间,进程是内存分配的最小单位,相互间运行...

Java笔记虾 2467

Cesium体渲染实战:从医学CT到气象数据的3D可视化全攻略

本文详细介绍了如何利用Cesium体渲染技术实现医学CT和气象数据的3D可视化。从数据预处理到交互式渲染,涵盖了DICOM和NetCDF格式的处理技巧,以及性能优化的实战策略,帮助开发者高效处理复杂的三维数据集。

weixin_28717431的博客 420

Thread类中的start()和run()方法有什么区别?

通过start()方法来启动一个线程,此时线程处于就绪状态,可以被JVM来调度执行,在调度过程中,JVM通过调用线程类的run()方法来完成实际的业务逻辑,当run()方法结束后,此线程就会终止,所以通过start()方法可以达到多线程的目的。 如果直接调用线程类的run()方法,会被当做一个普通的函数调用,程序中仍然只有主线程这一个线程,即start()方法呢能够异步的调用run()方法,但是直接调用run()方法确实同步的,无法达到多线程的目的。 ...

敢于挑战我可以的博客 3020

ThingsKit物联网平台产品管理之物模型

物模型是ThingsKit物联网平台为产品定义的数据模型,用于描述产品的功能。本文介绍物模型相关概念和使用限制。

云腾五洲官方博客 1174

CD-HIT的使用

继续我的生信笔记系列。从某网站的附带文献中读到该工具,搜索了一下,发现网上的大佬们都在讲如何用它去冗余,相对官方页面的介绍而言很是片面,于是刚好有聚类的需求的我准备对其全体功能做做实验,学习一下由李伟忠博士在伯纳姆研究所(现为桑福德伯纳姆医学研究所)亚当·戈兹克博士的实验室开发的牛工具。 首先是官方介绍: CD-HIT是一个非常广泛使用的程序,用于蛋白质或核苷酸序列的聚类和比较。最初由李伟忠博士在伯纳姆研究所(现为桑福德伯纳姆医学研究所)亚当·戈兹克博士的实验室开发,CD-HIT速度非常快,可以处理非常

一名学术waste的note pro 1万+

多线程的实现基础知识总结

多线程是在同一程序内部并行执行,因此会对相同的内存空间进行并发读写操作。Java中实先多线程的方式主要有如下几种: 1.继承Tread类 2.实现Runable接口 3.实现Callable接口(应用程序可以使用Executor框架来创建线程池)。其中最常见的是继承Tread类和实现Runable接口这两种方式。 这两种方式的话一般选择实现Runable接口,因为实现Runable接口比继承Trea

YEN_优快云的博客 885

手把手教你绕过网易易盾验证码:滑块、点选、语序全攻略

本文深度解析了以网易易盾为代表的现代验证码技术,重点探讨了滑块、点选、语序及空间推理等交互式验证码的工作原理与对抗策略。文章从行为验证和空间推理的核心逻辑出发,剖析了验证码交互的数据流与关键参数,并提供了模拟人类行为轨迹、维护设备指纹等工程实践方法,旨在帮助开发者在合规前提下设计更健壮的自动化流程。

3c4x5z6v7b的博客 705

我凭借这份“2024全网最全Java面试清单”彻底征服阿里面试官

谈到面试,其实说白了就是刷题刷题刷题,天天作死的刷。。。。。为了准备这个“金三银四”的春招,狂刷一个月的题,狂补超多的漏洞知识,像这次美团面试问的算法、数据库、Redis、设计模式等这些题目都是我刷到过的并且我也将自己刷的题全部整理成了PDF或者Word文档(含详细答案解析)66个Java面试知识点架构专题(MySQL,Java,Redis,线程,并发,设计模式,Nginx,Linux,框架,微服务等)+大厂面试题详解(百度,阿里,腾讯,华为,迅雷,网易,中兴,北京中软等)算法刷题(PDF)

2401_84435700的博客 968

Calibre:LVS 快速debug short/open技巧

用Fix Suggestion和Short Isolate两种方法debug LVS问题

m0_61544122的博客 7460

基于深度学习的工业零件缺陷检测系统

工业零件缺陷检测系统摘要 本文介绍了一个基于深度学习的工业零件缺陷检测系统解决方案。该系统采用Faster R-CNN目标检测算法,结合PyTorch框架和OpenCV图像处理库,实现了对工业零件表面缺陷(如裂纹、划痕、凹坑等)的自动检测与定位。系统包含完整的开发流程:从数据采集与标注、数据增强预处理、模型构建与训练,到最终的评估与部署。相比传统机器视觉方法,该深度学习方案具有更强的特征表示能力和环境适应性,能够有效提升工业质检的准确率和效率。文章详细阐述了系统架构设计、实现细节和优化策略,并提供了完整的代

huanghm88的专栏 286

Java面试官更看重基础还是项目经验

说到底,面试官也是普通人,他希望在面试中找到一个未来能并肩作战的人。基础好意味着你理解能力强、沟通成本低;项目经验好意味着你踩过坑、能规避风险。你的学习态度和自驱力。很多面试官会在最后问“你最近在看什么技术书?”或者“你怎样保持技术更新?”如果一个有五年经验的人说“我只看公司文档”,那面试官会认为他的成长已经停滞;而如果他说“我在看《Designing Data-Intensive Applications》”,即便他项目经验普通,面试官也会给出一个“可培养”的评级。

2501_90410679的博客 22

运放比较电路分析以及应用

摘要:本文介绍了基于LM321运放的同相输入型单限电压比较器电路。该电路通过运放开环工作模式实现电平判断:当输入信号Vs+>0V时输出+15V高电平,Vs+<0V时输出≈0V低电平。典型应用包括直流电压过零检测、交流信号整形、电池欠压报警和数字信号噪声过滤。电路具有结构简单(仅需1个运放和1个电阻)、成本低的优势,适用于传感器信号处理、波形转换等场景。使用时需注意参考电平调整、输出电平匹配等问题,如需抗噪声可增加正反馈电阻构成施密特触发器。该电路是运放实现简单电平逻辑的经典方案。

qq_37999850的博客 401

Java面试进阶:构建核心基础、场景实战与趋势融合的三层知识体系

在软件工程领域,构建一个稳固且可扩展的知识体系是开发者职业发展的基石。其核心在于理解计算机科学的基础概念,如数据结构、算法、操作系统原理和网络协议,这些构成了技术能力的根基。深入探究这些原理如何支撑高并发、分布式系统等复杂场景的实现,是提升工程实践价值的关键。例如,掌握JVM内存模型与垃圾回收机制,不仅能优化应用性能,还能有效应对高并发场景下的内存管理挑战;理解分布式事务与一致性协议,则是构建可靠分布式系统的前提。当前,随着AI工程化与云原生技术的快速发展,Java开发者需要将这些趋势与现有知识体系融合,例

weixin_30363981的博客 341

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

Java面试高效准备:体系化知识+场景实战+AI辅助2026冲刺

Java作为企业级应用开发的核心语言,其技术体系涵盖基础语法、并发编程、JVM虚拟机等关键模块。理解Java内存模型、垃圾回收机制等底层原理,能够帮助开发者编写高性能、高可用的应用程序。在微服务架构盛行的当下,掌握Spring框架的IoC/DI、AOP等核心特性,结合MySQL索引优化、事务管理等数据库知识,对构建稳定可靠的分布式系统具有重要价值。本文聚焦Java面试准备,通过建立知识体系、场景化实战演练,并引入大模型工具辅助复习和模拟面试,为2026年Java岗位求职者提供一套高效可落地的学习方案,重点覆

weixin_33850015的博客 377

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

Java面试进阶:从八股文到知识体系,构建问题链思维应对高并发与系统设计

在软件开发领域,构建稳固的知识体系是工程师应对复杂挑战的核心能力。其原理在于将零散的技术概念通过逻辑关联形成网络,从而提升问题分析与解决效率。这种系统化思维的技术价值在于,它能将基础理论转化为工程实践,帮助开发者从被动记忆转向主动应用。在典型的应用场景如高并发系统设计中,面对秒杀超卖、缓存一致性等高频问题,开发者需要串联起并发编程、数据库事务、分布式缓存及系统架构等多层知识。例如,解决超卖问题不仅涉及Java中的synchronized与CAS机制,更需理解Redis分布式锁的实现细节与数据库行锁原理,这正

weixin_33812433的博客 441

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

后端面试核心知识体系与高频题深度解析

在软件工程领域,系统设计与高并发处理是构建稳定、高效后端服务的基石。其核心原理在于通过合理的架构分层、资源管理与数据一致性策略,应对海量请求与复杂业务场景。从技术价值看,掌握分布式系统、数据库优化及缓存机制,能显著提升系统吞吐量、可用性与可维护性。这些能力在电商秒杀、实时通信、大数据处理等应用场景中至关重要。本文聚焦后端面试,深入剖析了以MySQL事务隔离、Redis高可用及Kafka吞吐优化为代表的高频考点,为开发者构建全面的知识图谱与实战应对策略提供清晰指引。

weixin_32342535的博客 304

Java面试突击:构建知识体系与场景题思维,高效备战2025秋招

在Java技术领域,构建扎实的知识体系是应对复杂工程挑战的基础。其核心原理在于将分散的技术点(如并发编程、JVM内存模型、数据库索引)通过逻辑关联形成网状认知结构,而非线性罗列。这种体系化的价值在于能系统性解决高并发、性能调优等实际场景问题,例如在秒杀系统设计中,需综合运用分布式锁、线程池、事务与GC优化等多维度知识。本文聚焦Java核心模块的关联与融合,通过场景题驱动学习,旨在帮助开发者快速建立应对面试与实战的立体化知识图谱,提升技术选型与问题拆解能力。

weixin_30770783的博客 393

Java后端面试高效复习:构建知识体系与实战思维

在Java后端开发领域,构建扎实的知识体系是应对技术面试和解决实际工程问题的基石。其核心原理在于将Java基础、并发编程、JVM、MySQL、Spring等核心技术模块进行有机串联,形成可解释、可推导的逻辑链条,而非孤立记忆知识点。这种体系化思维的技术价值在于,它能帮助开发者从底层原理出发,理解技术选型背后的设计考量,并具备从问题现象推导至解决方案的能力。典型的应用场景包括高并发场景下的系统设计、线上性能问题排查以及复杂业务逻辑的实现。例如,在应对“秒杀系统”这类高频面试题时,就需要综合运用并发控制、数据库

weixin_30372371的博客 302

Java面试体系构建:从基础到实战的技术脉络与排查思路

Java作为企业级应用开发的核心技术栈,其知识体系构建需要深入理解底层原理和工程实践。从JVM内存模型到并发编程机制,Java技术生态通过虚拟机优化、垃圾回收算法和线程安全保证等核心原理,为高并发场景提供稳定支撑。在分布式系统和微服务架构中,Java的技术价值体现在高性能、可维护性和跨平台特性上,广泛应用于电商、金融、大数据等业务场景。针对面试准备,需要掌握HashMap原理、JVM调优、MySQL索引优化等热词相关技术,通过系统化的知识地图和排查思路,构建从基础概念到实战应用的技术应对体系。

weixin_34296641的博客 356
上一篇: 干货来袭!20例JavaWeb项目开发精粹;(内含源码)
下一篇: CSAI首席顾问教你:如何成为更合格的架构师
Java斗帝之路
博客等级 码龄7年 2392粉丝 687原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值