Java线程(1)
1、创建线程的三种方法①、创建Thread类的子类对象实现run()方法run()是函数式接口Runnable中的抽象方法Thread类实现了Runnable接口调用Thread对象的start()来启动线程线程会执行run()方法public class Test extends Thread { Test() { super(Thread name); } public void run()//run为Thread中的线程方法 { String strThreadName getName(); System.out.println(strThreadName thread execute); } public static void fun1() throws Exception { //定义一个Thread对象只启动一个线程一个Thread对象代表一个线程 Test t new Test(); t.start(); //调用start()启动线程 t.start(); //error再次调用start()会抛出异常一个Thread对象不能启动两个线程 t.join(); } public static void fun2() { //定义两个Thread对象启动两个线程因为是两个Thread对象所以线程函数中访问的是各自的实例变量各线程不能共享自己的实例变量 new Test().start(); new Test().start(); } public static void main(String[] args) throws Exception { fun1(); fun2(); } }public class Test { public static void main(String[] args) throws Exception { Thread t new Thread() { Override public void run() { System.out.println(thread run); } }; t.start(); t.join(); } }②、创建实现Runnable接口的类的对象将该对象赋给Thread调用Thread对象的start()来启动线程线程会执行run()方法如下所示。这种方法使多个线程可以共享同一个线程执行类对象Runnable子类对象适合多线程处理同一资源的情况。示例1class ThreadRunner implements Runnable { public ThreadRunner(String str) { _str str; } private String _str; public void run() //run为Runnable中的线程方法 { System.out.println(_str); } } public class Test { public static void main(String[] args) { ThreadRunner st new ThreadRunner(new String(ThreadRunner Test)); //定义两个Thread对象启动两个线程因为各线程使用的是一个Runnable对象所以可以共享该对象的实例变量 new Thread(st).start(); new Thread(st , 线程名称).start(); } }示例2public class Test { public static void main(String[] args) { new Thread(()-{System.out.println(Thread execute);}).start(); new Thread(()-{System.out.println(hello world);},线程名称).start(); } }可以在线程里直接引用局部对象和成员对象public class Foo { public void func(){ int n 100; new Thread(()-{ System.out.println(n); System.out.println(m_n); }).start(); } public int m_n 99; }③、实现Callable接口使用FutureTask对象来包装该Callable对象以该FutureTask对象作为Thread类的target来创建线程这种方式是以Callable接口的call方法来作为线程的执行体优点是call方法可以有返回值还可以声明抛出异常。类似RunnableCallable只包含一个抽象方法call所以它也是一个函数式接口可以使用Lambda表达式来创建Callable对象。Callable接口的泛型类型应该与call方法返回值类型相同。FutureTask对象之所以可以作为Thread类的target是因为FutureTask类实现了Runnable接口在Runnable的run()中会调用Callable的call()方法。FutureTask还实现了Future接口Future代表线程结束后的返回值。Future接口中包含了一些控制它关联的Callable任务的方法如get获得Callable任务里call方法的返回值、cancel试图取消关联的Callable任务、isCancelled关联的Callable任务在正常完成前是否被取消、isDone关联的Callble任务是否已完成。import java.util.concurrent.*; public class Test { public static void main(String[] args) { // 使用Lambda表达式创建CallableInteger对象 // 使用FutureTask来包装Callable对象 /* CallableInteger call (CallableInteger)() - { int i 0; for ( ; i 100 ; i ) { System.out.println(Thread.currentThread().getName() 的循环变量i的值 i); } // call()方法可以有返回值 return i; }; FutureTaskInteger task new FutureTaskInteger(call); */ FutureTaskInteger task new FutureTaskInteger((CallableInteger)() - { int i 0; for ( ; i 100 ; i ) { System.out.println(Thread.currentThread().getName() 的循环变量i的值 i); } // call()方法可以有返回值 return i; }); //以FutureTask对象作为Thread的target实质还是以Callable对象来创建、并启动线程 new Thread(task , 有返回值的线程).start(); try { // get方法可以获取线程函数返回值该方法可能会抛出异常并且为阻塞等待线程结束 System.out.println(子线程的返回值 task.get()); } catch (Exception ex) { ex.printStackTrace(); } } }2、线程状态和Thread中的方法与C不同Java中主线程结束后其创建的子线程仍然能运行但如果子线程是守护线程的话那么主线程结束后子线程也会结束。JVM启动后不只有一个主线程还会有垃圾收集、内存管理等线程。线程的状态阻塞Blocked调用了sleep()、wait()等阻塞方法、调用suspend()。就绪Runnable调用了Thread线程对象的start()方法等待调度器执行该线程sleep()时间到阻塞方法返回调用resume()调用yield()。运行Running被调度器调度得到CPU时间。死亡run()/call()返回、调用stop()、Exception或error。Thread中的方法静态方法currentThread()获得当前Thread线程对象。成员方法getName()/setName()获得/设置线程名默认主线程名为main子线程名为Thread-n。成员方法join()/join(long millis)等待线程结束。成员方法isAlive()线程已死亡的话返回false否则返回true。成员方法setDaemon()设置为后台线程守护线程应该在start()之前调用该方法当前台线程都死亡后后台线程自动死亡。默认从Daemon线程产生的线程也是Daemon线程。静态方法sleep()睡眠执行毫秒或微妙该方法声明抛出异常所以调用它时要么捕获该异常要么显示声明抛出该异常。成员方法interrupt()一个进入Blocked状态的线程可以由另一个线程调用该线程的interrupt()让它离开Blocked状态而且此时线程会抛出InterruptedException异常该异常是受检异常必须对其捕获处理。比如一个进入sleep()的线程其它线程调用该线程的interrupt()可以唤醒它public class Test { public static void main(String[] args) { Thread thread new Thread() { Override public void run() { try { Thread.sleep(99999); }catch(InterruptedException ex){ System.out.println(I woke up); throw new RuntimeException(ex); } } }; thread.start(); thread.interrupt(); } }静态方法yield()让运行状态的线程转入就绪状态使当前线程暂停一下与当前线程优先级相同或更高的其它就绪线程会获得执行机会。setPriority()/getPriority()设置/获得线程的优先级可以有十档优先级默认是5Thread.NORM_PRIORITY推荐使用Thread类的静态常量MAX_PRIORITY(10)、MIN_PRIORITY(1)。线程默认与创建其的父线程优先级相同可以使用优先级高的线程将会得到更多的执行机会。成员方法suspend()挂起线程不推荐使用。成员方法resume()恢复挂起的线程不推荐使用。成员方法stop()结束线程不推荐使用。3、线程同步①、同步代码块——使用synchronized(obj){ ... }同步监视器为一个obj对象线程进入同步代码块之前先获得对于同步监视器的锁定多个线程使用同一个同步监视器同步代码块执行结束或代码块中break、return终止了代码块的执行或出现Exception或error的时候当前线程释放释放同步监视器。需要注意的一点是如果线程执行当中调用了同步监视器obj对象的wait()方法那么也会释放同步监视器。Object的wait()是阻塞方法会导致当前线程暂停另一线程可以调用对象的notify()或notifyAll()来唤醒暂停的线程。如下所示的示例程序会在5秒钟后打印main thread然后才会打印sub threadclass Foo { Integer m_lock 0; public void fun() { Thread t new Thread() { Override public void run() { try { Thread.sleep(1000); }catch(Exception ex) { ex.printStackTrace(); } synchronized(m_lock) { System.out.println(sub thread); } } }; t.start(); synchronized(m_lock) { try { Thread.sleep(5000); }catch(Exception ex) { ex.printStackTrace(); } System.out.println(main thread); } } } public class Test { public static void main(String[] args) { Foo f new Foo(); f.fun(); } }下面为使用synchronized(obj){ ... }同步代码块来实现购票程序class TicketAccount { private int balance 1; //余票 public int getBalance(){return balance;} //查询余票 public void setBalance(int buyCnt) {balance - buyCnt;}//购票 } class BuyTicket implements Runnable { private TicketAccount account; private int buyCnt 1; //购票数 BuyTicket(TicketAccount account) { this.account account; } public void run() { synchronized(account) { if(account.getBalance() buyCnt) //余票足够的话就购票 { account.setBalance(buyCnt); System.out.println(Thread.currentThread().getName() 购票成功余票为 account.getBalance()); } else { System.out.println(Thread.currentThread().getName() 购票失败无票); } } } } public class Test { public static void main(String[] args)throws Exception { BuyTicket ticket new BuyTicket(new TicketAccount()); //开启两个购票的线程 Thread t1 new Thread(ticket, 线程1); t1.start(); Thread t2 new Thread(ticket, 线程2); t2.start(); t1.join(); t2.join(); } }②、同步方法——使用synchronized此时的同步监视器为被synchronized修饰的方法的this实例如下所示的两个方法是相同的效果public synchronized void func() { ...... } public void func() { synchronized(this) { ...... } }下面是使用synchronized声明同步方法实现购票的示例class BuyTicket { private int balance 1; //余票 private int getBalance(){return balance;} //查询余票 private void setBalance(int buyCnt) {balance - buyCnt;}//购票 public synchronized void buyTicket(int buyCnt) { if(getBalance() buyCnt) //余票足够的话就购票 { setBalance(buyCnt); System.out.println(Thread.currentThread().getName() 购票成功余票为 getBalance()); } else { try{ wait(); } catch(Exception e) { } System.out.println(Thread.currentThread().getName() 购票失败无票); } } } class BuyTicketThread implements Runnable { private BuyTicket account; private int buyCnt 1; //购票数 BuyTicketThread(BuyTicket account) { this.account account; } public void run() { account.buyTicket(buyCnt); } } public class Test { public static void main(String[] args)throws Exception { BuyTicketThread ticket new BuyTicketThread(new BuyTicket()); //开启两个购票的线程 Thread t1 new Thread(ticket, 线程1); t1.start(); Thread t2 new Thread(ticket, 线程2); t2.start(); t1.join(); t2.join(); } }③、使用同步锁Lock同步锁、ReadWriteLock读写锁是两个接口ReentrantLock和ReentrantReadWriteLock是它们的实现类并且提供可重入可以对已加锁的Lock再次加锁。Lock中还定义了tryLock()方法获得锁失败会直接返回false而不是阻塞等待。为了避免程序发生异常而无法解锁Lock一定要在finally中调用Lock对象的unlock()方法解锁。import java.util.concurrent.locks.*; class BuyTicket { private int balance 1; //余票 private final ReentrantLock lock new ReentrantLock(); private int getBalance(){return balance;} //查询余票 private void setBalance(int buyCnt) {balance - buyCnt;}//购票 public void buyTicket(int buyCnt) { lock.lock(); try { if(getBalance() buyCnt) //余票足够的话就购票 { setBalance(buyCnt); System.out.println(Thread.currentThread().getName() 购票成功余票为 getBalance()); } else { System.out.println(Thread.currentThread().getName() 购票失败无票); } } finally { lock.unlock(); } } } class BuyTicketThread implements Runnable { private BuyTicket account; private int buyCnt 1; //购票数 BuyTicketThread(BuyTicket account) { this.account account; } public void run() { account.buyTicket(buyCnt); } } public class Test { public static void main(String[] args)throws Exception { BuyTicketThread ticket new BuyTicketThread(new BuyTicket()); //开启两个购票的线程 Thread t1 new Thread(ticket, 线程1); t1.start(); Thread t2 new Thread(ticket, 线程2); t2.start(); t1.join(); t2.join(); } }使用读写锁的好处是当只有读操作的时候所有线程都可以获得锁当写的时候会加锁。读写锁适合有两个或两个以上线程会进行读操作的情况。调用ReentrantReadWriteLock.ReadLock().lock()时若没有任何ReentrantReadWriteLock.().Writelock().lock()也就是没有任何写入获得锁定的时候就可以直接获得锁。调用ReentrantReadWriteLock.().Writelock().lock()时若没有任何ReentrantReadWriteLock.ReadLock().lock()或ReentrantReadWriteLock.().Writelock().lock()也就是没有任何读取或写入获得锁定的时候就可以直接获得锁。读写锁使用示例如下import java.util.concurrent.locks.*; class CFoo { void read() { rwLock.readLock().lock(); try { //ToDo... }finally { rwLock.readLock().unlock(); } } void write() { rwLock.writeLock().lock(); try { //ToDo... }finally { rwLock.writeLock().unlock(); } } ReadWriteLock rwLock new ReentrantReadWriteLock(); }ReadWriteLock在没有任何读取或写入锁定的时候才会获取写入的锁定悲观锁当读取线程很多写入线程很少的情况使用ReadWriteLock进行写入锁定的话可能会使写入线程遭受饥饿问题也就是写入线程迟迟无法竞争到锁因为很多读线程在占用锁而长时间处于等待状态。StampedLock提供了乐观的读取操作乐观锁也就是在读取线程很多写入线程很少的情况下可以乐观的认为读取和写入同时发生的机会很少读取的时候可以先不用锁定在读入数据后再判断数据是否已经遭到写入线程的变更再进行后续的措施重新读取变更的数据或抛出异常。Java 8增加的StampedLock可以在大多数场景中替换掉原来的ReentrantReadWriteLock。以下为使用StampedLock的示例import java.util.concurrent.locks.*; class CFoo { Object get(int index) { long stamp lock.tryOptimisticRead(); //试着乐观的获得读锁定该方法不会真正的获得读锁定不会阻塞调用它来获得一个锁定的标记 Object elem elems[index]; //直接读取数据 //validat()判断锁定的标记是不是已经被其它排他锁获得了是的话返回false表示数据可能已经被改变需要加锁获得数据 //validat()返回true表示上面获取数据过程中没有其它的写入操作可以直接使用读取的数据 if(!lock.validate(stamp)) { stamp lock.readLock(); //真正的获得读锁定 try { elem elems[index]; }finally { lock.unlockRead(stamp); //释放读锁定 } } return elem; } void add(Object elem) { long stamp lock.writeLock(); //获得写入锁定 try { elems.add(elem); }finally { lock.unlock(stamp); //释放写入锁定 } } StampedLock lock new StampedLock(); Object[] elems; }死锁当两个线程互相等待对方释放同步监视器时就会发生死锁。如下程序中子线程调用了b对象的同步方法bar()所以锁定了同步监视器b主线程调用了a对象的同步方法foo()所以锁定了同步监视器a而当子线程的bar()方法中要调用a对象的last()方法时就需要先对a对象加锁而此时a对象正被主线程锁定主线程的foo()方法中也想调用b对象的last()方法这就造成了互相等待的死锁。class A { public synchronized void foo( B b ) { try { Thread.sleep(200); } catch(InterruptedException ex) { ex.printStackTrace(); } b.last(); //企图调用B实例的last()方法 } public synchronized void last() { System.out.println(进入了A类的last()方法内部); } } class B { public synchronized void bar( A a ) { try { Thread.sleep(200); } catch(InterruptedException ex) { ex.printStackTrace(); } a.last(); //企图调用A实例的last()方法 } public synchronized void last() { System.out.println(进入了B类的last()方法内部); } } public class DeadLock implements Runnable { A a new A(); B b new B(); public void init() { a.foo(b); } public void run() { // 调用b对象的bar方法 b.bar(a); } public static void main(String[] args) { DeadLock dl new DeadLock(); new Thread(dl).start(); // 调用a对象的foo方法 dl.init(); } }④、原子类AtomicXXX是原子类比如AtomicInteger、AtomicLong、AtomicReference(对象的引用类型)等它们支持计算的原子操作而不用再加锁如计算可以直接调用原子类的aincrementAndGet()方法而不用加锁。AtomicDouble不属于java标准中类型可以引入google.guava来使用它如下为其依赖groupIdcom.google.guava/groupId artifactIdguava/artifactId version26.0-jre/versionAtomicXXX原子类利用了CAScompareAndSwap技术来实现旋转锁。比如当一个线程对一个数n调用aincrementAndGet()的时候会先获得n的值然后比较n现在在内存中的值是否还等于刚才获得的n的值如果等于的话就1不等于的话循环上面的操作直到等于的情况。比如线程A调用aincrementAndGet()在CAS方法中获得了n的值为100然后线程B执行aincrementAndGet()在CAS中也获得了n的值为100然后A的CAS继续比较n内存中的值为100与刚才获得的值相同所以将n加1,A的CAS执行成功 而线程B的CAS继续执行的时候比较n内存中的值为101与刚才获得的n的值不同所以不执行加1操作CAS返回失败而B会再次进行CAS操作直到成功。注意CAS利用了现代CPU的技术来使比较操作和加1操作是一个不可分割的原子操作。CAS实现的旋转锁的优点无锁编程如果使用lock等锁的话就是在访问共享变量前不管该变量有没有被其它线程使用都先加锁后访问加锁是一个耗费CPU的动作CAS不使用锁但它其实相当于是一个乐观锁如果变量被其它线程改变会直接返回失败而且失败的话会一直尝试CompareAndSwap直到成功否则是是直接对变量进行操作效率高。CAS实现的旋转锁的缺点CAS在意识到变量被其它线程修改后会返回失败然后再次进行比较和赋值的操作直到成功这就是自旋如果长时间不成功的话也会给CPU带来高额开销。4、线程通信①、使用Object的成员方法wait()、notify()、notifyAll()这三个方法必须由synchronized中的同步监视器对象来调用当线程中调用了同步监视器对象的wait()后会释放同步监视器并等待当调用了同步监视器对象的notify()后会从对象等待集合中随机通知一个线程。对于使用synchronized修饰的同步方法因为该类的默认实例this就是同步监视器所以可以在同步方法中直接调用。 对于synchronized(obj)修饰的同步代码块因为同步监视器是一个obj对象所以必须使用该对象来调用这三个方法。class Operation { boolean bFlag false; public synchronized void waitFun() { try { System.out.println(Thread.currentThread().getName() wait...); while(!bFlag) //这里使用while循环而不是if是因为wait()的返回可能是因为线程的私自苏醒 wait(); System.out.println(Thread.currentThread().getName() run!); } catch(InterruptedException ex) { ex.printStackTrace(); } } public synchronized void notifyFun() { System.out.println(Thread.currentThread().getName() notify.); bFlag true; notify(); } } class WaitThreadRun implements Runnable { private Operation op; public WaitThreadRun(Operation op){this.op op;} public void run() { op.waitFun(); } } class NotifyThreadRun implements Runnable { private Operation op; public NotifyThreadRun(Operation op){this.op op;} public void run() { op.notifyFun(); } } public class Test { public static void main(String[] args)throws Exception { Operation op new Operation(); Thread t1 new Thread(new WaitThreadRun(op), 等待线程); t1.start(); Thread.sleep(1000); Thread t2 new Thread(new NotifyThreadRun(op), 通知线程); t2.start(); t1.join(); t2.join(); } }②、使用Condition类的await()、signal()、signalAll()对于使用Lock锁来同步的程序不存在同步监视器可以使用Condition来控制线程通信在这种情况下Lock替代了同步代码块或方法Condition替代了同步监视器的功能。Condition的三个方法必须在获得同步锁Lock后使用线程调用Contidion的await()后会放弃对Lock的锁定。import java.util.concurrent.locks.*; class Operation { bool bStart false; private final ReentrantLock lock new ReentrantLock(); private final Condition cond lock.newCondition(); public void waitFun() { lock.lock(); try { System.out.println(Thread.currentThread().getName() wait...); while(!bStart) cond.await(); System.out.println(Thread.currentThread().getName() run!); } catch(InterruptedException ex) { ex.printStackTrace(); } finally { lock.unlock(); } } public void notifyFun() { lock.lock(); System.out.println(Thread.currentThread().getName() notify.); bStart true; cond.signal(); lock.unlock(); } } class WaitThreadRun implements Runnable { private Operation op; public WaitThreadRun(Operation op){this.op op;} public void run() { op.waitFun(); } } class NotifyThreadRun implements Runnable { private Operation op; public NotifyThreadRun(Operation op){this.op op;} public void run() { op.notifyFun(); } } public class Test { public static void main(String[] args)throws Exception { Operation op new Operation(); Thread t1 new Thread(new WaitThreadRun(op), 等待线程); t1.start(); Thread.sleep(1000); Thread t2 new Thread(new NotifyThreadRun(op), 通知线程); t2.start(); t1.join(); t2.join(); } }下面是使用条件变量实现生产者-消费者模型的例子import java.util.concurrent.locks.*; class Clerk { private int product -1; private Lock lock new ReentrantLock(); private Condition condition lock.newCondition(); public void setProduct(int product) throws InterruptedException //生产者 { lock.lock(); try { waitIfFull(); this.product product; System.out.printf(生产者设定%d%n, this.product); condition.signal(); }finally { lock.unlock(); } } private void waitIfFull() throws InterruptedException { while(this.product ! -1) condition.await(); } public int getProduct() throws InterruptedException //消费者 { lock.lock(); try { waitIfEmpty(); int p this.product; this.product -1; System.out.printf(消费者取走%d%n, p); condition.signal(); return p; }finally{ lock.unlock(); } } private void waitIfEmpty() throws InterruptedException { while(this.product -1) condition.await(); } } class Producer implements Runnable { private Clerk clerk; public Producer(Clerk clerk) { this.clerk clerk; } Override public void run() { System.out.println(生产者开始生产整数...); for(int i 0; i 10; i) { try { clerk.setProduct(i); }catch(InterruptedException ex) { throw new RuntimeException(ex); } } } } class Consumer implements Runnable { private Clerk clerk; public Consumer(Clerk clerk) { this.clerk clerk; } Override public void run() { System.out.println(消费者开始消费整数...); for(int i 0; i 10; i) { try { clerk.getProduct(); }catch(InterruptedException ex) { throw new RuntimeException(ex); } } } } public class Test { public static void main(String[] args) { Clerk clerk new Clerk(); new Thread(new Producer(clerk)).start(); new Thread(new Consumer(clerk)).start(); } }上面的示例可能会存在一个问题那就是在有多个生产者、消费者线程的时候调用notify()时可能通知的是生产者也可能通知的是消费者可以使用多个Condition实例一个给生产者线程用一个给消费者线程用class Clerk { private int product -1; private Lock lock new ReentrantLock(); private Condition producerCond lock.newCondition(); private Condition consumerCond lock.newCondition(); public void setProduct(int product) throws InterruptedException //生产者 { lock.lock(); try { waitIfFull(); this.product product; System.out.printf(生产者设定%d%n, this.product); consumerCond.signal(); }finally { lock.unlock(); } } private void waitIfFull() throws InterruptedException { while(this.product ! -1) producerCond.await(); } public int getProduct() throws InterruptedException //消费者 { lock.lock(); try { waitIfEmpty(); int p this.product; this.product -1; System.out.printf(消费者取走%d%n, p); producerCond.signal(); return p; }finally{ lock.unlock(); } } private void waitIfEmpty() throws InterruptedException { while(this.product -1) consumerCond.await(); } }③、使用阻塞队列BlockingQueueBlockingQueue是Queue容器的子接口当生产者线程调用put()方法向其队尾插入元素的时候如果队列已满则当前线程会阻塞当消费者线程调用take()方法从队列头取出元素的时候如果队列为空的话当前线程会阻塞。Queue中在队列尾插入元素的方法有add队列满会抛出异常、offer队列满会返回false在队列头部删除并返回删除的元素有remove队列空会抛出异常、poll队列空会返回false在队列头部取出但不删除元素有element队列空会抛出异常、peek队列空会返回false。BlockingQueue下有以下5个实现类ArrayBlockingQueue基于数组实现的BlockingQueue。LinkedBlockingQueue基于链表实现的BlockingQueue。PriorityBlockingQueue该队列以元素大小排列最小元素排在队头元素大小判断可以通过元素实现Comparable接口本身大小来自然排序也可以使用一个Comparator比较器来定制排序。SynchronousQueue同步队列对该队列的存、取操作必须交替进行。DelayQueue底层基于PriorityBlockingQueue它要求元素类型实现Delay接口根据接口方法的返回值进行排序。

相关新闻

基于EMR Serverless StarRocks AI Function构建多模态智能运维平台

基于EMR Serverless StarRocks AI Function构建多模态智能运维平台

1. 从“人肉”到“智能”:一个运维团队的效率困局与破局我所在的团队,曾经长期被两类看似简单、实则繁琐到令人头疼的任务所困扰。第一类是工单标注。每天,来自不同业务线的告警、故障报告、用户反馈像雪花一样涌进工单系统。这些工单里&…

2026/8/19 1:00:52 阅读更多 →
没有企业资质也能收款:Go 语言接入个人支付收款 SDK 的完整手记

没有企业资质也能收款:Go 语言接入个人支付收款 SDK 的完整手记

没有企业资质也能收款:Go 语言接入个人支付收款 SDK 的完整手记 【免费下载链接】payjs 个人支付收款解决方案PayJS的Golang版本SDK 项目地址: https://gitcode.com/gh_mirrors/pa/payjs 一个卡在收款上的深夜 独立开发者老周的故事,可能也是很多…

2026/8/19 0:58:52 阅读更多 →
暗黑3鼠标宏工具:解放双手的终极配置指南

暗黑3鼠标宏工具:解放双手的终极配置指南

暗黑3鼠标宏工具:解放双手的终极配置指南 【免费下载链接】D3keyHelper D3KeyHelper是一个有图形界面,可自定义配置的暗黑3鼠标宏工具。 项目地址: https://gitcode.com/gh_mirrors/d3/D3keyHelper 凌晨两点,大秘境还剩三分钟&#xf…

2026/8/19 0:58:52 阅读更多 →

最新新闻

基于STM32与W6100的IPv4/IPv6双栈回环服务器实现

基于STM32与W6100的IPv4/IPv6双栈回环服务器实现

1. 项目概述:当STM32遇上W6100,一个双栈回环服务器的诞生最近在捣鼓一个挺有意思的小项目,用STM32F103VC这颗经典的MCU,搭配WIZnet的W6100以太网芯片,实现了一个同时支持IPv4和IPv6的TCP/UDP回环服务器。说白了&#x…

2026/8/19 1:58:28 阅读更多 →
Transformer统治时代,我为什么还要回头补深度学习基础?

Transformer统治时代,我为什么还要回头补深度学习基础?

Transformer统治时代,我为什么还要回头补深度学习基础? 从大模型调参到基础崩塌:一个工程师的认知重构之路 去年用BERT做文本分类项目的经历,彻底改变了我对AI工程师能力模型的认知。当时我对着Hugging Face文档调参整整两周,测试集准确率始终卡在78%的瓶颈无法突破。这个数字…

2026/8/19 1:58:28 阅读更多 →
基于Edge Impulse与Sixfab CORE的边缘机器学习单元实战部署指南

基于Edge Impulse与Sixfab CORE的边缘机器学习单元实战部署指南

1. 项目缘起:当边缘设备需要一颗“会思考的大脑”最近在折腾一个环境监测的项目,核心需求是在一个部署在野外的设备上,实时识别特定的声音模式,比如特定鸟类的叫声或者设备异常的机械噪音。最直接的方案当然是把所有原始音频数据通…

2026/8/19 1:58:28 阅读更多 →
基于Arduino与WS2812B的火山熔岩夜灯DIY全攻略:从电路设计到光效编程

基于Arduino与WS2812B的火山熔岩夜灯DIY全攻略:从电路设计到光效编程

1. 项目概述:从“火山夜灯”看氛围感营造的底层逻辑最近在整理工作室的旧物时,翻出了一个几年前自己动手做的“火山夜灯”,插上电的瞬间,那种温暖、静谧又带点神秘感的氛围一下子又回来了。这让我想起,无论是居家装饰、…

2026/8/19 1:58:28 阅读更多 →
无风扇嵌入式AI系统构建:Jetson NANO被动散热与YOLO部署实战

无风扇嵌入式AI系统构建:Jetson NANO被动散热与YOLO部署实战

1. 为什么选择无风扇嵌入式系统?在边缘计算和嵌入式AI领域,NVIDIA Jetson NANO以其出色的能效比和强大的AI推理能力,成为了众多开发者和创客的首选平台。然而,当我们谈论“嵌入式系统”时,一个核心的诉求就是可靠性和环…

2026/8/19 1:58:28 阅读更多 →
STM32按键控制LED:从硬件原理到状态机消抖的嵌入式实战

STM32按键控制LED:从硬件原理到状态机消抖的嵌入式实战

1. 项目缘起:从“点亮”到“控制”的思维跃迁很多嵌入式新手,包括当年的我,都是从“点亮一个LED”这个“Hello World”开始的。在TrueStudio(现在已演进为STM32CubeIDE)里,我们通常会用几行代码&#xff0c…

2026/8/19 1:57:28 阅读更多 →

日新闻

【单片机课程设计/毕业设计】基于 STM32 与 WiFi 模块的室内通风智能管控系统设计 基于 STM32 的人体存在感知自适应风扇控制系统设计(018503)

【单片机课程设计/毕业设计】基于 STM32 与 WiFi 模块的室内通风智能管控系统设计 基于 STM32 的人体存在感知自适应风扇控制系统设计(018503)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

2026/8/19 0:00:30 阅读更多 →
AI如何驱动数学猜想生成:从大语言模型到自动化数学发现

AI如何驱动数学猜想生成:从大语言模型到自动化数学发现

1. 项目概述:当AI开始“猜”数学定理 最近在AI研究圈里,一个名为“Moonshine”的项目引起了不小的讨论。这名字本身就挺有意思,直译是“月光”,但在数学史上,它特指一个神秘而美丽的联系——魔群月光猜想,连…

2026/8/19 0:00:30 阅读更多 →
WarcraftHelper 魔兽争霸3优化实战指南

WarcraftHelper 魔兽争霸3优化实战指南

WarcraftHelper 魔兽争霸3优化实战指南 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper 一台刚配的新电脑,跑《魔兽争霸3》却卡成 PPT——这…

2026/8/19 0:02:31 阅读更多 →

周新闻

基于阿里云与通义千问(Qwen)构建AI应用:从模型调用到生产部署的完整实践指南

基于阿里云与通义千问(Qwen)构建AI应用:从模型调用到生产部署的完整实践指南

如果你是一名开发者,最近可能已经感受到了AI大模型正在从“玩具”变成“生产力工具”的强烈信号。从代码补全到智能Agent,从本地部署到云端API,我们正处在一个技术栈快速重构的节点。然而,面对层出不穷的模型、框架和工具&#xf…

2026/8/18 9:15:35 阅读更多 →
工业通信系统底层逻辑:04 反射——高频能量撞墙之后会发生什么?

工业通信系统底层逻辑:04 反射——高频能量撞墙之后会发生什么?

第四篇:反射——高频能量撞墙之后会发生什么? —— 你以为信号已经过去了,其实它正在回来打你 老Q的现场笔记 第五季,我们正式进入工业神经系统层。这里不再是单个设备的战斗,而是整个工厂“经脉”层面的秩序之战。从这一篇开始,你将第一次看清:看似简单的信号传播,背…

2026/8/18 9:06:28 阅读更多 →
【文章复现】非线性值迭代自适应动态规划(ADP):离散时间非线性系统的策略迭代自适应动态规划算法研究附Matlab代码

【文章复现】非线性值迭代自适应动态规划(ADP):离散时间非线性系统的策略迭代自适应动态规划算法研究附Matlab代码

✅作者简介:热爱科研的Matlab仿真开发者,擅长毕业设计辅导、数学建模、数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。🍎 往期回顾关注个人主页:Matlab科研工作室👇 关注我领取海量matlab电子书和…

2026/8/18 9:04:56 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/17 18:54:37 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/17 18:55:16 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/17 18:55:55 阅读更多 →