Java并发编程:理解线程、同步和锁

news2024/11/26 10:30:24

第1章:引言

Java并发编程是多线程技术的一种实现方式,它在现代软件开发中扮演着至关重要的角色。随着计算机处理器核心数量的增加,以及云计算和大数据技术的普及,能够有效利用并发编程的程序员将能为企业创造更高的效率和价值。此外,良好的并发控制还能显著提高程序的响应速度和吞吐量。

并发编程不仅限于提高执行效率,它还解决了多任务同时执行时的资源共享问题。例如,服务器同时处理成千上万个客户端请求,如果没有有效的并发控制,资源访问的冲突将导致程序失败。Java提供了一套完备的并发编程工具,从基本的线程控制到复杂的锁机制和并发集合,这些工具帮助开发者构建稳健的多线程应用程序。

理解并发的关键在于掌握如何合理地分配和管理资源,以及确保多个线程在访问共享资源时的正确性和高效性。随着技术的不断演进,Java并发编程也在不断地更新和发展,为开发者提供了更多的可能性和挑战。

第2章:Java中的线程基础

线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。在Java中,线程的实现主要通过以下三种方式:

1. 继承Thread类

Java中的线程可以通过继承Thread类来创建。这种方式简单直接,但因为Java不支持多重继承,所以如果一个类已经继承了其他类,则不能再继承Thread

public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Thread running: " + Thread.currentThread().getName());
    }
}

public class Example {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}

在这个例子中,MyThread继承自Thread类,并重写了run()方法。在主方法中通过创建MyThread的实例,并调用start()方法来启动线程。

2. 实现Runnable接口

实现Runnable接口是创建线程的另一种常用方式。这种方法的好处是不会影响类的继承体系,具有更好的灵活性。

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Thread running: " + Thread.currentThread().getName());
    }
}

public class Example {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start();
    }
}

这里,MyRunnable实现了Runnable接口并定义了run()方法。通过将其实例传递给Thread类的构造器,然后启动线程。

3. 使用Callable和Future

Callable接口类似于Runnable,不同之处在于它可以返回一个结果,并且能抛出异常。Callable经常与Future一起使用,以便于处理异步计算的结果。

import java.util.concurrent.*;

public class CallableExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(1);
        Future<Integer> future = executor.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                return 123;
            }
        });
        System.out.println("Future result: " + future.get());
        executor.shutdown();
    }
}

在这个例子中,通过Callable返回整数123,Future.get()

第3章:线程的生命周期

理解线程的生命周期对于精确控制Java中的并发行为至关重要。线程在其生命周期中会经历几种状态,每种状态的管理和转换都需精细操作以保证程序的正确性和效率。

线程状态

Java线程的生命周期包括以下几种状态:

  • 新建(NEW):新创建了一个线程实例,但还没有调用start()方法。
  • 就绪(RUNNABLE):线程已经启动,等待系统分配资源。
  • 运行(RUNNING):线程正在执行run()方法中的代码。
  • 阻塞(BLOCKED):线程被阻止执行,因为它正在等待一个监视器锁(进入一个同步块或方法)。
  • 等待(WAITING):线程通过调用wait()方法,等待其他线程通知(通过notify()notifyAll())来继续执行。
  • 计时等待(TIMED_WAITING):线程在指定的时间内等待另一个线程的通知,例如调用sleep()join(long millis)方法。
  • 终止(TERMINATED):线程的run()方法执行完毕或者因异常终止。
线程状态转换的关键方法

线程状态之间的转换不仅受到程序代码的控制,还受到操作系统调度的影响。下面是一些常用的方法,这些方法会影响线程状态:

  • start():将新建线程的状态从NEW转变到RUNNABLE。
  • run():执行线程要进行的操作。
  • sleep(long millis):使当前正在执行的线程暂停指定的时间(毫秒),不释放锁。
  • wait():使线程进入等待状态直到它被通知或中断。
  • notify()/notifyAll():通知一个或所有等待(在此对象的监视器上)的线程继续执行。
  • join():在一个线程中调用另一个线程的join()方法,会将调用线程置于阻塞状态,直到被join()的线程结束运行。
示例:线程的状态转换
public class ThreadStateExample implements Runnable {
    public static Thread thread1;

    public static void main(String[] args) throws InterruptedException {
        thread1 = new Thread(new ThreadStateExample());
        // 打印新建线程的状态
        System.out.println("State of thread1 after creation - " + thread1.getState());
        thread1.start();
        // 打印启动后的状态
        System.out.println("State of thread1 after calling start() - " + thread1.getState());
    }

    public void run() {
        Thread myThread = new Thread(new MyRunnable());
        // 新建线程
        System.out.println("State of myThread - " + myThread.getState());
        myThread.start();
        // 等待线程完成
        try {
            myThread.join();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            e.printStackTrace();
        }
        // 打印终止后的状态
        System.out.println("State of myThread after join() - " + myThread.getState());
    }

    private static class MyRunnable implements Runnable {
        public void run() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                e.printStackTrace();
            }
        }
    }
}

这段代码展示了线程从新建到运行、等待、再到终止的全过程。thread1myThread的状态在各个时间点被输出,展示了状态的转换。

通过深入了解线程的生命周期及其管理,开发者可以更好地设计和调试多线程应用,确保应用的高性能和稳定性。

第4章:同步基础

在Java并发编程中,同步是一种机制,用于控制多个线程对共享资源的访问,以防止数据的不一致性和腐败。理解并有效地实现同步是每个Java并发程序员必须掌握的技能。

为何需要同步

多线程环境下,当多个线程同时修改同一个资源(如修改同一个变量),而不进行适当的同步,就会引起线程安全问题。这可能导致数据不正确或程序行为异常。同步机制可以确保在任一时刻,只有一个线程可以访问特定的资源。

使用synchronized关键字

synchronized是Java中最基本的同步机制。它可以用于方法或代码块,保证同一时间只有一个线程执行该方法或代码块。

同步一个方法

可以将一个方法声明为synchronized,这样,这个方法在任何时候只能由一个线程执行。

public class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}

在这个示例中,increment方法和getCount方法都被标记为synchronized。这意味着,执行这些方法的线程在其他线程可以调用同一个对象的任何synchronized方法之前,必须获得对象的锁。

同步一个代码块

如果只有方法中的一部分代码访问共享资源,可以通过同步代码块来减小锁的范围,提高效率。

public class BetterCounter {
    private int count = 0;
    
    public void increment() {
        synchronized (this) {
            count++;
        }
    }

    public int getCount() {
        synchronized (this) {
            return count;
        }
    }
}

这里,只有修改count的部分被同步了,从而减少了锁的持有时间。

volatile关键字的作用

volatile关键字在Java中用于确保变量的修改对所有线程立即可见,它可以帮助程序员编写无锁的代码,同时保证线程间的可见性。

public class Flag {
    private volatile boolean flag = true;

    public void toggle() {
        flag = !flag;
    }

    public boolean checkFlag() {
        return flag;
    }
}

在上面的代码中,flag变量被声明为volatile。这保证了当一个线程修改了flag的值时,这个修改对于其他检查flag值的线程是立即可见的。

同步是确保多线程程序正确执行的基石。通过合理使用synchronizedvolatile等同步机制,开发者可以避免多线程环境下的竞态条件和数据不一致等问题,从而编写出更加健壮、可靠的Java应用程序。

第5章:高级锁机制

在Java并发编程中,除了基本的同步机制如synchronizedvolatile关键字之外,Java还提供了更复杂、更灵活的锁机制,这些高级锁能够帮助开发者在更加复杂的场景中有效管理线程间的协作和资源共享。

ReentrantLock的使用及其优势

ReentrantLock是Java提供的一个高级同步机制,相较于synchronized,它提供了更高的灵活性和更丰富的功能。ReentrantLock支持重入性,意味着同一个线程可以多次获得同一把锁。

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockCounter {
    private final ReentrantLock lock = new ReentrantLock();
    private int count = 0;

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }

    public int getCount() {
        return count;
    }
}

在这个例子中,increment方法中使用ReentrantLock确保增加操作的线程安全。使用tryfinally块确保在增加计数后,锁一定会被释放,这是一个良好的锁管理习惯。

ReentrantLock还提供了如尝试锁定(tryLock)、定时锁定以及公平锁(Fairness)等高级功能,这些功能使得ReentrantLock在某些场景下比synchronized更加合适。

ReadWriteLock读写分离锁

当多个线程在进行读操作,而写操作相对较少时,ReadWriteLock可以提升性能。ReadWriteLock维护了一对锁——一个读锁和一个写锁。通过允许多个线程同时读取而不互相阻塞,这种锁机制提高了程序的并发性能。

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class CachedData {
    private Object data;
    private volatile boolean cacheValid;
    private final ReadWriteLock rwl = new ReentrantReadWriteLock();

    public void processCachedData() {
        rwl.readLock().lock();
        if (!cacheValid) {
            // Must release read lock before acquiring write lock
            rwl.readLock().unlock();
            rwl.writeLock().lock();
            try {
                // Recheck state because another thread might have acquired
                // write lock and changed state before we did.
                if (!cacheValid) {
                    data = loadData();
                    cacheValid = true;
                }
                // Downgrade by acquiring read lock before releasing write lock
                rwl.readLock().lock();
            } finally {
                rwl.writeLock().unlock(); // Unlock write, still hold read
            }
        }

        try {
            use(data);
        } finally {
            rwl.readLock().unlock();
        }
    }

    private Object loadData() {
        // Load data from external source
        return new Object();
    }

    private void use(Object data) {
        // Use the data
    }
}

在这个例子中,使用了读写锁来控制对缓存数据的访问,使得多个线程可以同时读取数据,但只有一个线程可以进行写操作,并且写操作时会阻塞读操作。

StampedLock的特点和使用案例

StampedLock是Java 8引入的一种锁机制,它也提供了读写锁的功能,但性能通常比ReentrantReadWriteLock更优,因为它的锁方法返回一个戳记(stamp),用于释放锁或检查锁是否有效。

第6章:线程安全与不安全的实例分析

在Java并发编程中,确保线程安全是一个重要的挑战。线程安全意味着在多线程环境下,程序能够正确地处理多个线程对同一资源的访问和修改,不会导致数据损坏或不一致。本章将通过具体的实例来分析线程安全和线程不安全的情况,并讨论如何实现线程安全。

线程不安全的实例

首先,让我们看一个线程不安全的例子,并分析为什么会出现问题。

public class UnsafeCounter {
    private int count = 0;

    public void increment() {
        count++; // 非线程安全
    }

    public int getCount() {
        return count;
    }
}

在这个例子中,increment方法简单地将count的值增加1。虽然这看起来是一个原子操作,但实际上它不是。该操作可以分解为三个独立的步骤:读取count的值,增加1,然后写回新值。如果多个线程同时执行increment()方法,它们可能读取相同的初始值,导致它们都只增加1,最终结果会小于预期。

线程安全的实现

现在,让我们看看如何使increment操作变得线程安全。

使用synchronized关键字

一个简单的方法是使用synchronized关键字同步方法:

public class SafeCounterWithSync {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}

通过同步increment方法,我们确保任何时候只有一个线程可以执行该方法。这保证了当一个线程在修改count值时,没有其他线程可以访问该方法,从而避免了并发修改的问题。

使用AtomicInteger

另一个实现线程安全的方法是使用Java的Atomic类,如AtomicInteger,这些类利用了底层硬件的原子性操作来保证操作的原子性。

import java.util.concurrent.atomic.AtomicInteger;

public class SafeCounterWithAtomic {
    private AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        count.incrementAndGet();
    }

    public int getCount() {
        return count.get();
    }
}

在这个例子中,incrementAndGet方法自动保证了增加操作的原子性。这种方法不需要synchronized关键字,通常比同步方法更高效。

分析和对比

通过上述示例,我们可以看到不同方法实现线程安全的策略和效果。使用synchronized是最直接的同步方法,但可能会因为锁的争用而降低性能。而AtomicInteger等原子类提供了一种更细粒度的同步机制,通常在高并发场景下表现更好。

理解这些基本的线程安全模式对于编写健壮的并发程序至关重要。在设计并发程序时,开发者应该根据实际的应用场景选择合适的同步策略,以平衡性能和安全性。

第7章:条件变量和阻塞队列

在Java并发编程中,除了锁和同步方法,条件变量和阻塞队列也是重要的工具,用于在多线程环境中协调线程之间的操作。本章将深入探讨条件变量和阻塞队列的概念、用途和实现方式。

条件变量的使用

条件变量允许线程在某些条件不满足时挂起,直到其他线程改变条件并通知等待线程继续执行。在Java中,条件变量通常与一个锁(如ReentrantLock)关联使用。

使用Condition接口

Condition接口提供了一种方式,允许线程获取一个锁并在某个条件上等待,直到被通知或中断。

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

public class ConditionExample {
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition notFull  = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();
    private int[] items = new int[10];
    private int putptr, takeptr, count;

    public void put(int x) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length)
                notFull.await();
            items[putptr] = x;
            if (++putptr == items.length) putptr = 0;
            ++count;
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public int take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0)
                notEmpty.await();
            int x = items[takeptr];
            if (++takeptr == items.length) takeptr = 0;
            --count;
            notFull.signal();
            return x;
        } finally {
            lock.unlock();
        }
    }
}

在这个例子中,使用了两个条件变量notFullnotEmpty来控制数组的存取操作,确保生产者在数组满时等待,消费者在数组空时等待。

阻塞队列的实现和应用

阻塞队列是支持两个附加操作的队列,这些操作包括在队列为空时队列的获取操作将阻塞,等待队列变为非空,当队列满时插入操作将阻塞,等待队列可用。

使用ArrayBlockingQueue

ArrayBlockingQueue是一个由数组支持的有界阻塞队列,此类队列按 FIFO(先进先出)原则对元素进行排序。

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueExample {
    public static void main(String[] args) {
        BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);

        Thread producer = new Thread(() -> {
            try {
                int value = 0;
                while (true) {
                    queue.put(value++);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        Thread consumer = new Thread(() -> {
            try {
                while (true) {
                    System.out.println(queue.take());
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        producer.start();
        consumer.start();
    }
}

在此示例中,生产者不断地将数字放入队列,而消费者则从队列中取出数字。puttake方法都是阻塞方法,分别在队列满和队列空时阻塞。

通过使用条件变量和阻塞队列,Java开发者可以有效地管理线程间的协作,使得资源访问和任务执行更加高效和安全。这些工具极大地简化了复杂并发程序的开发,是实现生产者-消费者模式等多线程设计模式的关键。

第8章:Java中的原子类

在Java并发编程中,原子类提供了一种在无锁环境下进行线程安全操作的方法。这些类利用底层硬件的能力,以原子方式更新其值,从而避免了同步的开销。本章将探讨几种常用的原子类,并示例它们的应用。

AtomicInteger的使用

AtomicInteger是一种基本的原子类,提供了原子更新整数值的操作。它是实现非阻塞算法的基础,常用于计数器或生成唯一序列号等场景。

示例:使用AtomicInteger
import java.util.concurrent.atomic.AtomicInteger;

public class Counter {
    private AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        count.incrementAndGet(); // 原子方式递增
    }

    public int getCount() {
        return count.get();
    }
}

在这个例子中,incrementAndGet()方法原子地将count的值增加1,并返回新值。这种方式保证了即使多个线程同时调用increment()方法,每次调用也能安全地修改count的值,而无需同步。

AtomicLong和AtomicReference的应用

AtomicInteger相似,AtomicLong提供了针对长整型的原子操作。AtomicReference则允许我们对任何对象的引用进行原子更新,这在管理共享对象时非常有用。

示例:使用AtomicLong
import java.util.concurrent.atomic.AtomicLong;

public class SequenceGenerator {
    private AtomicLong value = new AtomicLong(0);

    public long next() {
        return value.incrementAndGet(); // 原子方式递增
    }
}

SequenceGenerator类使用AtomicLong来生成唯一的序列号。每次调用next()方法都会安全地递增内部计数器。

示例:使用AtomicReference
import java.util.concurrent.atomic.AtomicReference;

public class Node {
    static class Link {
        final String data;
        Link next;

        Link(String data) {
            this.data = data;
        }
    }

    private AtomicReference<Link> head = new AtomicReference<>();

    public void add(String data) {
        Link newLink = new Link(data);
        Link oldHead;
        do {
            oldHead = head.get();
            newLink.next = oldHead;
        } while (!head.compareAndSet(oldHead, newLink));
    }
}

在这个例子中,使用AtomicReference来管理链表的头部。add方法通过原子的compareAndSet操作尝试更新链表的头部,这保证了即使多个线程同时添加元素,链表的状态也总是一致的。

原子类的优势

原子类提供的主要优势是无锁的线程安全性,这通常比使用锁或其他同步机制具有更高的性能,特别是在高竞争的环境中。通过利用现代CPU提供的底层并发原语,原子类能够提供一种有效的方法来减少并发程序的复杂性。

通过上述示例和解释,可以看到原子类是实现高效并发处理的强大工具。它们不仅可以简化代码,还可以提高程序的响应速度和吞吐量,是现代Java并发编程中不可或缺的一部分。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

小米15系列将首发骁龙8 Gen4 SoC

高通已确认2024年骁龙峰会定于10月21日举行。在这次峰会中高通将推出其最新的移动芯片Snapdragon 8 Gen4 SoC。著名科技博主DigitalChatStation今天证实&#xff0c;骁龙8 Gen4将以小米15系列首次亮相。这意味着小米15系列将是第一款使用这款新旗舰处理器的手机。 这不是小米第…

【软件设计】详细设计说明书(word原件,项目直接套用)

软件详细设计说明书 1.系统总体设计 2.性能设计 3.系统功能模块详细设计 4.数据库设计 5.接口设计 6.系统出错处理设计 7.系统处理规定 软件全套资料&#xff1a;本文末个人名片直接获取或者进主页。

使用ViewDragHelper打造属于自己的DragLayout(抽屉开关 )

</com.xujun.drawerLayout.drag.DragLayout> 在代码中若想为其设置监听器, 分别可以监听打开的 时候&#xff0c;关闭的时候&#xff0c;拖动的时候&#xff0c;可以在里面做相应的处理&#xff0c;同时我还加入了 自定义属性可以通过 app:range”480”或者setRange&am…

基于JSP技术的家用电器销售网站

开头语&#xff1a;你好呀&#xff0c;我是计算机学长猫哥&#xff01;如果有相关需求&#xff0c;文末可以找到我的联系方式。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;JSPJava 工具&#xff1a;ECLIPSE、MySQL数据库管理工具、Tomcat 系统展…

MVVM架构详解:前端开发的理想选择

目录 前言1. MVVM架构概述1.1 MVVM架构的定义1.2 MVVM与MVC的区别 2. MVVM架构的核心组件2.1 模型&#xff08;Model&#xff09;2.2 视图&#xff08;View&#xff09;2.3 视图模型&#xff08;ViewModel&#xff09; 3. MVVM架构的优势3.1 分离关注点3.2 提高代码可测试性3.3…

听说你还不会用Dagger2?Dagger2 For Android最佳实践教程

Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG,chef.cook()); } } 可以看到&#xff0c;在使用Dagger2的时候&#xff0c;使用者的代码会变得非常简洁。但是&#…

Windows10中端口被占用处理方法

前言 在Windows 10中&#xff0c;查看端口被占用情况的方法主要依赖于命令行工具netstat。以下是详细步骤&#xff0c;以及必要的解释和归纳&#xff1a; 打开命令提示符 方法1&#xff1a;使用快捷键Win R&#xff0c;打开“运行”对话框&#xff0c;输入cmd&#xff0c;然…

2024-06-23 编译原理实验3——语义分析

文章目录 一、实验要求二、实验设计三、实验结果四、附完整代码 补录与分享本科实验&#xff0c;以示纪念。 一、实验要求 基于前面的实验&#xff0c;编写一个程序对使用 C—语言书写的源代码进行语义分析&#xff0c;输出语义分析中发现的错误&#xff08;涉及 17 种错误类…

异地局域网纯软件组网如何设置?

在现代社会中&#xff0c;随着企业的不断扩张和分布&#xff0c;异地办公成为一种常见的工作模式。随之而来的是&#xff0c;如何实现异地局域网的组网设置成为了一个挑战。在这种情况下&#xff0c;采用纯软件组网方案是一种有效的解决方案。本文将介绍异地局域网纯软件组网设…

Redis数据库的删除和安装

Redis数据库的删除和安装 1、删除Redis数据库2、下载Redis数据库 1、删除Redis数据库 没有下载过的&#xff0c;可以直接跳到下面的安装过程↓ 我们电脑中如果有下载过Redis数据库&#xff0c;要更换版本的话&#xff0c;其实Redis数据库的删除是比较简单的&#xff0c;打开我…

字节大神强推千页PDF学习笔记,弱化学历问题,已拿意向书字节提前批移动端!

主要问java&#xff0c;以及虚拟机&#xff0c;问了一点android 1.实习项目有关的介绍以及问题回答 2.反射与代理的区别&#xff0c;动态代理&#xff0c;静态代理&#xff0c;二者的区别&#xff0c;以及代理模式的UML图 3.字节码技术 4.虚拟机的双亲委派&#xff0c;以及好…

1Panel应用推荐:Bitwarden开源密码管理器

1Panel&#xff08;github.com/1Panel-dev/1Panel&#xff09;是一款现代化、开源的Linux服务器运维管理面板&#xff0c;它致力于通过开源的方式&#xff0c;帮助用户简化建站与运维管理流程。为了方便广大用户快捷安装部署相关软件应用&#xff0c;1Panel特别开通应用商店&am…

贪心算法—

贪心算法是一种在每一步选择中都采取在当前状态下最好或最优&#xff08;即最有利&#xff09;的选择&#xff0c;从而希望导致结果是全局最好或最优的算法。这种算法并不总是能找到全局最优解&#xff0c;但在某些问题上能提供足够好的解决方案。贪心算法的关键特性包括&#…

判断题无答案22届期末复习

判断: 1-3.结构体变量不能进行整体输入输出。 1-4.不同类型的结构变量之间也可以直接赋值。 1-5假设结构指针p已定义并正确赋值,其指向的结构变量有一个成员是int型的num,则语句 (*p).num = 100; 等价于p->num=1…

1panel OpenResty 设置网站重定向

当我们部署网站时需要&#xff0c;输入"cheshi.com"域名回车&#xff0c;希望他自动跳转https://cheshi.com/indx/&#xff0c;而不是直接跳转https://cheshi.com时可以利用重定向来实现&#xff0c; 这里演示的是 1panel 如何设置&#xff08;nginx 貌似也是这样配…

【大疆pocket3】到手后5个必改初始设置关键点(下)

【大疆pocket3】到手后5个必改初始设置关键点&#xff08;下&#xff09; 一&#xff0c;简介二&#xff0c;必改关键点2.1 数字变焦2.2 慢动作拍摄2.3 神奇的小摇杆2.4 云台模式使用方法&#xff08;默认增稳模式和俯仰角锁定的差异化以及使用场景&#xff09;2.5 云台转向速度…

[HBM] HBM 国产进程, 国产HBM首次研发成功 (202406)

依公知及经验整理&#xff0c;原创保护&#xff0c;禁止转载。 专栏 《深入理解DDR》 AI 的火热浪潮带火了高带宽内存的需求&#xff0c;HBM已是存储市场耀眼的明星。目前市场上还没有国产HBM, 什么时候可以看到国产希望呢&#xff1f; 或许现在可以看到曙光了。 1. 设计端 1…

Linux查看公网IP的解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

使用Android Studio导入源码

2-1 基础准备工作 首先你得安装配置了Android Studio&#xff0c;具体不明白的参考《Android Studio入门到精通 》。 接着你得下载好了源码Code&#xff0c;至于如何下载这里不再说明&#xff0c;比较简单&#xff0c;上官网查看就行了。 其次你需要保证源码已经被编译生成了…

压力测试

1.什么是压力测试 压力测试考察当前软硬件环境下系统所能承受的最大负荷并帮助找出系统瓶颈所在。压测都是为了系统在线上的处理能力和稳定性维持在一个标准范围内&#xff0c;做到心中有数 使用压力测试&#xff0c;我们有希望找到很多种用其他测试方法更难发现的错误&#…