线程与并发介绍

本文是我们学院课程中名为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方法中,我们首先启动一个新线程,如果不中断它将会休眠很长时间(大约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!”。 在主线程再次开始休眠之前,将打印出“休眠5s…”。 但是从输出中可以看到,调度程序在再次启动myThread之前已经执行了主线程。 因此,在主线程开始休眠之后,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/359094.shtml

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

相关文章

ajax跨域请求(cors实现),ajax跨域请求(CORS实现)

场景&#xff1a;目前有项目A(基于servlet的WEB项目)&#xff0c;和项目B(基于spring boot的WEB项目)&#xff0c;使用同一CAS提供单点登陆&#xff0c;如今须要两个项目互相调用接口数据&#xff0c;因此涉及到ajax的跨域请求java调研&#xff1a;通过调研发现目前的ajax跨域解…

百度贴吧排名计算方式

排名模拟公式:历史帖子数a+新增帖子数b+∑(单一帖子浏览量该帖回复总数)c+独立ip数d=总人气值 其中,a、b、c、d为系数,如果系数为1,则1;(单一帖子浏览量该帖回复总数)这个比率只有在百度认可的一个范围,譬如说5-8之间才能得到加分,有些贴吧每日新增的帖子数表面…

转-Android Studio *.jar 与 *.aar 的生成与*.aar导入项目方法

主要讲解Android Studio中生成aar文件以及本地方式使用aar文件的方法。 在Android Studio中对一个自己库进行生成操作时将会同时生成*.jar与*.aar文件。 分别存储位置&#xff1a; *.jar&#xff1a;库/build/intermediates/bundles/debug(release)/classes.jar *.aar&#xff…

sprutcam 多机器人_Sprutcam工业机器人离线编程系统

SprutCAM机器人离线编程软件简介1.软件基于Windows风格&#xff0c;中文界面&#xff0c;易学易用&#xff0c;很快可以上手并投入实际工作。2.SprutCAM是一个能独立工作的系统&#xff0c;并非插件,不需要依托任何其他的软件即可独立运行。3.SprutCAM能对6轴工业机器人进行编程…

手机 服务器 推送消息推送消息,推送信息到手机的pushover使用方法及sample code

今天给大家介绍一个好东西&#xff0c;用了两年多了&#xff0c;一直没时间给大家推荐。pushover&#xff0c;移动端的信息推送服务API&#xff0c;包括使用、设置以及API实现。用途在关键节点放上推送(比如用户注册、举报、评论&#xff0c;系统检测等)&#xff0c;当节点触发…

【APICloud系列|8】APICloud下载编译包安装,点击图标打不开,提示很抱歉,程序出现异常,即将退出

下载编译包之后,安装在真机上提示很抱歉,程序出现异常,即将退出。进不去这个应用,请问有什么解决办法? 分享一下解决办法:大体分为两个方向: 1.检查新添加的模块。 2.检查config.xml文件(及index.html文件)。 我的问题是这样解决的,下载每个测试包确定出问题的时间…

4阶范德蒙德行列式例题_行列式的性质和计算问题

行列式计算(2) 01 前言 (1)今天我们继续讨论行列式的性质和计算问题。 (2)①第1题: 很多同学在一开始会直接将行列式拆成两个行列式后直接计算, 这是错误的使用了行列式的性质。注意行列式的分拆是“单行单列可拆”, 所以本题如果一直分拆, 应该是2X2X2=8个行列式, 当然此处我们…

HDU 4508

祼的完全背包问题 #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #define LL __int64 using namespace std;LL dp[100005]; struct Food{int a,b; }fd[105];int main(){int n,m;while(scanf("%d",&n)…

java不想出差_您不想错过的十大Java书籍

java不想出差我们通过阅读书籍并进行实验来学习。 因此&#xff0c;必须选择最佳的可用选项。 在本文中&#xff0c;我想与一些书分享我的经验&#xff0c;以及它们如何帮助您发展成为Java开发人员。 让我们从头开始&#xff0c;对于任何Java学生来说&#xff0c;前三本书都是一…

diff命令两个服务器文件,linux 比较两个文件夹不同 (diff命令, md5列表)

比较文件夹diff&#xff0c;可以直接使用diff命令[root~]# diff -urNa dir1 dir2-a Treat all files as text and compare them line-by-line, even if they do not seem to be text.-N, --new-fileIn directory comparison, if a file is found in only one directory, treat …

各大银行对应的字段(仅做参考)

字符型银行编码银行名称 ICBC_DEBIT 工商银行&#xff08;借记卡&#xff09; ICBC_CREDIT 工商银行&#xff08;信用卡&#xff09; ABC_DEBIT 农业银行&#xff08;借记卡&#xff09; ABC_CREDIT 农业银行&#xff08;信用卡&#xff09; PSBC_CREDIT 邮储银行&…

hive分区用2个字段有何限制_Hive分区表和桶表的使用

我们看官网文档中这个地方我们先创建好数据库&#xff0c;以供练习使用数据库我们创建数据表我们创建分区表&#xff0c;选取的字段不能是表中存在的字段元数据信息Formatted信息那我们加载信息load data local inpath /data/hivetest/dept.txt into table dept_partition part…

Oracle实用技巧

一. ORACLE SQL PLUS 使用技巧: ----①查找重复记录: SELECT DRAWING, DSNOFROM EM5_PIPE_PREFABWHERE ROWID! (SELECT MAX(ROWID) FROM EM5 _PIPE_PREFAB D WHERE EM5_PIPE_PREFAB.DRAWINGD.DRAWING AND EM5_PIPE_PREFAB.DSNOD.DSNO);  -- 和自己连接&#xff0c;查找其最大…

mysql按日、周、月、年分别统计数据

<!-- 按日查询 --> SELECT DATE_FORMAT(created_date,%Y-%m-%d) as time,sum(money) money FROM o_finance_detail where org_id = 1000 GROUP BY time <!-- 按月查询 --> SELECT DATE_FORMAT(created_date,%Y-%m) as time,sum(money) money FROM o_finan…

魔兽三国服务器维护,魔兽三国开服七天技巧

魔兽三国开服七天技巧是9K9K小编星星为大家带来的&#xff0c;开服前七天&#xff0c;是玩家战斗力飙升&#xff0c;最能拉开与别人距离的时候&#xff0c;那么开服七天应该怎么玩呢。开服七天技巧1.抽出第一个英雄&#xff0c;这个看脸哈!通常出的陆逊&#xff0c;步练师&…

使用Docker容器和Java EE进行持续交付

组织需要一种使应用程序交付快速&#xff0c;可预测和安全的方法&#xff0c;而诸如docker之类的容器所提供的敏捷性则可以帮助开发人员实现这一目标。 对于Java EE应用程序&#xff0c;这可以在容器中打包应用程序&#xff0c;应用程序服务器和其他依赖项&#xff0c;这些容器…

MVC小例子

【约定胜于配置】 1. 右键Mode数据层添加新建项&#xff0c;用linq连接数据库 (不要在控制层上直接操控linq&#xff0c;要在数据层新建一个类&#xff0c;来对数据库进行操作) 2. 右键Mode数据层添加类&#xff0c;来完成对数据库的操作.类的名字叫Carda 3. 在Carda类中写对数…

flutter ios打包_Flutter通过BasicMessageChannel与Android iOS 的双向通信

更多文章请查看 flutter从入门 到精通本文章中的完整代码在这里题记&#xff1a;不到最后时刻&#xff0c;千万别轻言放弃&#xff0c;无论结局成功与否&#xff0c;只要你拼博过&#xff0c;尽力过&#xff0c;一切问心无愧。通过 Flutter 来进行移动应用开发&#xff0c;打包…

程序员养家活口接私活必备网站(顺便用技术改变世界)

程序员接私活的原因很多种(挣钱、养家糊口、提升技术等等)。下面整理了一下网站送给最有潜能的你。 提前准备好自己的笔记本和技术呦。 1.码客帮:https://www.make8.com/ 码客帮是一个基于众包的互联网软件技术服务平台,建立项目需求方与技术大牛的连接。帮助需求方快速找到靠…

作为服务器上的操作系统,作为服务器的操作系统

作为服务器的操作系统 内容精选换一换本节介绍如何使用华为云镜像&#xff0c;通过切换镜像部署Windows环境。当您已经购买了弹性云服务器&#xff0c;但想切换成其它类型操作系统&#xff0c;或者想使用镜像重新部署已经预装了其它软件的环境&#xff0c;可以参考本文档的介绍…