Java 并发 - 快速指南


Java 并发 - 概述

Java是一种多线程编程语言,这意味着我们可以使用Java开发多线程程序。多线程程序包含两个或多个可以同时运行的部分,每个部分可以同时处理不同的任务,从而充分利用可用资源,特别是当您的计算机具有多个 CPU 时。

根据定义,多任务处理是指多个进程共享公共处理资源(例如 CPU)。多线程将多任务处理的理念扩展到应用程序中,您可以将单个应用程序中的特定操作细分为单独的线程。每个线程都可以并行运行。操作系统不仅在不同的应用程序之间划分处理时间,而且还在应用程序内的每个线程之间划分处理时间。

多线程使您能够以多种活动可以在同一程序中同时进行的方式进行编写。

线程的生命周期

线程在其生命周期中经历各个阶段。例如,一个线程诞生、启动、运行,然后消亡。下图展示了线程的完整生命周期。

Java线程

以下是生命周期的阶段 -

  • New - 新线程在新状态下开始其生命周期。它保持这种状态直到程序启动线程。它也被称为天生线程

  • Runnable - 新生成的线程启动后,该线程变得可运行。处于这种状态的线程被认为正在执行其任务。

  • 等待- 有时,当线程等待另一个线程执行任务时,线程会转换到等待状态。仅当另一个线程向等待线程发出继续执行的信号时,线程才会转换回可运行状态。

  • 定时等待- 可运行线程可以在指定的时间间隔内进入定时等待状态。当该时间间隔到期或它正在等待的事件发生时,处于此状态的线程将转换回可运行状态。

  • 终止(死亡) - 可运行线程在完成其任务或以其他方式终止时进入终止状态。

线程优先级

每个 Java 线程都有一个优先级,可以帮助操作系统确定线程的调度顺序。

Java 线程优先级的范围是 MIN_PRIORITY(常数为 1)和 MAX_PRIORITY(常数为 10)之间。默认情况下,每个线程都被赋予优先级 NORM_PRIORITY(常数为 5)。

具有较高优先级的线程对于程序来说更重要,并且应该在较低优先级的线程之前分配处理器时间。然而,线程优先级不能保证线程执行的顺序,并且很大程度上依赖于平台。

通过实现可运行接口创建线程

如果您的类打算作为线程执行,那么您可以通过实现Runnable接口来实现这一点。您需要遵循三个基本步骤 -

步骤1

第一步,您需要实现Runnable接口提供的 run() 方法。该方法为线程提供了一个入口点,您将把完整的业务逻辑放入该方法中。以下是 run() 方法的简单语法 -

public void run( )

第2步

第二步,您将使用以下构造函数实例化Thread对象 -

Thread(Runnable threadObj, String threadName);

其中,threadObj是实现Runnable接口的类的实例,threadName是赋予新线程的名称。

步骤3

一旦创建了 Thread 对象,就可以通过调用start()方法来启动它,该方法会调用 run() 方法。以下是 start() 方法的简单语法 -

void start();

例子

这是一个创建新线程并开始运行它的示例 -

现场演示
class RunnableDemo implements Runnable {
   private Thread t;
   private String threadName;

   RunnableDemo(String name) {
      threadName = name;
      System.out.println("Creating " +  threadName );
   }
   
   public void run() {
      System.out.println("Running " +  threadName );
      
      try {
      
         for(int i = 4; i > 0; i--) {
            System.out.println("Thread: " + threadName + ", " + i);
            
            // Let the thread sleep for a while.
            Thread.sleep(50);
         }
      } catch (InterruptedException e) {
         System.out.println("Thread " +  threadName + " interrupted.");
      }
      System.out.println("Thread " +  threadName + " exiting.");
   }
   
   public void start () {
      System.out.println("Starting " +  threadName );
      
      if (t == null) {
         t = new Thread (this, threadName);
         t.start ();
      }
   }
}

public class TestThread {

   public static void main(String args[]) {
      RunnableDemo R1 = new RunnableDemo("Thread-1");
      R1.start();
      
      RunnableDemo R2 = new RunnableDemo("Thread-2");
      R2.start();
   }   
}

这将产生以下结果 -

输出

Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 4
Running Thread-2
Thread: Thread-2, 4
Thread: Thread-1, 3
Thread: Thread-2, 3
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.

通过扩展线程类创建线程

创建线程的第二种方法是使用以下两个简单步骤创建一个扩展Thread类的新类。这种方法在处理使用 Thread 类中的可用方法创建的多个线程时提供了更大的灵活性。

步骤1

您将需要重写Thread 类中可用的run()方法。该方法为线程提供了一个入口点,您将把完整的业务逻辑放入该方法中。以下是 run() 方法的简单语法 -

public void run( )

第2步

一旦创建了Thread对象,就可以通过调用start()方法来启动它,该方法会调用run()方法。以下是 start() 方法的简单语法 -

void start( );

例子

这是前面的程序重写以扩展线程 -

现场演示
class ThreadDemo extends Thread {
   private Thread t;
   private String threadName;
   
   ThreadDemo(String name) {
      threadName = name;
      System.out.println("Creating " +  threadName );
   }
   
   public void run() {
      System.out.println("Running " +  threadName );
      
      try {

         for(int i = 4; i > 0; i--) {
            System.out.println("Thread: " + threadName + ", " + i);
            
            // Let the thread sleep for a while.
            Thread.sleep(50);
         }
      } catch (InterruptedException e) {
         System.out.println("Thread " +  threadName + " interrupted.");
      }
      System.out.println("Thread " +  threadName + " exiting.");
   }
   
   public void start () {
      System.out.println("Starting " +  threadName );
      
      if (t == null) {
         t = new Thread (this, threadName);
         t.start ();
      }
   }
}

public class TestThread {

   public static void main(String args[]) {
      ThreadDemo T1 = new ThreadDemo("Thread-1");
      T1.start();
      
      ThreadDemo T2 = new ThreadDemo("Thread-2");
      T2.start();
   }   
}

这将产生以下结果 -

输出

Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 4
Running Thread-2
Thread: Thread-2, 4
Thread: Thread-1, 3
Thread: Thread-2, 3
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.

Java 并发 - 环境设置

在本章中,我们将讨论建立合适的 Java 环境的各个方面。

本地环境设置

如果您仍然愿意为 Java 编程语言设置环境,那么本节将指导您如何在计算机上下载和设置 Java。以下是设置环境的步骤。

Java SE 可以通过下载 Java链接免费获得。您可以根据您的操作系统下载版本。

按照说明下载 Java 并运行 .exe在您的计算机上安装 Java。在计算机上安装 Java 后,您将需要设置环境变量以指向正确的安装目录 -

设置 Windows 的路径

假设您已将 Java 安装在c:\Program Files\java\jdk目录中 -

  • 右键单击“我的电脑”并选择“属性”。

  • 单击“高级”选项卡下的“环境变量”按钮。

  • 现在,更改“Path”变量,使其也包含 Java 可执行文件的路径。例如,如果路径当前设置为“C:\WINDOWS\SYSTEM32”,则将路径更改为“C:\WINDOWS\SYSTEM32;c:\Program Files\java\jdk\bin”。

设置 Linux、UNIX、Solaris、FreeBSD 的路径

应将环境变量 PATH 设置为指向 Java 二进制文件的安装位置。如果您在执行此操作时遇到问题,请参阅您的 shell 文档。

例如,如果您使用bash作为 shell,那么您可以将以下行添加到 '.bashrc:export PATH = /path/to/java:$PATH' 的末尾

流行的 Java 编辑器

要编写 Java 程序,您需要一个文本编辑器。市场上还有更复杂的 IDE。但现在,您可以考虑以下其中一项 -

  • 记事本- 在 Windows 计算机上,您可以使用任何简单的文本编辑器,例如记事本(本教程推荐)、TextPad。

  • Netbeans - 开源且免费的 Java IDE,可以从https://netbeans.org/index.html下载。

  • Eclipse - 由 eclipse 开源社区开发的 Java IDE,可以从https://www.eclipse.org/下载。

Java并发-主要操作

Core Java 提供了对多线程程序的完全控制。您可以开发一个多线程程序,该程序可以根据您的要求暂停、恢复或完全停止。您可以在线程对象上使用各种静态方法来控制它们的Behave。下表列出了这些方法 -

先生。 方法及说明
1

公共无效暂停()

此方法将线程置于挂起状态,并且可以使用resume() 方法恢复。

2

公共无效停止()

此方法完全停止线程。

3

公共无效简历()

此方法恢复使用 suspend() 方法挂起的线程。

4

公共无效等待()

导致当前线程等待,直到另一个线程调用notify()。

5

公共无效通知()

唤醒正在该对象的监视器上等待的单个线程。

请注意,最新版本的 Java 已弃用 suspend( )、resume( ) 和 stop( ) 方法,因此您需要使用可用的替代方法。

例子

现场演示
class RunnableDemo implements Runnable {
   public Thread t;
   private String threadName;
   boolean suspended = false;

   RunnableDemo(String name) {
      threadName = name;
      System.out.println("Creating " +  threadName );
   }
   
   public void run() {
      System.out.println("Running " +  threadName );

      try {
         
         for(int i = 10; i > 0; i--) {
            System.out.println("Thread: " + threadName + ", " + i);

            // Let the thread sleep for a while.
            Thread.sleep(300);

            synchronized(this) {
               
               while(suspended) {
                  wait();
               }
            }
         }
      } catch (InterruptedException e) {
         System.out.println("Thread " +  threadName + " interrupted.");
      }
      System.out.println("Thread " +  threadName + " exiting.");
   }

   public void start () {
      System.out.println("Starting " +  threadName );
      
      if (t == null) {
         t = new Thread (this, threadName);
         t.start ();
      }
   }
   
   void suspend() {
      suspended = true;
   }
   
   synchronized void resume() {
      suspended = false;
      notify();
   }
}

public class TestThread {

   public static void main(String args[]) {
      RunnableDemo R1 = new RunnableDemo("Thread-1");
      R1.start();

      RunnableDemo R2 = new RunnableDemo("Thread-2");
      R2.start();

      try {
         Thread.sleep(1000);
         R1.suspend();
         System.out.println("Suspending First Thread");
         Thread.sleep(1000);
         R1.resume();
         System.out.println("Resuming First Thread");
         
         R2.suspend();
         System.out.println("Suspending thread Two");
         Thread.sleep(1000);
         R2.resume();
         System.out.println("Resuming thread Two");
      } catch (InterruptedException e) {
         System.out.println("Main thread Interrupted");
      } try {
         System.out.println("Waiting for threads to finish.");
         R1.t.join();
         R2.t.join();
      } catch (InterruptedException e) {
         System.out.println("Main thread Interrupted");
      }
      System.out.println("Main thread exiting.");
   }
}

上面的程序产生以下输出 -

输出

Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 10
Running Thread-2
Thread: Thread-2, 10
Thread: Thread-1, 9
Thread: Thread-2, 9
Thread: Thread-1, 8
Thread: Thread-2, 8
Thread: Thread-1, 7
Thread: Thread-2, 7
Suspending First Thread
Thread: Thread-2, 6
Thread: Thread-2, 5
Thread: Thread-2, 4
Resuming First Thread
Suspending thread Two
Thread: Thread-1, 6
Thread: Thread-1, 5
Thread: Thread-1, 4
Thread: Thread-1, 3
Resuming thread Two
Thread: Thread-2, 3
Waiting for threads to finish.
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.
Main thread exiting.

线程间通信

如果您了解进程间通信,那么您将很容易理解线程间通信。当您开发两个或多个线程交换某些信息的应用程序时,线程间通信非常重要。

三个简单的方法和一个小技巧可以使线程通信成为可能。下面列出了所有三种方法 -

先生。 方法及说明
1

公共无效等待()

导致当前线程等待,直到另一个线程调用notify()。

2

公共无效通知()

唤醒正在该对象的监视器上等待的单个线程。

3

公共无效notifyAll()

唤醒对同一对象调用 wait() 的所有线程。

这些方法已在 Object 中作为最终方法实现,因此它们在所有类中都可用。所有这三个方法只能从同步上下文中调用。

例子

此示例显示两个线程如何使用wait()notify()方法进行通信。您可以使用相同的概念创建一个复杂的系统。

现场演示
class Chat {
   boolean flag = false;

   public synchronized void Question(String msg) {

      if (flag) {
         
         try {
            wait();
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
      System.out.println(msg);
      flag = true;
      notify();
   }

   public synchronized void Answer(String msg) {

      if (!flag) {
         
         try {
            wait();
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
      System.out.println(msg);
      flag = false;
      notify();
   }
}

class T1 implements Runnable {
   Chat m;
   String[] s1 = { "Hi", "How are you ?", "I am also doing fine!" };

   public T1(Chat m1) {
      this.m = m1;
      new Thread(this, "Question").start();
   }

   public void run() {
   
      for (int i = 0; i < s1.length; i++) {
         m.Question(s1[i]);
      }
   }
}

class T2 implements Runnable {
   Chat m;
   String[] s2 = { "Hi", "I am good, what about you?", "Great!" };

   public T2(Chat m2) {
      this.m = m2;
      new Thread(this, "Answer").start();
   }

   public void run() {

      for (int i = 0; i < s2.length; i++) {
         m.Answer(s2[i]);
      }
   }
}

public class TestThread {

   public static void main(String[] args) {
      Chat m = new Chat();
      new T1(m);
      new T2(m);
   }
}

当上面的程序被编译并执行时,它会产生以下结果 -

输出

Hi
Hi
How are you ?
I am good, what about you?
I am also doing fine!
Great!

上面的例子是从[https://stackoverflow.com/questions/2170520/inter-thread-communication-in-java]中获取和修改的

Java 并发 - 同步

带有同步的多线程示例

这是按顺序打印计数器值的同一示例,每次运行它时,它都会产生相同的结果。

例子

现场演示
class PrintDemo {
   
   public void printCount() {
      
      try {
         
         for(int i = 5; i > 0; i--) {
            System.out.println("Counter   ---   "  + i );
         }
      } catch (Exception e) {
         System.out.println("Thread  interrupted.");
      }
   }
}

class ThreadDemo extends Thread {
   private Thread t;
   private String threadName;
   PrintDemo  PD;

   ThreadDemo(String name,  PrintDemo pd) {
      threadName = name;
      PD = pd;
   }
   
   public void run() {
      
      synchronized(PD) {
         PD.printCount();
      }
      System.out.println("Thread " +  threadName + " exiting.");
   }

   public void start () {
      System.out.println("Starting " +  threadName );
      
      if (t == null) {
         t = new Thread (this, threadName);
         t.start ();
      }
   }
}

public class TestThread {

   public static void main(String args[]) {
      PrintDemo PD = new PrintDemo();

      ThreadDemo T1 = new ThreadDemo("Thread - 1 ", PD);
      ThreadDemo T2 = new ThreadDemo("Thread - 2 ", PD);

      T1.start();
      T2.start();

      // wait for threads to end
      try {
         T1.join();
         T2.join();
      } catch (Exception e) {
         System.out.println("Interrupted");
      }
   }
}

每次运行该程序时都会产生相同的结果 -

输出

Starting Thread - 1
Starting Thread - 2
Counter   ---   5
Counter   ---   4
Counter   ---   3
Counter   ---   2
Counter   ---   1
Thread Thread - 1  exiting.
Counter   ---   5
Counter   ---   4
Counter   ---   3
Counter   ---   2
Counter   ---   1
Thread Thread - 2  exiting.

Java并发-死锁

死锁描述了两个或多个线程永远阻塞、互相等待的情况。当多个线程需要相同的锁但以不同的顺序获取锁时,就会发生死锁。Java 多线程程序可能会遇到死锁情况,因为同步关键字会导致执行线程在等待与指定对象关联的锁或监视器时阻塞。这是一个例子。

例子

现场演示
public class TestThread {
   public static Object Lock1 = new Object();
   public static Object Lock2 = new Object();
   
   public static void main(String args[]) {
      ThreadDemo1 T1 = new ThreadDemo1();
      ThreadDemo2 T2 = new ThreadDemo2();
      T1.start();
      T2.start();
   }
   
   private static class ThreadDemo1 extends Thread {
   
      public void run() {
      
         synchronized (Lock1) {
            System.out.println("Thread 1: Holding lock 1...");

            try {
               Thread.sleep(10);
            } catch (InterruptedException e) {}
            System.out.println("Thread 1: Waiting for lock 2...");

            synchronized (Lock2) {
               System.out.println("Thread 1: Holding lock 1 & 2...");
            }
         }
      }
   }

   private static class ThreadDemo2 extends Thread {
   
      public void run() {
      
         synchronized (Lock2) {
            System.out.println("Thread 2: Holding lock 2...");
            
            try {
               Thread.sleep(10);
            } catch (InterruptedException e) {}
            System.out.println("Thread 2: Waiting for lock 1...");
            
            synchronized (Lock1) {
               System.out.println("Thread 2: Holding lock 1 & 2...");
            }
         }
      }
   } 
}

当您编译并执行上述程序时,您会发现死锁情况,以下是程序产生的输出 -

输出

Thread 1: Holding lock 1...
Thread 2: Holding lock 2...
Thread 1: Waiting for lock 2...
Thread 2: Waiting for lock 1...

上面的程序将永远挂起,因为两个线程都没有到位并等待彼此释放锁,因此您可以通过按 CTRL+C 退出程序。

死锁解决方案示例

让我们更改同一程序的锁定和运行顺序,看看两个线程是否仍在互相等待 -

例子

现场演示
public class TestThread {
   public static Object Lock1 = new Object();
   public static Object Lock2 = new Object();
   
   public static void main(String args[]) {
      ThreadDemo1 T1 = new ThreadDemo1();
      ThreadDemo2 T2 = new ThreadDemo2();
      T1.start();
      T2.start();
   }
   
   private static class ThreadDemo1 extends Thread {
   
      public void run() {
         
         synchronized (Lock1) {
            System.out.println("Thread 1: Holding lock 1...");
            
            try {
               Thread.sleep(10);
            } catch (InterruptedException e) {}
            System.out.println("Thread 1: Waiting for lock 2...");

            synchronized (Lock2) {
               System.out.println("Thread 1: Holding lock 1 & 2...");
            }
         }
      }
   }

   private static class ThreadDemo2 extends Thread {
      
      public void run() {
         
         synchronized (Lock1) {
            System.out.println("Thread 2: Holding lock 1...");
           
            try {
               Thread.sleep(10);
            } catch (InterruptedException e) {}
            System.out.println("Thread 2: Waiting for lock 2...");

            synchronized (Lock2) {
               System.out.println("Thread 2: Holding lock 1 & 2...");
            }
         }
      }
   } 
}

因此,只需更改锁的顺序即可防止程序陷入死锁情况,并以以下结果完成 -

输出

Thread 1: Holding lock 1...
Thread 1: Waiting for lock 2...
Thread 1: Holding lock 1 & 2...
Thread 2: Holding lock 1...
Thread 2: Waiting for lock 2...
Thread 2: Holding lock 1 & 2...

上面的示例只是为了阐明概念,但是,这是一个复杂的概念,在开发应用程序来处理死锁情况之前,您应该深入研究它。

Java并发-ThreadLocal类

ThreadLocal类用于创建只能由同一线程读写的线程局部变量。例如,如果两个线程正在访问引用相同 threadLocal 变量的代码,则每个线程将看不到其他线程对 threadLocal 变量所做的任何修改。

线程局部方法

以下是 ThreadLocal 类中可用的重要方法的列表。

先生。 方法及说明
1

公共 T get()

返回此线程局部变量的当前线程副本中的值。

2

受保护的 T 初始值()

返回此线程局部变量的当前线程的“初始值”。

3

公共无效删除()

删除此线程局部变量的当前线程值。

4

公共无效集(T值)

将此线程局部变量的当前线程副本设置为指定值。

例子

下面的 TestThread 程序演示了 ThreadLocal 类的一些方法。这里我们使用了两个计数器变量,一个是普通变量,另一个是ThreadLocal。

现场演示
class RunnableDemo implements Runnable {
   int counter;
   ThreadLocal<Integer> threadLocalCounter = new ThreadLocal<Integer>();

   public void run() {     
      counter++;

      if(threadLocalCounter.get() != null) {
         threadLocalCounter.set(threadLocalCounter.get().intValue() + 1);
      } else {
         threadLocalCounter.set(0);
      }
      System.out.println("Counter: " + counter);
      System.out.println("threadLocalCounter: " + threadLocalCounter.get());
   }
}

public class TestThread {

   public static void main(String args[]) {
      RunnableDemo commonInstance = new RunnableDemo();

      Thread t1 = new Thread(commonInstance);
      Thread t2 = new Thread(commonInstance);
      Thread t3 = new Thread(commonInstance);
      Thread t4 = new Thread(commonInstance);

      t1.start();
      t2.start();
      t3.start();
      t4.start();

      // wait for threads to end
      try {
         t1.join();
         t2.join();
         t3.join();
         t4.join();
      } catch (Exception e) {
         System.out.println("Interrupted");
      }
   }
}

这将产生以下结果。

输出

Counter: 1
threadLocalCounter: 0
Counter: 2
threadLocalCounter: 0
Counter: 3
threadLocalCounter: 0
Counter: 4
threadLocalCounter: 0

您可以看到每个线程的 counter 值都会增加,但每个线程的 threadLocalCounter 保持为 0。

ThreadLocalRandom 类

java.util.concurrent.ThreadLocalRandom 是从 jdk 1.7 开始引入的实用程序类,当需要多个线程或 ForkJoinTasks 来生成随机数时非常有用。与 Math.random() 方法相比,它提高了性能并减少了争用。

ThreadLocalRandom 方法

以下是 ThreadLocalRandom 类中可用的重要方法的列表。

先生。 方法及说明
1

公共静态 ThreadLocalRandom current()

返回当前线程的 ThreadLocalRandom。

2

受保护的 int next(int 位)

生成下一个伪随机数。

3

公共双nextDouble(双n)

返回 0(含)和指定值(不含)之间的伪随机、均匀分布的双精度值。

4

public double nextDouble(双最小,双界限)

返回给定最小值(包含)和界限(不包含)之间均匀分布的伪随机值。

5

public int nextInt(int 至少,int 边界)

返回给定最小值(包含)和界限(不包含)之间均匀分布的伪随机值。

6

公共长nextLong(长n)

返回 0(含)和指定值(不含)之间均匀分布的伪随机值。

7

public long nextLong(长最小,长界限)

返回给定最小值(包含)和界限(不包含)之间均匀分布的伪随机值。

8

公共无效setSeed(长种子)

抛出 UnsupportedOperationException。

例子

下面的 TestThread 程序演示了 Lock 接口的一些方法。这里我们使用lock()来获取锁,使用unlock()来释放锁。

现场演示
import java.util.Random;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.ThreadLocalRandom;

public class TestThread {
  
   public static void main(final String[] arguments) {
      System.out.println("Random Integer: " + new Random().nextInt());  
      System.out.println("Seeded Random Integer: " + new Random(15).nextInt());  
      System.out.println(
         "Thread Local Random Integer: " + ThreadLocalRandom.current().nextInt());
      
      final ThreadLocalRandom random = ThreadLocalRandom.current();  
      random.setSeed(15); //exception will come as seeding is not allowed in ThreadLocalRandom.
      System.out.println("Seeded Thread Local Random Integer: " + random.nextInt());  
   }
}

这将产生以下结果。

输出

Random Integer: 1566889198
Seeded Random Integer: -1159716814
Thread Local Random Integer: 358693993
Exception in thread "main" java.lang.UnsupportedOperationException
        at java.util.concurrent.ThreadLocalRandom.setSeed(Unknown Source)
        at TestThread.main(TestThread.java:21)

这里我们使用 ThreadLocalRandom 和 Random 类来获取随机数。

Java并发-锁接口

java.util.concurrent.locks.Lock 接口用作类似于同步块的线程同步机制。新的锁定机制比同步块更灵活,并提供更多选项。锁和同步块之间的主要区别如下:

  • 顺序保证- 同步块不提供任何等待线程访问顺序的保证。锁接口处理它。

  • 无超时- 如果未授予锁定,则同步块没有超时选项。锁定界面提供了这样的选项。

  • 单一方法- 同步块必须完全包含在单一方法中,而锁接口的方法lock()和unlock()可以在不同的方法中调用。

锁定方法

以下是 Lock 类中可用的重要方法的列表。

先生。 方法及说明
1

公共无效锁()

获取锁。

2

公共无效锁可中断()

除非当前线程被中断,否则获取锁。

3

公共条件 newCondition()

返回绑定到此 Lock 实例的新 Condition 实例。

4

公共布尔tryLock()

仅当调用时锁空闲时才获取锁。

5

public boolean tryLock(长时间, TimeUnit 单位)

如果在给定的等待时间内锁是空闲的并且当前线程没有被中断,则获取锁。

6

公共无效解锁()

释放锁。

例子

下面的 TestThread 程序演示了 Lock 接口的一些方法。这里我们使用lock()来获取锁,使用unlock()来释放锁。

现场演示
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class PrintDemo {
   private final Lock queueLock = new ReentrantLock();

   public void print() {
      queueLock.lock();

      try {
         Long duration = (long) (Math.random() * 10000);
         System.out.println(Thread.currentThread().getName() 
            + "  Time Taken " + (duration / 1000) + " seconds.");
         Thread.sleep(duration);
      } catch (InterruptedException e) {
         e.printStackTrace();
      } finally {
         System.out.printf(
            "%s printed the document successfully.\n", Thread.currentThread().getName());
         queueLock.unlock();
      }
   }
}

class ThreadDemo extends Thread {
   PrintDemo  printDemo;

   ThreadDemo(String name,  PrintDemo printDemo) {
      super(name);
      this.printDemo = printDemo;
   }   

   @Override
   public void run() {
      System.out.printf(
         "%s starts printing a document\n", Thread.currentThread().getName());
      printDemo.print();
   }
}

public class TestThread {

   public static void main(String args[]) {
      PrintDemo PD = new PrintDemo();

      ThreadDemo t1 = new ThreadDemo("Thread - 1 ", PD);
      ThreadDemo t2 = new ThreadDemo("Thread - 2 ", PD);
      ThreadDemo t3 = new ThreadDemo("Thread - 3 ", PD);
      ThreadDemo t4 = new ThreadDemo("Thread - 4 ", PD);

      t1.start();
      t2.start();
      t3.start();
      t4.start();
   }
}

这将产生以下结果。

输出

Thread - 1  starts printing a document
Thread - 4  starts printing a document
Thread - 3  starts printing a document
Thread - 2  starts printing a document
Thread - 1   Time Taken 4 seconds.
Thread - 1  printed the document successfully.
Thread - 4   Time Taken 3 seconds.
Thread - 4  printed the document successfully.
Thread - 3   Time Taken 5 seconds.
Thread - 3  printed the document successfully.
Thread - 2   Time Taken 4 seconds.
Thread - 2  printed the document successfully.

我们在这里使用 ReentrantLock 类作为 Lock 接口的实现。ReentrantLock 类允许线程锁定一个方法,即使它已经拥有其他方法的锁定。

Java 并发 - ReadWriteLock 接口

java.util.concurrent.locks.ReadWriteLock 接口允许多个线程同时读取,但一次只有一个线程可以写入。

  • 读锁- 如果没有线程锁定 ReadWriteLock 进行写入,则多个线程可以访问读锁。

  • 写锁- 如果没有线程正在读取或写入,则一个线程可以访问写锁。

锁定方法

以下是 Lock 类中可用的重要方法的列表。

先生。 方法及说明
1

公共锁 readLock()

返回用于读取的锁。

2

公共锁 writeLock()

返回用于写入的锁。

例子

以下 TestThread 程序演示了 ReadWriteLock 接口的这些方法。这里我们使用 readlock() 来获取读锁,使用 writeLock() 来获取写锁。

现场演示
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class TestThread {
   private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
   private static String message = "a";

   public static void main(String[] args) throws InterruptedException {
      Thread t1 = new Thread(new WriterA());
      t1.setName("Writer A");
      
      Thread t2 = new Thread(new WriterB());
      t2.setName("Writer B");
      
      Thread t3 = new Thread(new Reader());
      t3.setName("Reader");
      t1.start();
      t2.start();
      t3.start();
      t1.join();
      t2.join();
      t3.join();
   }

   static class Reader implements Runnable {

      public void run() {
         
         if(lock.isWriteLocked()) {
            System.out.println("Write Lock Present.");
         }
         lock.readLock().lock();

         try {
            Long duration = (long) (Math.random() * 10000);
            System.out.println(Thread.currentThread().getName() 
               + "  Time Taken " + (duration / 1000) + " seconds.");
            Thread.sleep(duration);
         } catch (InterruptedException e) {
            e.printStackTrace();
         } finally {
            System.out.println(Thread.currentThread().getName() +": "+ message );
            lock.readLock().unlock();
         }
      }
   }

   static class WriterA implements Runnable {

      public void run() {
         lock.writeLock().lock();
         
         try {
            Long duration = (long) (Math.random() * 10000);
            System.out.println(Thread.currentThread().getName() 
               + "  Time Taken " + (duration / 1000) + " seconds.");
            Thread.sleep(duration);
         } catch (InterruptedException e) {
            e.printStackTrace();
         } finally {
            message = message.concat("a");
            lock.writeLock().unlock();
         }
      }
   }

   static class WriterB implements Runnable {

      public void run() {
         lock.writeLock().lock();
         
         try {
            Long duration = (long) (Math.random() * 10000);
            System.out.println(Thread.currentThread().getName() 
               + "  Time Taken " + (duration / 1000) + " seconds.");
            Thread.sleep(duration);
         } catch (InterruptedException e) {
            e.printStackTrace();
         } finally {
            message = message.concat("b");
            lock.writeLock().unlock();
         }
      }
   }
}

这将产生以下结果。

输出

Writer A  Time Taken 6 seconds.
Write Lock Present.
Writer B  Time Taken 2 seconds.
Reader  Time Taken 0 seconds.
Reader: aab

Java 并发 - 条件接口

java.util.concurrent.locks.Condition 接口提供了线程暂停其执行的能力,直到给定的条件为真。Condition 对象必须绑定到 Lock 并使用 newCondition() 方法获取。

条件方法

以下是 Condition 类中可用的重要方法的列表。

先生。 方法及说明
1

公共无效等待()

导致当前线程等待,直到收到信号或中断。

2

public boolean wait(long time, TimeUnit 单位)

使当前线程等待,直到收到信号或中断,或者指定的等待时间过去。

3

公共长等待Nanos(长nanosTimeout)

使当前线程等待,直到收到信号或中断,或者指定的等待时间过去。

4

公共长等待不间断()

导致当前线程等待,直到收到信号。

5

公共长等待直到()

导致当前线程等待,直到收到信号或中断,或者指定的截止时间过去。

6

公共无效信号()

唤醒一个等待线程。

7

公共无效信号全部()

唤醒所有等待线程。

例子

下面的 TestThread 程序演示了 Condition 接口的这些方法。这里我们使用 signal() 来通知并使用 wait() 来挂起线程。

现场演示
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestThread {

   public static void main(String[] args) throws InterruptedException {
      ItemQueue itemQueue = new ItemQueue(10);

      //Create a producer and a consumer.
      Thread producer = new Producer(itemQueue);
      Thread consumer = new Consumer(itemQueue);

      //Start both threads.
      producer.start();
      consumer.start();

      //Wait for both threads to terminate.
      producer.join();
      consumer.join();
   }

   static class ItemQueue {
      private Object[] items = null;
      private int current = 0;
      private int placeIndex = 0;
      private int removeIndex = 0;

      private final Lock lock;
      private final Condition isEmpty;
      private final Condition isFull;

      public ItemQueue(int capacity) {
         this.items = new Object[capacity];
         lock = new ReentrantLock();
         isEmpty = lock.newCondition();
         isFull = lock.newCondition();
      }

      public void add(Object item) throws InterruptedException {
         lock.lock();

         while(current >= items.length)
            isFull.await();

         items[placeIndex] = item;
         placeIndex = (placeIndex + 1) % items.length;
         ++current;

         //Notify the consumer that there is data available.
         isEmpty.signal();
         lock.unlock();
      }

      public Object remove() throws InterruptedException {
         Object item = null;

         lock.lock();

         while(current <= 0) {
            isEmpty.await();
         }
         item = items[removeIndex];
         removeIndex = (removeIndex + 1) % items.length;
         --current;

         //Notify the producer that there is space available.
         isFull.signal();
         lock.unlock();

         return item;
      }

      public boolean isEmpty() {
         return (items.length == 0);
      }
   }

   static class Producer extends Thread {
      private final ItemQueue queue;
      
      public Producer(ItemQueue queue) {
         this.queue = queue;
      }

      @Override
      public void run() {
         String[] numbers =
            {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"};

         try {
            
            for(String number: numbers) {
               System.out.println("[Producer]: " + number);
            }
            queue.add(null);
         } catch (InterruptedException ex) {
            ex.printStackTrace();
         } 
      }
   }

   static class Consumer extends Thread {
      private final ItemQueue queue;
      
      public Consumer(ItemQueue queue) {
         this.queue = queue;
      }

      @Override
      public void run() {
         
         try {
            
            do {
               Object number = queue.remove();
               System.out.println("[Consumer]: " + number);

               if(number == null) {
                  return;
               }
            } while(!queue.isEmpty());
         } catch (InterruptedException ex) {
            ex.printStackTrace();
         }
      }
   }
}

这将产生以下结果。

输出

[Producer]: 1
[Producer]: 2
[Producer]: 3
[Producer]: 4
[Producer]: 5
[Producer]: 6
[Producer]: 7
[Producer]: 8
[Producer]: 9
[Producer]: 10
[Producer]: 11
[Producer]: 12
[Consumer]: null

Java 并发 - AtomicInteger 类

java.util.concurrent.atomic.AtomicInteger 类提供对底层 int 值的操作,这些操作可以Atomics方式读写,并且还包含高级Atomics操作。AtomicInteger 支持对底层 int 变量进行Atomics操作。它具有 get 和 set 方法,其工作方式类似于对易失性变量进行读取和写入。也就是说,集合与同一变量上的任何后续获取具有先发生关系。AtomicscompareAndSet方法也具有这些内存一致性特征。

Atomics整数方法

以下是 AtomicInteger 类中可用的重要方法的列表。

先生。 方法及说明
1

公共 int addAndGet(int delta)

以Atomics方式将给定值添加到当前值。

2

公共布尔比较AndSet(int期望,int更新)

如果当前值与预期值相同,则自动将该值设置为给定的更新值。

3

公共 int decrementAndGet()

以Atomics方式将当前值减一。

4

公共双倍双值()

以双精度形式返回指定数字的值。

5

公共浮点数 floatValue()

以浮点数形式返回指定数字的值。

6

公共 int get()

获取当前值。

7

公共 int getAndAdd(int delta)

以Atomics方式将给定值添加到当前值。

8

公共 int getAndDecrement()

以Atomics方式将当前值减一。

9

公共 int getAndIncrement()

以Atomics方式将当前值加一。

10

公共 int getAndSet(int newValue)

Atomics地设置为给定值并返回旧值。

11

公共 int 增量AndGet()

以Atomics方式将当前值加一。

12

公共 int intValue()

以 int 形式返回指定数字的值。

13

公共无效lazySet(int newValue)

最终设置为给定值。

14

公共长长值()

以 long 形式返回指定数字的值。

15

公共无效集(int newValue)

设置为给定值。

16

公共字符串 toString()

返回当前值的字符串表示形式。

17 号

公共布尔weakCompareAndSet(int期望,int更新)

如果当前值与预期值相同,则自动将该值设置为给定的更新值。

例子

以下 TestThread 程序显示了基于线程的环境中计数器的不安全实现。

现场演示
public class TestThread {

   static class Counter {
      private int c = 0;

      public void increment() {
         c++;
      }

      public int value() {
         return c;
      }
   }
   
   public static void main(final String[] arguments) throws InterruptedException {
      final Counter counter = new Counter();
      
      //1000 threads
      for(int i = 0; i < 1000 ; i++) {
         
         new Thread(new Runnable() {
            
            public void run() {
               counter.increment();
            }
         }).start(); 
      }  
      Thread.sleep(6000);
      System.out.println("Final number (should be 1000): " + counter.value());
   }  
}

根据计算机的速度和线程交错,这可能会产生以下结果。

输出

Final number (should be 1000): 1000

例子

以下 TestThread 程序显示了在基于线程的环境中使用 AtomicInteger 的计数器的安全实现。

现场演示
import java.util.concurrent.atomic.AtomicInteger;

public class TestThread {

   static class Counter {
      private AtomicInteger c = new AtomicInteger(0);

      public void increment() {
         c.getAndIncrement();
      }

      public int value() {
         return c.get();
      }
   }
   
   public static void main(final String[] arguments) throws InterruptedException {
      final Counter counter = new Counter();
      
      //1000 threads
      for(int i = 0; i < 1000 ; i++) {

         new Thread(new Runnable() {
            public void run() {
               counter.increment();
            }
         }).start(); 
      }  
      Thread.sleep(6000);
      System.out.println("Final number (should be 1000): " + counter.value());
   }
}

这将产生以下结果。

输出

Final number (should be 1000): 1000

Java 并发 - AtomicLong 类

java.util.concurrent.atomic.AtomicLong 类提供对基础 long 值的操作,这些操作可以Atomics方式读取和写入,并且还包含高级Atomics操作。AtomicLong 支持对底层 long 变量进行Atomics操作。它具有 get 和 set 方法,其工作方式类似于对易失性变量进行读取和写入。也就是说,集合与同一变量上的任何后续获取具有先发生关系。AtomicscompareAndSet方法也具有这些内存一致性特征。

Atomics长方法

以下是 AtomicLong 类中可用的重要方法的列表。

先生。 方法及说明
1

公共长addAndGet(长增量)

以Atomics方式将给定值添加到当前值。

2

public boolean CompareAndSet(长期期待,长期更新)

如果当前值与预期值相同,则自动将该值设置为给定的更新值。

3

公共长减量AndGet()

以Atomics方式将当前值减一。

4

公共双倍双值()

以双精度形式返回指定数字的值。

5

公共浮点数 floatValue()

以浮点数形式返回指定数字的值。

6

公共长get()

获取当前值。

7

公共长 getAndAdd(长增量)

以Atomics方式将给定值添加到当前值。

8

公共长 getAndDecrement()

以Atomics方式将当前值减一。

9

公共长 getAndIncrement()

以Atomics方式将当前值加一。

10

公共长 getAndSet(长 newValue)

Atomics地设置为给定值并返回旧值。

11

公共长增量AndGet()

以Atomics方式将当前值加一。

12

公共 int intValue()

以 int 形式返回指定数字的值。

13

公共无效lazySet(长newValue)

最终设置为给定值。

14

公共长长值()

以 long 形式返回指定数字的值。

15

公共无效集(长新值)

设置为给定值。

16

公共字符串 toString()

返回当前值的字符串表示形式。

17 号

公共布尔weakCompareAndSet(长期期望,长期更新)

如果当前值与预期值相同,则自动将该值设置为给定的更新值。

例子

以下 TestThread 程序显示了在基于线程的环境中使用 AtomicLong 的计数器的安全实现。

现场演示
import java.util.concurrent.atomic.AtomicLong;

public class TestThread {

   static class Counter {
      private AtomicLong c = new AtomicLong(0);

      public void increment() {
         c.getAndIncrement();
      }

      public long value() {
         return c.get();
      }
   }

   public static void main(final String[] arguments) throws InterruptedException {
      final Counter counter = new Counter();
      
      //1000 threads
      for(int i = 0; i < 1000 ; i++) {
         
         new Thread(new Runnable() {
            
            public void run() {
               counter.increment();
            }
         }).start();	
      }
      Thread.sleep(6000);			   		  
      System.out.println("Final number (should be 1000): " + counter.value());
   }
}

这将产生以下结果。

输出

Final number (should be 1000): 1000

Java 并发 - AtomicBoolean 类

java.util.concurrent.atomic.AtomicBoolean 类提供对可Atomics读写的基础布尔值的操作,并且还包含高级Atomics操作。AtomicBoolean 支持对底层布尔变量进行Atomics操作。它具有 get 和 set 方法,其工作方式类似于对易失性变量进行读取和写入。也就是说,集合与同一变量上的任何后续获取具有先发生关系。AtomicscompareAndSet方法也具有这些内存一致性特征。

Atomics布尔方法

以下是 AtomicBoolean 类中可用的重要方法的列表。

先生。 方法及说明
1

公共布尔比较AndSet(布尔期望,布尔更新)

如果当前值 == 预期值,则自动将该值设置为给定的更新值。

2

公共布尔值 get()

返回当前值。

3

公共布尔 getAndSet(布尔 newValue)

Atomics地设置为给定值并返回先前的值。

4

公共无效lazySet(布尔新值)

最终设置为给定值。

5

公共无效集(布尔新值)

无条件设置为给定值。

6

公共字符串 toString()

返回当前值的字符串表示形式。

7

公共布尔weakCompareAndSet(布尔期望,布尔更新)

如果当前值 == 预期值,则自动将该值设置为给定的更新值。

例子

以下 TestThread 程序显示了 AtomicBoolean 变量在基于线程的环境中的用法。

现场演示
import java.util.concurrent.atomic.AtomicBoolean;

public class TestThread {

   public static void main(final String[] arguments) throws InterruptedException {
      final AtomicBoolean atomicBoolean = new AtomicBoolean(false);

      new Thread("Thread 1") {

         public void run() {

            while(true) {
               System.out.println(Thread.currentThread().getName() 
                  +" Waiting for Thread 2 to set Atomic variable to true. Current value is "
                  + atomicBoolean.get());

               if(atomicBoolean.compareAndSet(true, false)) {
                  System.out.println("Done!");
                  break;
               }
            }
         };
      }.start();

      new Thread("Thread 2") {

         public void run() {
            System.out.println(Thread.currentThread().getName() +
               ", Atomic Variable: " +atomicBoolean.get()); 
            System.out.println(Thread.currentThread().getName() +
               " is setting the variable to true ");
            atomicBoolean.set(true);
            System.out.println(Thread.currentThread().getName() +
               ", Atomic Variable: " +atomicBoolean.get()); 
         };
      }.start();
   }
}

这将产生以下结果。

输出

Thread 1 Waiting for Thread 2 to set Atomic variable to true. Current value is false
Thread 1 Waiting for Thread 2 to set Atomic variable to true. Current value is false
Thread 1 Waiting for Thread 2 to set Atomic variable to true. Current value is false
Thread 2, Atomic Variable: false
Thread 1 Waiting for Thread 2 to set Atomic variable to true. Current value is false
Thread 2 is setting the variable to true
Thread 2, Atomic Variable: true
Thread 1 Waiting for Thread 2 to set Atomic variable to true. Current value is false
Done!

Java 并发 - AtomicReference 类

java.util.concurrent.atomic.AtomicReference 类提供对可Atomics读写的底层对​​象引用的操作,并且还包含高级Atomics操作。AtomicReference 支持对底层对象引用变量进行Atomics操作。它具有 get 和 set 方法,其工作方式类似于对易失性变量进行读取和写入。也就是说,集合与同一变量上的任何后续获取具有先发生关系。AtomicscompareAndSet方法也具有这些内存一致性特征。

Atomics引用方法

以下是 AtomicReference 类中可用的重要方法的列表。

先生。 方法及说明
1

公共布尔compareAndSet(V期望,V更新)

如果当前值 == 预期值,则自动将该值设置为给定的更新值。

2

公共布尔值 get()

返回当前值。

3

公共布尔 getAndSet(V newValue)

Atomics地设置为给定值并返回先前的值。

4

公共无效lazySet(V newValue)

最终设置为给定值。

5

公共无效集(V newValue)

无条件设置为给定值。

6

公共字符串 toString()

返回当前值的字符串表示形式。

7

公共布尔weakCompareAndSet(V期望,V更新)

如果当前值 == 预期值,则自动将该值设置为给定的更新值。

例子

以下 TestThread 程序显示了 AtomicReference 变量在基于线程的环境中的用法。

现场演示
import java.util.concurrent.atomic.AtomicReference;

public class TestThread {
   private static