Read / Write Locks in Java---笔记

本文深入探讨了读写锁的基本概念及其实现原理,包括非可重入与可重入读写锁的不同应用场景,并通过具体代码示例展示了如何避免死锁。

链接:http://tutorials.jenkov.com/java-concurrency/read-write-locks.html

以下对链接中的代码加了一些注释,对canGrantReadAccess函数添加了注释。


java.util.concurrent包中提供了读写锁的实现。使用读写锁,同一时间中多个线程可以同时读同一资源,但是如果一个线程在写资源时,其他读和写线程都不能使用资源。虽说java api已经提供了实现,但是知道如何实现的还是很有用的。


读写锁的非可重入实现

先总结一下读写访问的条件:

读访问:如果没有线程写时,也没有写请求的线程,可以获得读访问

写访问:如果没有线程写和读。

如果读的线程很多时,写的线程可能会因为等待所有读线程而饿死。因此要使用增加请求变量,将读写锁设计成写优先读写锁。

public class ReadWriteLock{

  private int readers       = 0;
  private int writers       = 0;
  private int writeRequests = 0;

  public synchronized void lockRead() throws InterruptedException{
    while(writers > 0 || writeRequests > 0){//有写请求时,后续读者就不在获得锁,已获得锁的读者继续执行,因此实现了写优先的规则
      wait();
    }
    readers++;
  }

  public synchronized void unlockRead(){
    readers--;
    notifyAll();
  }

  public synchronized void lockWrite() throws InterruptedException{
    writeRequests++;

    while(readers > 0 || writers > 0){
      wait();
    }
    writeRequests--;
    writers++;
  }

  public synchronized void unlockWrite() throws InterruptedException{
    writers--;
    notifyAll();
  }
}

note:注意使用notifyAll而不是notify,参考链接中有详细讲解。

读写锁的可重入实现

上述代码是不可重入的,不可重入的代码很容易造成死锁。比如下面的例子:

  1. 线程1得到写访问
  2. 线程2请求写访问,由于有了读者,因此被阻塞
  3. 线程1重新请求读访问,但是有线程请求写访问,因此被阻塞

这种情况下就产生了死锁。因此实现可重入很必要,下面分别为每一部分实现可重入。

Read Reentrance

如果没有写者和读者,线程可以再次获得读访问,或者它已经获得了读访问。

public class ReadWriteLock{

  private Map<Thread, Integer> readingThreads =
      new HashMap<Thread, Integer>();

  private int writers        = 0;
  private int writeRequests  = 0;

  public synchronized void lockRead() throws InterruptedException{
    Thread callingThread = Thread.currentThread();
    while(! canGrantReadAccess(callingThread)){
      wait();                                                                   
    }

    readingThreads.put(callingThread,
       (getAccessCount(callingThread) + 1));
  }


  public synchronized void unlockRead(){
    Thread callingThread = Thread.currentThread();
    int accessCount = getAccessCount(callingThread);
    if(accessCount == 1){ readingThreads.remove(callingThread); }
    else { readingThreads.put(callingThread, (accessCount -1)); }
    notifyAll();
  }


  private boolean canGrantReadAccess(Thread callingThread){
    if(writers > 0)            return false;
    if(isReader(callingThread) return true;//当无写者使用资源,且当前线程为已获得访问权限的线程,则能够再次访问。
    if(writeRequests > 0)      return false;
    return true;//无写者占用,该线程之前也未获得访问权限,也没有写者请求访问。
  }

  private int getReadAccessCount(Thread callingThread){
    Integer accessCount = readingThreads.get(callingThread);
    if(accessCount == null) return 0;
    return accessCount.intValue();
  }

  private boolean isReader(Thread callingThread){
    return readingThreads.get(callingThread) != null;
  }

}

Write Reentrance

写重入被授予仅当线程已经获得了写访问。

public class ReadWriteLock{

    private Map<Thread, Integer> readingThreads =
        new HashMap<Thread, Integer>();

    private int writeAccesses    = 0;
    private int writeRequests    = 0;
    private Thread writingThread = null;

  public synchronized void lockWrite() throws InterruptedException{
    writeRequests++;
    Thread callingThread = Thread.currentThread();
    while(! canGrantWriteAccess(callingThread)){
      wait();
    }
    writeRequests--;
    writeAccesses++;
    writingThread = callingThread;
  }

  public synchronized void unlockWrite() throws InterruptedException{
    writeAccesses--;
    if(writeAccesses == 0){
      writingThread = null;
    }
    notifyAll();
  }

  private boolean canGrantWriteAccess(Thread callingThread){
    if(hasReaders())             return false;
    if(writingThread == null)    return true;//没有读者访问,也没有写者访问
    if(!isWriter(callingThread)) return false;
    return true;//没有读者访问,但是有写者访问,该线程就是拥有访问权限的写者,返回true
  }

  private boolean hasReaders(){
    return readingThreads.size() > 0;
  }

  private boolean isWriter(Thread callingThread){
    return writingThread == callingThread;
  }
}

Read to Write Reentrance

获得读访问的线程也可以获得写访问,仅当该线程是唯一的读线程。

public class ReadWriteLock{

    private Map<Thread, Integer> readingThreads =
        new HashMap<Thread, Integer>();

    private int writeAccesses    = 0;
    private int writeRequests    = 0;
    private Thread writingThread = null;

  public synchronized void lockWrite() throws InterruptedException{
    writeRequests++;
    Thread callingThread = Thread.currentThread();
    while(! canGrantWriteAccess(callingThread)){
      wait();
    }
    writeRequests--;
    writeAccesses++;
    writingThread = callingThread;
  }

  public synchronized void unlockWrite() throws InterruptedException{
    writeAccesses--;
    if(writeAccesses == 0){
      writingThread = null;
    }
    notifyAll();
  }

  private boolean canGrantWriteAccess(Thread callingThread){
    if(isOnlyReader(callingThread))    return true;//当仅有一个读者线程,且为该线程
    if(hasReaders())                   return false;
    if(writingThread == null)          return true;//当没有读者和写者线程
    if(!isWriter(callingThread))       return false;
    return true;//当为获得当前写权限的写者线程
  }

  private boolean hasReaders(){
    return readingThreads.size() > 0;
  }

  private boolean isWriter(Thread callingThread){
    return writingThread == callingThread;
  }

  private boolean isOnlyReader(Thread thread){
     return readingThreads.size() == 1 &&readingThreads.get(callingThread) != null;
  }
  
}

Write to Read Reentrance

一个获得了写访问的线程总是可以获得读访问的。

public class ReadWriteLock{
    ...
    private boolean canGrantReadAccess(Thread callingThread){
      if(isWriter(callingThread)) return true;//如果该线程是拥有写访问锁的线程,可以获得读锁
      if(writingThread != null)   return false;
      if(isReader(callingThread)  return true;
      if(writeRequests > 0)       return false;
      return true;
    }
    
    ...

}

Fully Reentrant ReadWriteLock

这是完整的程序。

public class ReadWriteLock{

  private Map<Thread, Integer> readingThreads =
       new HashMap<Thread, Integer>();

   private int writeAccesses    = 0;
   private int writeRequests    = 0;
   private Thread writingThread = null;


  public synchronized void lockRead() throws InterruptedException{
    Thread callingThread = Thread.currentThread();
    while(! canGrantReadAccess(callingThread)){
      wait();
    }

    readingThreads.put(callingThread,
     (getReadAccessCount(callingThread) + 1));
  }

  private boolean canGrantReadAccess(Thread callingThread){
    if( isWriter(callingThread) ) return true;
    if( hasWriter()             ) return false;
    if( isReader(callingThread) ) return true;
    if( hasWriteRequests()      ) return false;
    return true;
  }


  public synchronized void unlockRead(){
    Thread callingThread = Thread.currentThread();
    if(!isReader(callingThread)){
      throw new IllegalMonitorStateException("Calling Thread does not" +
        " hold a read lock on this ReadWriteLock");
    }
    int accessCount = getReadAccessCount(callingThread);
    if(accessCount == 1){ readingThreads.remove(callingThread); }
    else { readingThreads.put(callingThread, (accessCount -1)); }
    notifyAll();
  }

  public synchronized void lockWrite() throws InterruptedException{
    writeRequests++;
    Thread callingThread = Thread.currentThread();
    while(! canGrantWriteAccess(callingThread)){
      wait();
    }
    writeRequests--;
    writeAccesses++;
    writingThread = callingThread;
  }

  public synchronized void unlockWrite() throws InterruptedException{
    if(!isWriter(Thread.currentThread()){
      throw new IllegalMonitorStateException("Calling Thread does not" +
        " hold the write lock on this ReadWriteLock");
    }
    writeAccesses--;
    if(writeAccesses == 0){
      writingThread = null;
    }
    notifyAll();
  }

  private boolean canGrantWriteAccess(Thread callingThread){
    if(isOnlyReader(callingThread))    return true;
    if(hasReaders())                   return false;
    if(writingThread == null)          return true;
    if(!isWriter(callingThread))       return false;
    return true;
  }


  private int getReadAccessCount(Thread callingThread){
    Integer accessCount = readingThreads.get(callingThread);
    if(accessCount == null) return 0;
    return accessCount.intValue();
  }


  private boolean hasReaders(){
    return readingThreads.size() > 0;
  }

  private boolean isReader(Thread callingThread){
    return readingThreads.get(callingThread) != null;
  }

  private boolean isOnlyReader(Thread callingThread){
    return readingThreads.size() == 1 &&
           readingThreads.get(callingThread) != null;
  }

  private boolean hasWriter(){
    return writingThread != null;
  }

  private boolean isWriter(Thread callingThread){
    return writingThread == callingThread;
  }

  private boolean hasWriteRequests(){
      return this.writeRequests > 0;
  }

}

 

[root@yfw ~]# cd /opt/openfire [root@yfw openfire]# cd /tmp [root@yfw tmp]# wget https://downloads.apache.org/maven/maven-3/3.9.8/binaries/apache-maven-3.9.8-bin.tar.gz --2025-10-03 07:44:49-- https://downloads.apache.org/maven/maven-3/3.9.8/binaries/apache-maven-3.9.8-bin.tar.gz Resolving downloads.apache.org (downloads.apache.org)... 135.181.214.104, 88.99.208.237, 2a01:4f8:10a:39da::2, ... Connecting to downloads.apache.org (downloads.apache.org)|135.181.214.104|:443... connected. HTTP request sent, awaiting response... 404 Not Found 2025-10-03 07:44:50 ERROR 404: Not Found. [root@yfw tmp]# wget https://mirror.sjtu.edu.cn/apache/maven/maven-3/3.9.8/binaries/apache-maven-3.9.8-bin.tar.gz --2025-10-03 07:45:13-- https://mirror.sjtu.edu.cn/apache/maven/maven-3/3.9.8/binaries/apache-maven-3.9.8-bin.tar.gz Resolving mirror.sjtu.edu.cn (mirror.sjtu.edu.cn)... 111.186.58.212 Connecting to mirror.sjtu.edu.cn (mirror.sjtu.edu.cn)|111.186.58.212|:443... connected. HTTP request sent, awaiting response... 404 Not Found 2025-10-03 07:45:14 ERROR 404: Not Found. [root@yfw tmp]# wget https://mirrors.aliyun.com/apache/maven/maven-3/3.9.8/binaries/apache-maven-3.9.8-bin.tar.gz --2025-10-03 07:45:34-- https://mirrors.aliyun.com/apache/maven/maven-3/3.9.8/binaries/apache-maven-3.9.8-bin.tar.gz Resolving mirrors.aliyun.com (mirrors.aliyun.com)... 124.95.175.51, 124.95.175.56, 124.95.175.52, ... Connecting to mirrors.aliyun.com (mirrors.aliyun.com)|124.95.175.51|:443... connected. HTTP request sent, awaiting response... 404 Not Found 2025-10-03 07:45:34 ERROR 404: Not Found. [root@yfw tmp]# wget https://dlcdn.apache.org/maven/maven-3/3.9.11/binaries/apache-maven-3.9.11-bin.tar.gz --2025-10-03 07:47:12-- https://dlcdn.apache.org/maven/maven-3/3.9.11/binaries/apache-maven-3.9.11-bin.tar.gz Resolving dlcdn.apache.org (dlcdn.apache.org)... 151.101.2.132, 2a04:4e42::644 Connecting to dlcdn.apache.org (dlcdn.apache.org)|151.101.2.132|:443... failed: Connection timed out. Connecting to dlcdn.apache.org (dlcdn.apache.org)|2a04:4e42::644|:443... failed: Network is unreachable. [root@yfw tmp]# [root@yfw tmp]# [root@yfw tmp]# [root@yfw tmp]# [root@yfw tmp]# wget https://dlcdn.apache.org/maven/maven-3/3.9.11/binaries/apache-maven-3.9.11-bin.zip --2025-10-03 07:50:59-- https://dlcdn.apache.org/maven/maven-3/3.9.11/binaries/apache-maven-3.9.11-bin.zip Resolving dlcdn.apache.org (dlcdn.apache.org)... 151.101.2.132, 2a04:4e42::644 Connecting to dlcdn.apache.org (dlcdn.apache.org)|151.101.2.132|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 9278421 (8.8M) [application/zip] Saving to: 'apache-maven-3.9.11-bin.zip' apache-maven-3.9.11-bin.zi 100%[======================================>] 8.85M 20.2KB/s in 6m 47s 2025-10-03 07:57:47 (22.2 KB/s) - 'apache-maven-3.9.11-bin.zip' saved [9278421/9278421] [root@yfw tmp]# cd /tmp [root@yfw tmp]# unzip apache-maven-3.9.11-bin.zip -d /opt/ Archive: apache-maven-3.9.11-bin.zip creating: /opt/apache-maven-3.9.11/ creating: /opt/apache-maven-3.9.11/lib/ creating: /opt/apache-maven-3.9.11/boot/ creating: /opt/apache-maven-3.9.11/lib/jansi-native/ creating: /opt/apache-maven-3.9.11/lib/jansi-native/Windows/ creating: /opt/apache-maven-3.9.11/lib/jansi-native/Windows/arm64/ creating: /opt/apache-maven-3.9.11/lib/jansi-native/Windows/x86/ creating: /opt/apache-maven-3.9.11/lib/jansi-native/Windows/x86_64/ creating: /opt/apache-maven-3.9.11/bin/ creating: /opt/apache-maven-3.9.11/conf/ creating: /opt/apache-maven-3.9.11/conf/logging/ creating: /opt/apache-maven-3.9.11/lib/ext/ creating: /opt/apache-maven-3.9.11/lib/ext/hazelcast/ creating: /opt/apache-maven-3.9.11/lib/ext/redisson/ inflating: /opt/apache-maven-3.9.11/README.txt inflating: /opt/apache-maven-3.9.11/LICENSE inflating: /opt/apache-maven-3.9.11/NOTICE inflating: /opt/apache-maven-3.9.11/lib/aopalliance.license inflating: /opt/apache-maven-3.9.11/lib/asm.license inflating: /opt/apache-maven-3.9.11/lib/commons-cli.license inflating: /opt/apache-maven-3.9.11/lib/commons-codec.license inflating: /opt/apache-maven-3.9.11/lib/error_prone_annotations.license inflating: /opt/apache-maven-3.9.11/lib/failureaccess.license inflating: /opt/apache-maven-3.9.11/lib/gson.license inflating: /opt/apache-maven-3.9.11/lib/guava.license inflating: /opt/apache-maven-3.9.11/lib/guice.license inflating: /opt/apache-maven-3.9.11/lib/httpclient.license inflating: /opt/apache-maven-3.9.11/lib/httpcore.license inflating: /opt/apache-maven-3.9.11/lib/jansi.license inflating: /opt/apache-maven-3.9.11/lib/javax.annotation-api.license inflating: /opt/apache-maven-3.9.11/lib/javax.inject.license inflating: /opt/apache-maven-3.9.11/lib/jcl-over-slf4j.license inflating: /opt/apache-maven-3.9.11/lib/jspecify.license inflating: /opt/apache-maven-3.9.11/lib/org.eclipse.sisu.inject.license inflating: /opt/apache-maven-3.9.11/lib/org.eclipse.sisu.plexus.license inflating: /opt/apache-maven-3.9.11/lib/plexus-cipher.license inflating: /opt/apache-maven-3.9.11/lib/plexus-component-annotations.license inflating: /opt/apache-maven-3.9.11/lib/plexus-interpolation.license inflating: /opt/apache-maven-3.9.11/lib/plexus-sec-dispatcher.license inflating: /opt/apache-maven-3.9.11/lib/plexus-utils.license inflating: /opt/apache-maven-3.9.11/lib/slf4j-api.license inflating: /opt/apache-maven-3.9.11/boot/plexus-classworlds.license inflating: /opt/apache-maven-3.9.11/lib/jansi-native/Windows/arm64/jansi.dll inflating: /opt/apache-maven-3.9.11/lib/jansi-native/Windows/x86/jansi.dll inflating: /opt/apache-maven-3.9.11/lib/jansi-native/Windows/x86_64/jansi.dll inflating: /opt/apache-maven-3.9.11/bin/m2.conf inflating: /opt/apache-maven-3.9.11/bin/mvn.cmd inflating: /opt/apache-maven-3.9.11/bin/mvnDebug.cmd inflating: /opt/apache-maven-3.9.11/bin/mvn inflating: /opt/apache-maven-3.9.11/bin/mvnDebug inflating: /opt/apache-maven-3.9.11/bin/mvnyjp inflating: /opt/apache-maven-3.9.11/conf/logging/simplelogger.properties inflating: /opt/apache-maven-3.9.11/conf/settings.xml inflating: /opt/apache-maven-3.9.11/conf/toolchains.xml inflating: /opt/apache-maven-3.9.11/lib/ext/README.txt inflating: /opt/apache-maven-3.9.11/lib/ext/hazelcast/README.txt inflating: /opt/apache-maven-3.9.11/lib/ext/redisson/README.txt inflating: /opt/apache-maven-3.9.11/lib/jansi-native/README.txt inflating: /opt/apache-maven-3.9.11/boot/plexus-classworlds-2.9.0.jar inflating: /opt/apache-maven-3.9.11/lib/jcl-over-slf4j-1.7.36.jar inflating: /opt/apache-maven-3.9.11/lib/aopalliance-1.0.jar inflating: /opt/apache-maven-3.9.11/lib/maven-resolver-connector-basic-1.9.24.jar inflating: /opt/apache-maven-3.9.11/lib/maven-settings-builder-3.9.11.jar inflating: /opt/apache-maven-3.9.11/lib/maven-artifact-3.9.11.jar inflating: /opt/apache-maven-3.9.11/lib/maven-compat-3.9.11.jar inflating: /opt/apache-maven-3.9.11/lib/slf4j-api-1.7.36.jar inflating: /opt/apache-maven-3.9.11/lib/org.eclipse.sisu.inject-0.9.0.M4.jar inflating: /opt/apache-maven-3.9.11/lib/jspecify-1.0.0.jar inflating: /opt/apache-maven-3.9.11/lib/wagon-provider-api-3.5.3.jar inflating: /opt/apache-maven-3.9.11/lib/guice-5.1.0-classes.jar inflating: /opt/apache-maven-3.9.11/lib/maven-slf4j-provider-3.9.11.jar inflating: /opt/apache-maven-3.9.11/lib/maven-resolver-named-locks-1.9.24.jar inflating: /opt/apache-maven-3.9.11/lib/javax.annotation-api-1.3.2.jar inflating: /opt/apache-maven-3.9.11/lib/commons-codec-1.18.0.jar inflating: /opt/apache-maven-3.9.11/lib/maven-resolver-transport-http-1.9.24.jar inflating: /opt/apache-maven-3.9.11/lib/maven-resolver-spi-1.9.24.jar inflating: /opt/apache-maven-3.9.11/lib/httpcore-4.4.16.jar inflating: /opt/apache-maven-3.9.11/lib/wagon-http-3.5.3.jar inflating: /opt/apache-maven-3.9.11/lib/org.eclipse.sisu.plexus-0.9.0.M4.jar inflating: /opt/apache-maven-3.9.11/lib/jansi-2.4.2.jar inflating: /opt/apache-maven-3.9.11/lib/maven-resolver-transport-file-1.9.24.jar inflating: /opt/apache-maven-3.9.11/lib/maven-plugin-api-3.9.11.jar inflating: /opt/apache-maven-3.9.11/lib/plexus-interpolation-1.28.jar inflating: /opt/apache-maven-3.9.11/lib/maven-resolver-transport-wagon-1.9.24.jar inflating: /opt/apache-maven-3.9.11/lib/plexus-utils-3.6.0.jar inflating: /opt/apache-maven-3.9.11/lib/plexus-cipher-2.0.jar inflating: /opt/apache-maven-3.9.11/lib/plexus-sec-dispatcher-2.0.jar inflating: /opt/apache-maven-3.9.11/lib/maven-resolver-provider-3.9.11.jar inflating: /opt/apache-maven-3.9.11/lib/maven-shared-utils-3.4.2.jar inflating: /opt/apache-maven-3.9.11/lib/wagon-file-3.5.3.jar inflating: /opt/apache-maven-3.9.11/lib/guava-33.4.8-jre.jar inflating: /opt/apache-maven-3.9.11/lib/gson-2.13.1.jar inflating: /opt/apache-maven-3.9.11/lib/asm-9.8.jar inflating: /opt/apache-maven-3.9.11/lib/maven-resolver-api-1.9.24.jar inflating: /opt/apache-maven-3.9.11/lib/maven-resolver-util-1.9.24.jar inflating: /opt/apache-maven-3.9.11/lib/maven-embedder-3.9.11.jar inflating: /opt/apache-maven-3.9.11/lib/maven-settings-3.9.11.jar inflating: /opt/apache-maven-3.9.11/lib/error_prone_annotations-2.38.0.jar inflating: /opt/apache-maven-3.9.11/lib/plexus-component-annotations-2.2.0.jar inflating: /opt/apache-maven-3.9.11/lib/javax.inject-1.jar inflating: /opt/apache-maven-3.9.11/lib/maven-model-3.9.11.jar inflating: /opt/apache-maven-3.9.11/lib/failureaccess-1.0.3.jar inflating: /opt/apache-maven-3.9.11/lib/maven-builder-support-3.9.11.jar inflating: /opt/apache-maven-3.9.11/lib/maven-repository-metadata-3.9.11.jar inflating: /opt/apache-maven-3.9.11/lib/maven-resolver-impl-1.9.24.jar inflating: /opt/apache-maven-3.9.11/lib/wagon-http-shared-3.5.3.jar inflating: /opt/apache-maven-3.9.11/lib/httpclient-4.5.14.jar inflating: /opt/apache-maven-3.9.11/lib/maven-core-3.9.11.jar inflating: /opt/apache-maven-3.9.11/lib/commons-cli-1.9.0.jar inflating: /opt/apache-maven-3.9.11/lib/maven-model-builder-3.9.11.jar [root@yfw tmp]# sudo ln -s /opt/apache-maven-3.9.11 /opt/maven [root@yfw tmp]# sudo tee /etc/profile.d/maven.sh << 'EOF' > export M2_HOME=/opt/maven > export PATH=$M2_HOME/bin:$PATH > EOF export M2_HOME=/opt/maven export PATH=$M2_HOME/bin:$PATH [root@yfw tmp]# source /etc/profile.d/maven.sh [root@yfw tmp]# mvn -version Apache Maven 3.9.11 (3e54c93a704957b63ee3494413a2b544fd3d825b) Maven home: /opt/maven Java version: 1.8.0_312, vendor: Red Hat, Inc., runtime: /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.312.b07-2.el8_5.x86_64/jre Default locale: en_US, platform encoding: ANSI_X3.4-1968 OS name: "linux", version: "4.18.0-348.7.1.el8_5.x86_64", arch: "amd64", family: "unix" [root@yfw tmp]# ls /usr/lib/jvm/ | grep -i openjdk-11 java-11-openjdk-11.0.13.0.8-4.el8_5.x86_64 jre-11-openjdk-11.0.13.0.8-4.el8_5.x86_64 [root@yfw tmp]# mvn -version Apache Maven 3.9.11 (3e54c93a704957b63ee3494413a2b544fd3d825b) Maven home: /opt/maven Java version: 1.8.0_312, vendor: Red Hat, Inc., runtime: /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.312.b07-2.el8_5.x86_64/jre Default locale: en_US, platform encoding: ANSI_X3.4-1968 OS name: "linux", version: "4.18.0-348.7.1.el8_5.x86_64", arch: "amd64", family: "unix" [root@yfw tmp]# sudo tee /etc/profile.d/java-maven.sh << 'EOF' > # 设置 Java 11 为默认 JDK > export JAVA_HOME=/usr/lib/jvm/java-11-openjdk > > # 强制 Maven 使用 UTF-8 编码(解决 ANSI_X3.4-1968 问题) > export MAVEN_OPTS="-Dfile.encoding=UTF-8" > > # Maven 路径 > export M2_HOME=/opt/maven > export PATH=$JAVA_HOME/bin:$M2_HOME/bin:$PATH > > echo "✅ JAVA_HOME: $JAVA_HOME" > echo "✅ Maven home: $M2_HOME" > echo "✅ Java version: $(java -version 2>&1 | head -n 1)" > EOF # 设置 Java 11 为默认 JDK export JAVA_HOME=/usr/lib/jvm/java-11-openjdk # 强制 Maven 使用 UTF-8 编码(解决 ANSI_X3.4-1968 问题) export MAVEN_OPTS="-Dfile.encoding=UTF-8" # Maven 路径 export M2_HOME=/opt/maven export PATH=$JAVA_HOME/bin:$M2_HOME/bin:$PATH echo "✅ JAVA_HOME: $JAVA_HOME" echo "✅ Maven home: $M2_HOME" echo "✅ Java version: $(java -version 2>&1 | head -n 1)" [root@yfw tmp]# [root@yfw tmp]# # 删除旧的仅 Maven 配置(避免冲突) [root@yfw tmp]# sudo rm -f /etc/profile.d/maven.sh [root@yfw tmp]# [root@yfw tmp]# # 使用新的 java-maven.sh(上面已创建) [root@yfw tmp]# ls /etc/profile.d/java-maven.sh /etc/profile.d/java-maven.sh [root@yfw tmp]# cd ~ [root@yfw ~]# git clone https://github.com/igniterealtime/openfire-restAPI-plugin.git Cloning into 'openfire-restAPI-plugin'...
最新发布
10-04
你已经完成了 **Maven 3.9.11 的安装**,并且也创建了正确的环境变量脚本 `/etc/profile.d/java-maven.sh` 来设置: - `JAVA_HOME=/usr/lib/jvm/java-11-openjdk`(✅ 正确指向 Java 11) - `MAVEN_OPTS="-Dfile.encoding=UTF-8"`(✅ 解决编码问题) - 将 `$JAVA_HOME/bin` 和 `$M2_HOME/bin` 加入 `PATH` 但是!你在执行完 `sudo tee ... << 'EOF'` 后,并没有运行: ```bash source /etc/profile.d/java-maven.sh ``` 所以新的环境变量 **还没有生效**! --- ## ✅ 第一步:立即加载新配置 ```bash source /etc/profile.d/java-maven.sh ``` 你现在应该看到类似输出: ```text ✅ JAVA_HOME: /usr/lib/jvm/java-11-openjdk ✅ Maven home: /opt/maven ✅ Java version: openjdk version "11.0.13" 2021-10-19 LTS ``` --- ## ✅ 第二步:验证 Java 版本已切换为 Java 11 ```bash java -version javac -version echo $JAVA_HOME ``` 预期输出: ```text openjdk version "11.0.13" 2021-10-19 LTS OpenJDK Runtime Environment 18.9 (build 11.0.13+8-LTS) OpenJDK 64-Bit Server VM 18.9 (build 11.0.13+8-LTS, mixed mode) javac 11.0.13 /usr/lib/jvm/java-11-openjdk ``` --- ## ✅ 第三步:确认 Maven 使用的是 Java 11 + UTF-8 ```bash mvn -version ``` 🎯 理想输出应是: ```text Apache Maven 3.9.11 (3e54c93a704957b63ee3494413a2b544fd3d825b) Maven home: /opt/maven Java version: 11.0.13, vendor: Red Hat, Inc., runtime: /usr/lib/jvm/java-11-openjdk-11.0.13.0.8-4.el8_5.x86_64 Default locale: en_US, platform encoding: UTF-8 ← 关键!不再是 ANSI_X3.4-1968 OS name: "linux", version: "4.18.0-348.7.1.el8_5.x86_64", arch: "amd64", family: "unix" ``` 如果还是显示 Java 8,请检查以下几点: ### 🔍 排查点 1:是否有其他脚本覆盖了 JAVA_HOME? 查看当前 shell 是否被其他地方设置了旧的 `JAVA_HOME`: ```bash echo $JAVA_HOME ``` 如果是 `/usr/lib/jvm/java-1.8.0-openjdk...`,说明有别的配置文件在后面加载了旧值。 查找可能干扰的文件: ```bash grep -r "JAVA_HOME" /etc/profile.d/ ~/.bashrc ~/.profile 2>/dev/null ``` 删除或注释掉所有设置 Java 8 的行。 --- ### 🔍 排查点 2:系统默认 `java` 命令是否仍指向 Java 8? ```bash which java readlink -f $(which java) ``` 如果结果包含 `java-1.8.0-openjdk`,说明系统的默认 `java` 没有通过 `alternatives` 正确注册。 你可以用 `alternatives` 设置默认 Java: ```bash # 注册 Java 11 到 alternatives 系统 sudo alternatives --install /usr/bin/java java /usr/lib/jvm/java-11-openjdk-11.0.13.0.8-4.el8_5.x86_64/bin/java 1 sudo alternatives --install /usr/bin/javac javac /usr/lib/jvm/java-11-openjdk-11.0.13.0.8-4.el8_5.x86_64/bin/javac 1 # 交互式选择默认版本(可选) sudo alternatives --config java sudo alternatives --config javac ``` --- ## ✅ 最终确认:你现在可以安全编译 Openfire 插件 你刚刚已经克隆了仓库: ```bash cd openfire-restAPI-plugin git checkout tags/v2.2.0 -b build-v2.2.0 ``` 确保 `pom.xml` 中的关键属性如下: ```xml <properties> <openfire.version>4.9.2</openfire.version> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> ``` 然后开始构建: ```bash mvn clean package ``` ✅ 成功后你会看到: ```text [INFO] BUILD SUCCESS ``` 生成的插件位于: ```text target/restapi-2.2.0.jar ``` 将它复制到 Openfire 插件目录: ```bash cp target/restapi-2.2.0.jar /opt/openfire/plugins/ ``` 稍等片刻,Openfire 会自动解压并加载该插件。 --- ## 📌 总结:你现在完成的所有步骤 | 步骤 | 内容 | |------|------| | 1 | 下载并解压 Maven 3.9.11(ZIP 格式) | | 2 | 创建软链接 `/opt/maven -> /opt/apache-maven-3.9.11` | | 3 | 创建统一环境变量脚本 `/etc/profile.d/java-maven.sh` | | 4 | 设置 `JAVA_HOME`, `MAVEN_OPTS`, `PATH` | | 5 | 删除冲突的旧脚本 `/etc/profile.d/maven.sh` | | 6 | 执行 `source` 加载新环境 | | 7 | 验证 `java -version`, `mvn -version` 正确使用 Java 11 + UTF-8 | | 8 | 克隆并成功构建 Openfire REST API v2.2.0 插件 | 🎉 完全符合生产级构建要求! --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值