线程并发库和线程池的作用_线程和并发介绍

线程并发库和线程池的作用

本文是我们名为Java Concurrency Essentials的学院课程的一部分。

在本课程中,您将深入探讨并发的魔力。 将向您介绍并发和并发代码的基础知识,并学习诸如原子性,同步和线程安全性的概念。 在这里查看 !

目录

1.有关线程的基本知识 2.创建和启动线程 3.睡觉和打断 4.连接线程 5.同步 6.原子访问

1.有关线程的基本知识

并发是程序同时执行多个计算的能力。 这可以通过将计算分布在计算机的可用CPU内核上,甚至在同一网络内的不同计算机上来实现​​。

为了更好地理解并行执行,我们必须区分进程和线程。 进程是操作系统提供的执行环境,它具有自己的一组私有资源(例如,内存,打开的文件等)。 相反, Threads是指生活在一个流程中并与该流程的其他线程共享资源(内存,打开的文件等)的流程。

在不同线程之间共享资源的能力使线程更适合于对性能有重要要求的任务。 尽管可以在同一计算机上甚至在同一网络内的不同计算机上运行的不同进程之间建立进程间通信,但是出于性能原因,通常会选择线程来并行化单台计算机上的计算。

在Java中,进程对应于正在运行的Java虚拟机(JVM),而线程位于同一个JVM中,并且可以由Java应用程序在运行时动态创建和停止。 每个程序至少有一个线程:主线程。 这个主线程是在每个Java应用程序启动期间创建的,它是调用程序的main()方法的那个线程。 从这一点开始,Java应用程序可以创建新的线程并使用它们。

下面的源代码对此进行了演示。 JDK类java.lang.Thread的静态方法currentThread()提供对当前Thread访问:

public class MainThread {public static void main(String[] args) {long id = Thread.currentThread().getId();String name = Thread.currentThread().getName();int priority = Thread.currentThread().getPriority();State state = Thread.currentThread().getState();String threadGroupName = Thread.currentThread().getThreadGroup().getName();System.out.println("id="+id+"; name="+name+"; priority="+priority+"; state="+state+"; threadGroupName="+threadGroupName);}}

从这个简单应用程序的源代码中可以看到,我们直接在main()方法中访问当前Thread ,并打印出有关它的一些信息:

id=1; name=main; priority=5; state=RUNNABLE; threadGroupName=main

输出揭示了有关每个线程的一些有趣信息。 每个线程都有一个标识符,该标识符在JVM中是唯一的。 线程的名称有助于在监视运行中的JVM的外部应用程序(例如调试器或JConsole工具)中找到某些线程。 当执行多个线程时,优先级决定下一个应该执行的任务。

关于线程的真相是,并非所有线程都真正同时执行,而是将每个CPU内核上的执行时间分成小片,并将下一个时间片分配给具有最高优先级的下一个等待线程。 JVM的调度程序根据线程的优先级确定下一个要执行的线程。

在优先级旁边,线程还具有状态,可以是以下状态之一:

  • 新:尚未启动的线程处于此状态。
  • 可运行:在Java虚拟机中执行的线程处于此状态。
  • BLOCKED:处于等待监视器锁定状态的被阻塞线程处于此状态。
  • 等待:无限期等待另一个线程执行特定操作的线程处于此状态。
  • TIMED_WAITING:正在等待另一个线程执行操作的线程最多达到指定的等待时间,该线程处于此状态。
  • 终止:退出的线程处于此状态。

上面示例中的主线程当然处于RUNNABLE状态。 像BLOCKED这样的状态名称已经在这里表明线程管理是高级主题。 如果处理不当,线程可能会相互阻塞,进而导致应用程序挂起。 但是我们稍后会谈到。

最后但并非最threadGroup是,线程的属性threadGroup指示线程是按组管理的。 每个线程都属于一组线程。 JDK类java.lang.ThreadGroup提供了一些方法来处理整个Threads组。 通过这些方法,我们可以例如中断组中的所有线程或设置其最大优先级。

2.创建和启动线程

现在,我们已经仔细研究了线程的属性,是时候创建和启动我们的第一个线程了。 基本上,有两种方法可以用Java创建线程。 第一个是编写一个扩展JDK类java.lang.Thread类:

public class MyThread extends Thread {public MyThread(String name) {super(name);}@Overridepublic void run() {System.out.println("Executing thread "+Thread.currentThread().getName());}public static void main(String[] args) throws InterruptedException {MyThread myThread = new MyThread("myThread");myThread.start();}}

从上面可以看到,类MyThread扩展了Thread类并覆盖了run()方法。 虚拟机启动线程后,将执行run()方法。 由于虚拟机必须做一些工作才能设置线程的执行环境,因此我们无法直接调用此方法来启动线程。 相反,我们在类MyThread的实例上调用方法start() 。 当此类从其超类继承方法stop() ,该方法背后的代码告诉JVM为线程分配所有必需的资源并启动该线程。 当我们运行上面的代码时,我们看到输出“ Executing thread myThread”。 与我们的介绍示例相反,方法run()的代码不是在“主”线程中执行的,而是在我们自己的名为“ myThread”的线程中执行的。

创建线程的第二种方法是实现接口Runnable

public class MyRunnable implements Runnable {public void run() {System.out.println("Executing thread "+Thread.currentThread().getName());}public static void main(String[] args) throws InterruptedException {Thread myThread = new Thread(new MyRunnable(), "myRunnable");myThread.start();}}

与子类化方法的主要区别在于,我们创建了java.lang.Thread的实例,并提供了将Runnable接口实现为Thread构造函数的参数的类的实例。 在此实例旁边,我们还传递了Thread的名称,以便从命令行执行程序时看到以下输出:“ Executing thread myRunnable”。

是否应使用子类化或接口方法,取决于您的喜好。 该接口是一种更轻便的方法,因为您要做的就是实现接口。 该类仍然可以是某些其他类的子类。 您还可以将自己的参数传递给构造函数,而Thread子类将您限制为Thread类带来的可用构造函数。

在本系列的后面部分,我们将了解线程池,并了解如何启动相同类型的多个线程。 在这里,我们将再次使用Runnable方法。

3.睡觉和打断

一旦启动了Thread ,它将一直运行直到run()方法结束。 在上面的示例中, run()方法所做的只是打印出当前线程的名称。 因此线程很快完成。

在现实世界的应用程序中,通常必须实现某种后台处理,在这种处理中,线程必须运行,直到例如已经处理了目录结构中的所有文件。 另一个常见的用例是有一个后台线程,如果发生任何事情(例如,已创建文件),则每隔n秒查看一次,并启动某种操作。 在这种情况下,您将必须等待n秒或毫秒。 您可以使用while循环来实现这一点,该循环的主体获取当前的毫秒数并查看下一秒的时间。 尽管这样的实现可行,但是由于您的线程占用了CPU并一次又一次地获取当前时间,因此浪费了CPU处理时间。

对于此类用例,更好的方法是调用java.lang.Thread类的sleep()方法,如以下示例所示:

public void run() {while(true) {doSomethingUseful();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}

sleep()的调用使当前Thread进入睡眠状态,而不消耗任何处理时间。 这意味着当前线程将从活动线程列表中删除自己,并且调度程序不会在第二次(以毫秒为单位)过去之前将其调度用于下一次执行。

请注意,传递给sleep()方法的时间只是调度程序的指示,而不是绝对准确的时间范围。 由于实际的调度,线程可能会提前几纳秒或几毫秒返回。 因此,您不应将此方法用于实时调度。 但是对于大多数使用情况,所达到的精度是足够的。

在上面的代码示例中,您可能已经注意到sleep()可能抛出的InterruptedException 。 中断是线程交互的一个非常基本的功能,可以理解为一个线程发送到另一个线程的简单中断消息。 接收线程可以通过调用Thread.interrupted()方法来显式询问它是否已被中断,或者在将其时间花在诸如sleep()之类的方法上时会隐式中断,该方法在发生中断的情况下会引发异常。

让我们用下面的代码示例仔细看一下中断:

public class InterruptExample implements Runnable {public void run() {try {Thread.sleep(Long.MAX_VALUE);} catch (InterruptedException e) {System.out.println("["+Thread.currentThread().getName()+"] Interrupted by exception!");}while(!Thread.interrupted()) {// do nothing here}System.out.println("["+Thread.currentThread().getName()+"] Interrupted for the second time.");}public static void main(String[] args) throws InterruptedException {Thread myThread = new Thread(new InterruptExample(), "myThread");myThread.start();System.out.println("["+Thread.currentThread().getName()+"] Sleeping in main thread for 5s...");Thread.sleep(5000);System.out.println("["+Thread.currentThread().getName()+"] Interrupting myThread");myThread.interrupt();System.out.println("["+Thread.currentThread().getName()+"] Sleeping in main thread for 5s...");Thread.sleep(5000);System.out.println("["+Thread.currentThread().getName()+"] Interrupting myThread");myThread.interrupt();}}

在main方法中,我们首先启动一个新线程,如果不中断它将会Hibernate很长时间(大约290.000年)。 为了在这段时间过去之前完成程序,通过在main方法中对其实例变量调用interrupt()来中断myThread 。 这会在sleep()调用中引起InterruptedException ,并在控制台上显示为“ Interrupted by exception!”。 记录了异常后,线程会进行一些繁忙的等待,直到设置了线程上的中断标志为止。 通过在线程的实例变量上调用interrupt()再次从主线程进行设置。 总的来说,我们在控制台上看到以下输出:

[main] Sleeping in main thread for 5s...
[main] Interrupting myThread
[main] Sleeping in main thread for 5s...
[myThread] Interrupted by exception!
[main] Interrupting myThread
[myThread] Interrupted for the second time.

此输出中有趣的是第3行和第4行。如果我们遍历代码,我们可能期望字符串“ Interrupted by exception!”。 在主线程再次开始Hibernate之前,将打印出“Hibernate5s…”。 但是从输出中可以看到,调度程序在再次启动myThread之前已经执行了主线程。 因此,在主线程开始Hibernate之后,myThread打印出接收到的异常。

当使用多个线程进行编程时,这是一个基本观察结果,即很难预测线程的日志记录输出,因为很难计算下一个执行的线程。 当您不得不处理更多的线程(如上例所示)的暂停没有被硬编码时,情况变得更加糟糕。 在这些情况下,整个程序会获得某种内部动力,这使得并发编程成为一项艰巨的任务。

4.连接线程

正如在上一节中所看到的,我们可以让我们的线程进入睡眠状态,直到被另一个线程唤醒。 您将不时使用的线程的另一个重要功能是线程等待另一个线程终止的能力。

假设您必须实施某种数字运算,可以将其分为几个并行运行的线程。 启动所谓的工作线程的主线程必须等待,直到其所有子线程都终止。 以下代码显示了如何实现此目的:

public class JoinExample implements Runnable {private Random rand = new Random(System.currentTimeMillis());public void run() {//simulate some CPU expensive taskfor(int i=0; i<100000000; i++) {rand.nextInt();}System.out.println("["+Thread.currentThread().getName()+"] finished.");}public static void main(String[] args) throws InterruptedException {Thread[] threads = new Thread[5];for(int i=0; i<threads.length; i++) {threads[i] = new Thread(new JoinExample(), "joinThread-"+i);threads[i].start();}for(int i=0; i<threads.length; i++) {threads[i].join();}System.out.println("["+Thread.currentThread().getName()+"] All threads have finished.");}}

在我们的main方法中,我们创建了一个由5个Threads的数组,它们全部一个接一个地启动。 启动它们后,我们在主Thread等待其终止。 线程本身通过计算一个随机数来模拟一些数字运算。 完成后,将打印“完成”。 最后,主线程确认其所有子线程的终止:

[joinThread-4] finished.
[joinThread-3] finished.
[joinThread-2] finished.
[joinThread-1] finished.
[joinThread-0] finished.
[main] All threads have finished.

您会发现,“完成”消息的顺序因执行而异。 如果您多次执行该程序,您可能会看到最先完成的线程并不总是相同的。 但是最后一条语句始终是等待其子级的主线程。

5.同步

正如我们在最后一个示例中所看到的,执行所有正在运行的线程的确切顺序取决于线程配置,例如优先级还取决于可用的CPU资源以及调度程序选择下一个线程执行的方式。 尽管调度程序的行为是完全确定的,但是很难预测在给定时间点的哪个时刻哪个线程执行。 这使得对共享资源的访问变得至关重要,因为很难预测哪个线程将是尝试访问它的第一个线程。 通常,对共享资源的访问是排他性的,这意味着在给定时间点只有一个线程应访问此资源,而没有任何其他线程干扰此访问。

一个并发访问独占资源的简单示例是一个静态变量,该变量增加一个以上线程:

public class NotSynchronizedCounter implements Runnable {private static int counter = 0;public void run() {while(counter < 10) {System.out.println("["+Thread.currentThread().getName()+"] before: "+counter);counter++;System.out.println("["+Thread.currentThread().getName()+"] after: "+counter);}}public static void main(String[] args) throws InterruptedException {Thread[] threads = new Thread[5];for(int i=0; i<threads.length; i++) {threads[i] = new Thread(new NotSynchronizedCounter(), "thread-"+i);threads[i].start();}for(int i=0; i<threads.length; i++) {threads[i].join();}}}

当我们仔细查看此简单应用程序的输出时,我们看到类似以下内容的内容:

[thread-2] before: 8
[thread-2] after: 9
[thread-1] before: 0
[thread-1] after: 10
[thread-2] before: 9
[thread-2] after: 11

在这里,线程2将当前值检索为8,然后将其递增,然后是9。这就是我们以前期望的值。 但是以下线程执行的内容可能使我们感到惊讶。 线程1将当前值输出为零,将其递增,然后为10。这怎么办? 当线程1读取变量计数器的值时,该值为0。然后上下文切换执行第二个线程,并且当线程1再次轮到该线程时,其他线程已经将计数器递增到9。结果是10。

此类问题的解决方案是Java中的同步关键字。 使用同步,您可以创建只能由线程访问的语句块,该线程获得了对同步资源的锁定。 让我们从上一个示例中更改run()方法,并为整个类引入一个同步块:

public void run() {while (counter < 10) {synchronized (SynchronizedCounter.class) {System.out.println("[" + Thread.currentThread().getName() + "] before: " + counter);counter++;System.out.println("[" + Thread.currentThread().getName() + "] after: " + counter);}}}

synchronized(SynchronizedCounter.class)语句就像一个屏障,在该屏障中,所有线程都必须停止并要求进入。 只有第一个获得资源锁的线程才被允许通过。 一旦离开同步块,另一个等待线程可以进入,依此类推。

在输出周围有同步块且输出上方计数器递增的情况下,如下例所示:

[thread-1] before: 11
[thread-1] after: 12
[thread-4] before: 12
[thread-4] after: 13

现在,您将只看到计数器变量加1之前和之后的后续输出。
可以以两种不同的方式使用synced关键字。 可以在上述方法中使用它。 在这种情况下,您必须提供一个被当前线程锁定的资源。 必须谨慎选择此资源,因为基于变量的范围,线程屏障的含义完全不同。

如果变量是当前类的成员,则所有线程都将与该类的实例同步,因为每个LocalSync实例都存在变量sync:

public class LocalSync {private Integer sync = 0;public void someMethod() {synchronized (sync) {// synchronized on instance level}}}

除了创建覆盖整个方法主体的块之外,您还可以添加与方法签名同步的关键字。 下面的代码与上面的代码具有相同的作用:

public class MethodSync {private Integer sync = 0;public synchronized void someMethod() {// synchronized on instance level}}

两种方法之间的主要区别在于,第一种方法的粒度更细,因为您可以使同步块比方法主体小。 请记住,同步块一次只能由一个线程执行,因此每个同步块都是潜在的性能问题,因为所有并发运行的线程可能必须等待直到当前线程离开该块。 因此,我们应始终尝试使块尽可能小。

大多数情况下,您将不得不同步对每个JVM仅存在一次的某些资源的访问。 常用的方法是使用类的静态成员变量:

public class StaticSync {private static Integer sync = 0;public void someMethod() {synchronized (sync) {// synchronized on ClassLoader/JVM level}}}

上面的代码同步在同一JVM中通过方法someMethod()运行的所有线程,因为静态变量在同一JVM中仅存在一次。 如您所知,一个类只有在由同一类加载器加载的情况下,才在一个JVM中是唯一的。 如果使用多个类加载器加载类StaticSync ,则静态变量将不止一次存在。 但是在大多数日常应用程序中,您不会有多个类加载器来两次加载同一类,因此您可以假定静态变量仅存在一次,因此同一JVM中的所有线程都必须等待障碍,直到它们获得锁。

6.原子访问

在上一节中,我们看到了当许多并发线程必须执行代码的特定部分但每个时间点仅一个线程应执行该代码时,如何同步对某些复杂资源的访问。 我们还看到,如果不同步对公共资源的访问,则对这些资源的操作会交织并可能导致非法状态。

Java语言提供了一些原子性的基本操作,因此可用于确保并发线程始终看到相同的值:

  • 对引用变量和原始变量(长整型和双精度型除外)的读写操作
  • 对所有声明为易失性的变量的读写操作

为了更详细地了解这一点,我们假设我们有一个HashMap填充了从文件读取的属性,以及一堆使用这些属性的线程。 显然,我们这里需要某种同步,因为读取文件和更新Map花费时间,并且在此期间将执行其他线程。

我们无法轻松地在所有线程之间共享此Map一个实例,并且无法在更新过程中使用此Map 。 这将导致Map状态不一致,该状态由访问线程读取。 有了上一节的知识,我们当然可以在映射的每次访问(读/写)周围使用一个同步块,以确保所有线程仅看到一个状态,而不是部分更新的Map 。 但是,如果必须非常频繁地从Map读取并发线程,则会导致性能问题。

为同步块中的每个线程克隆Map并让每个线程在单独的副本上工作也是一种解决方案。 但是每个线程都必须不时请求更新的副本,并且该副本占用内存,这在每种情况下都不可行。 但是有一个更简单的解决方案。

由于我们知道对引用的写操作是原子的,因此每次读取文件并在一个原子操作中更新线程之间共享的引用时,就可以创建一个新的Map 。 在此实现中,工作线程将永远不会读取不一致的Map因为使用一个原子操作更新了Map

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;public class AtomicAssignment implements Runnable {private static volatile Map<String, String> configuration = new HashMap<String, String>();public void run() {for (int i = 0; i < 10000; i++) {Map<String, String> currConfig = configuration;String value1 = currConfig.get("key-1");String value2 = currConfig.get("key-2");String value3 = currConfig.get("key-3");if (!(value1.equals(value2) && value2.equals(value3))) {throw new IllegalStateException("Values are not equal.");}try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}}}public static void readConfig() {Map<String, String> newConfig = new HashMap<String, String>();Date now = new Date();SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss:SSS");newConfig.put("key-1", sdf.format(now));newConfig.put("key-2", sdf.format(now));newConfig.put("key-3", sdf.format(now));configuration = newConfig;}public static void main(String[] args) throws InterruptedException {readConfig();Thread configThread = new Thread(new Runnable() {public void run() {for (int i = 0; i < 10000; i++) {readConfig();try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}}}}, "configuration-thread");configThread.start();Thread[] threads = new Thread[5];for (int i = 0; i < threads.length; i++) {threads[i] = new Thread(new AtomicAssignment(), "thread-" + i);threads[i].start();}for (int i = 0; i < threads.length; i++) {threads[i].join();}configThread.join();System.out.println("[" + Thread.currentThread().getName() + "] All threads have finished.");}
}

上面的例子稍微复杂一点,但并不难理解。 共享的MapAtomicAssignment的配置变量。 在main()方法中,我们最初读取配置一次,然后向Map添加三个具有相同值的键(此处为当前时间,包括毫秒)。 然后,我们启动一个“配置线程”,该线程通过将当前时间戳始终添加到地图的三倍来模拟配置的读取。 然后,五个工作线程使用配置变量读取Map并比较三个值。 如果它们不相等,则抛出IllegalStateException。

您可以运行该程序一段时间,并且不会看到任何IllegalStateException 。 这是由于我们通过一次原子操作将新Map分配给共享配置变量的事实:

configuration = newConfig;

我们还可以在一个原子步骤中读取共享变量的值:

Map<String, String> currConfig = configuration;

由于这两个步骤都是原子步骤,因此我们将始终引用所有三个值相等的有效Map实例。 例如,如果更改run()方法的方式是直接使用配置变量,而不是先将其复制到本地变量,则很快就会看到IllegalStateExceptions因为配置变量始终指向“当前”配置。 当配置线程更改了它之后,对Map后续读取访问将已经读取新值,并将它们与旧Map中的值进行比较。

如果直接在配置变量上使用readConfig()方法而不是创建新的Map并通过一次原子操作将其分配给共享变量,则情况也是如此。 但是可能要花一些时间,直到看到第一个IllegalStateException为止。 这对于使用多线程的所有应用程序都是如此。 并发问题乍一看并不总是很明显,但是它们需要在重负载条件下进行一些测试才能出现。

翻译自: https://www.javacodegeeks.com/2015/09/introduction-to-threads-and-concurrency.html

线程并发库和线程池的作用

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

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

相关文章

快速搞定C/C++ 的条件编译

点击蓝字关注我们1、条件编译的时机我们都知道vscode其实是一个编辑器&#xff0c;你要在上面跑C或者C你需要配置编译器&#xff0c;拿编译器是怎样吧一个文本文件变成一个可执行文件的呢&#xff1f;那必然是经历以下这四步预处理&#xff1a;宏替换&#xff0c;头文件的展开&…

C语言加强学习营(二):定义整型的最大值和最小值

示例 #include <stdio.h> #include <stdint.h>int main(int argc, char **argv) {uint8_t uint8_max ~(uint8_t)0; /*!< 定义无符号字符型型的最大值 */int8_t int8_max (int8_t)((uint8_t)(~1) >> 1); /*!< 定义有符号字符型的最大值 */uint32_t u…

python连接不上数据库_pycharm连接mysql数据库连接不上

代码其实很简单&#xff0c;只有一小段&#xff0c;是在pycharm上运行的&#xff0c;所用的python版本为2.7&#xff0c;mysql版本为5.7.21 # -*- coding: UTF-8 -*- import re import MySQLdb if __name__ __main__: #打开数据库 conn MySQLdb.connect(hostlocalhost,port33…

assertj_AssertJ的SoftAssertions –我们需要它们吗?

assertj编写好的单元测试的规则之一是&#xff0c;它应该由于一种原因而失败&#xff0c;因此&#xff0c;单元测试应该测试一种逻辑概念。 有时很难在每个测试中拥有一个断言。 为了遵循规则&#xff0c;我们可能在单个测试中每个对象具有多个断言。 但是&#xff0c;在一个测…

用C/C++语言代码实现一个虚拟机

点击蓝字关注我们本文将教你编写一个自己的虚拟机&#xff08;VM&#xff09;&#xff0c;这个虚拟机能够运行汇编语言编写的程序&#xff0c; 例如我朋友编写的 2048 或者我自己的 Roguelike。如果你会编程&#xff0c;但希望 更深入地了解计算机的内部原理以及编程语言是如何…

python对列表中的数值进行统计运算_python-从单词列表中计算元音并返回数字作......

您正在提供一个列表,但是您的逻辑仅适用于单个字符串&#xff1a; number_of_vowels(Bean) # [2] 您需要调整函数的输入,或者修改函数以计算列表中每个元素的元音数量.由于您希望输出为数字列表,因此我假设您正在寻找第二个选项. 为此,只需在列表理解中添加一个额外的for子句&a…

杜克大学_记录链接:与杜克一起玩

杜克大学最近&#xff0c;我在记录链接方面变得非常有趣&#xff0c;并遇到了Duke项目&#xff0c;该项目提供了一些工具来帮助解决此问题。 我以为我会尝试一下。 进行记录链接时的典型问题是&#xff0c;我们有两个来自不同数据集的记录&#xff0c;它们代表同一实体&#x…

嵩天python笔记_嵩天Python学习笔记-05

文件和数据格式化 文本文件和二进制文件 # 文本形式打开文件 tf open("f.txt", "rt") printf(tf.readline()) tf.close() 文件的打开和关闭 文件处理的步骤&#xff1a;打开->操作->关闭 a.open( <文件名>, <打开模式> ) # 打开文件 # …

web编程 端口分配_以编程方式衡量分配

web编程 端口分配我从Heinz Kabutz撰写的Java专家通讯中获得了这个技巧。 &#xff08;对于所有想要了解JDK内容的Java开发人员来说&#xff0c;这是绝对必要的&#xff01;&#xff09; 特别是对于编写低延迟代码的开发人员&#xff0c;即使对于普通的Java代码&#xff0c;分…

C++—vector的使用

点击蓝字关注我们一、vector的介绍说的简单点&#xff1a;vector是可以动态增长的数组容器vector是表示可变大小数组的序列容器。就像数组一样&#xff0c;vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问&#xff0c;和数组一样高效。…

python词频统计完整步骤_Python中文文本分词、词频统计、词云绘制

本文主要从中文文本分词、词频统计、词云绘制方面介绍Python中文文本分词的使用。会使用到的中文文本处理包包括&#xff1a;wordcloud,jieba&#xff0c;re&#xff08;正则表达式&#xff09;,collections。 1 准备工作 导入相关的包&#xff0c;读取相关数据。 #导入包 impo…

现代 C++ 测试工具链

点击蓝字关注我们gtest的问题gtest需要安装有时候带来很多不方便&#xff0c;比如需要经常切换gcc和clang的时候就比较麻烦&#xff0c;安装的gtest可能在另一个编译器下编译不过, 编写跨平台程序的时候需要多次安装gtest&#xff0c;非常不便。另外一个问题是网络原因&#xf…

数据结构压缩_将数据压缩到数据结构中

数据结构压缩这个故事是关于我们最近在Plumbr进行的容量优化任务。 一切始于将无害的要求添加到现有组合中。 您可能知道&#xff0c;Plumbr监视解决方案作为连接到服务器的Java代理分发。 只需少量添加即可跟踪一段时间内所有已连接的代理&#xff0c;以便可以实时回答以下问…

python打开软件输入消息_用Python编写一个私人助理程序,为我们起草电子邮件!...

在你的工作中是否有一些你自己经常做重复的任务?这就是编程的乐趣所在。通过一些思考和编程&#xff0c;您可以使您的任务自动化&#xff0c;并为您节省大量时间。在本文中&#xff0c;我们将介绍一些Python工具和技巧&#xff0c;让你可以创建自己的Python个人助理。1. 助手功…

swing 状态视图分离_Java Swing模型视图适配器介体

swing 状态视图分离通常&#xff0c;我基于Spring Framework构建Java应用程序。 但是&#xff0c;最近有人要求我使用与语言无关的MVC框架PureMVC为客户端实现Java桌面应用程序&#xff0c;因此以下是我在Java Swing中为PureMVC进行员工管理展示的演示实现。 如果您想继续学习&…

超级炫酷的C语言技巧!

点击蓝字关注我们C语言常常让人觉得它所能表达的东西非常有限。它不具有类似第一级函数和模式匹配这样的高级功能。但是C非常简单&#xff0c;并且仍然有一些非常有用的语法技巧和功能&#xff0c;只是没有多少人知道罢了。一、指定的初始化很多人都知道像这样来静态地初始化数…

webgl 基础渲染demo_WebGL + ThreeJS 实现实时水下焦散 Part 1

知乎视频​www.zhihu.com采用 WebGL 和 ThreeJS 运行实时焦散运算&#xff0c;需要一点相关基础。本文主要介绍焦散的原理以及计算方法原作者https://github.com/martinRenou​github.com代码和原文https://github.com/martinRenou/threejs-caustics​github.com由于本人的笔电…

python代码性能分析_使用memory_profiler对代码进行性能分析会增加执行时间

我正在编写一个简单的应用程序&#xff0c;它将大文本文件拆分为较小的文件&#xff0c;并且我已经编写了2个版本&#xff0c;一个使用列表&#xff0c;另一个使用生成器。我使用memory_profiler模块对这两个版本进行了概要分析&#xff0c;并清楚地显示了生成器版本的更好的内…

超硬核C++BestPractices翻译与阅读笔记

点击蓝字关注我们硬货开始这本书的副标题是&#xff1a;45ish Simple Rules with Specific Action items for better C ,这本书是由大佬推荐的&#xff0c; C学习有必要掌握一下这45条最佳实践&#xff0c; 可以很大程度上提升代码的可读性和健壮性&#xff0c; 而且这本书也不…

redis集成spring_将Redis集成到您的Spring项目中

redis集成spring本文介绍如何通过注释配置将Redis缓存集成到您的spring项目中。 我们将从Gradle配置开始。 我们将使用jedis驱动程序。 group com.gkatzioura.spring version 1.0-SNAPSHOTapply plugin: java apply plugin: eclipse apply plugin: idea apply plugin: spring…