当前位置: 首页 > news >正文

企业网站管理关键词你们懂的

企业网站管理,关键词你们懂的,婚恋注册,石家庄网络科技有限公司线程并发库和线程池的作用本文是我们名为Java Concurrency Essentials的学院课程的一部分。 在本课程中#xff0c;您将深入探讨并发的魔力。 将向您介绍并发和并发代码的基础知识#xff0c;并学习诸如原子性#xff0c;同步和线程安全性的概念。 在这里查看 #xff01;… 线程并发库和线程池的作用 本文是我们名为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(idid; namename; prioritypriority; statestate; threadGroupNamethreadGroupName);}} 从这个简单应用程序的源代码中可以看到我们直接在main()方法中访问当前Thread 并打印出有关它的一些信息 id1; namemain; priority5; stateRUNNABLE; threadGroupNamemain 输出揭示了有关每个线程的一些有趣信息。 每个线程都有一个标识符该标识符在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 i0; i100000000; 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 i0; ithreads.length; i) {threads[i] new Thread(new JoinExample(), joinThread-i);threads[i].start();}for(int i0; ithreads.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 i0; ithreads.length; i) {threads[i] new Thread(new NotSynchronizedCounter(), thread-i);threads[i].start();}for(int i0; ithreads.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 MapString, String configuration new HashMapString, String();public void run() {for (int i 0; i 10000; i) {MapString, 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() {MapString, String newConfig new HashMapString, 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.);} } 上面的例子稍微复杂一点但并不难理解。 共享的Map是AtomicAssignment的配置变量。 在main()方法中我们最初读取配置一次然后向Map添加三个具有相同值的键此处为当前时间包括毫秒。 然后我们启动一个“配置线程”该线程通过将当前时间戳始终添加到地图的三倍来模拟配置的读取。 然后五个工作线程使用配置变量读取Map并比较三个值。 如果它们不相等则抛出IllegalStateException。 您可以运行该程序一段时间并且不会看到任何IllegalStateException 。 这是由于我们通过一次原子操作将新Map分配给共享配置变量的事实 configuration newConfig; 我们还可以在一个原子步骤中读取共享变量的值 MapString, 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.zqtcl.cn/news/773357/

相关文章:

  • 成都成华网站建设跟网站开发公司签合同主要要点
  • 手机搭建平台网站化工厂建设网站
  • 怎样建设自己网站的后台龙港哪里有做百度网站的
  • 西安做网站建设哪家好2345网址导航电脑版下载
  • 做暧暧小视频网站十大职业资格培训机构
  • 泰安网站建设优化营销策划是做什么
  • 做网站百度排前位网页设计实训报告2000字
  • 网站建设的活动方案房地产销售渠道拓客方案
  • 哈尔滨网站提升排名版式设计图片
  • 我的专业网站建设策划书网站logo教程
  • 百度 网站 移动端win10系统之家官网
  • h5商城网站建站成都网站建设全平台
  • xuzhou公司网站制作有什么手机网站
  • 网站建设 培训深圳网站建设制作品牌公司
  • 网站到期怎么续费网站运营优化推广
  • 一站式装修的利弊上海建设厅焊工证查询网站
  • 济宁做网站公司找融合深圳招聘一般在哪个网站
  • 重庆建网站推广公司个人网站需要建站群吗
  • 深圳网站建设吗个人博客网站制作代码
  • 化妆品网站模板网络营销的网站分类有哪些
  • 广州网站建设程序员培训wordpress 微信 抓取
  • 毕设给学校做网站个人店铺logo
  • 中国做w7的网站宿迁网站建设价位
  • 网站建设售后服务合同百度关键词排名点击器
  • 编辑网站用什么软件推广是什么
  • 北京模板开发建站做网站赚钱的点在哪里
  • 网站建设价格兴田德润i网址多少wordpress主题汉化是什么意思
  • 用最少的钱做网站根据域名查询网站名称
  • 网站开发答辩难点网站返回按钮设计
  • 鹤壁做网站优化建设银行理财产品网站