Java 防止循环初始化

对于某些对象实例而言, 变量就算是static final的, 它们的初始化也可能会被安排在后期进行

声明一个字段为static final并不能保证它在被读之前已经完全初始化

仔细理解这两句话,然后来看下面这段代码(keng)

public class Cycle {
    private final int result;
    private static final Cycle self = new Cycle();
    private static final int randomNumber = (int)(Math.random()*100);
    
    public Cycle(){
        System.out.println("randomNumber:"+randomNumber);
        result = randomNumber - 10;
    }
    
    public static void main(String args[]){
        System.out.println(self.result);
    }
}
 

上面这段代码会永远输出 -10 , 而不是随机值

再仔细看看这句话

当你使用一个类的静态成员变量时,会触发这个类的构造器函数

也就是说当代码执行到第四行

private static final Cycle self = new Cycle();

此时出现了第一个静态变量self, 接着jvm就需要调用构造器了

public Cycle(){
        System.out.println("randomNumber:"+randomNumber);
        result = randomNumber - 10;
    }

注意此时randomNumber的值还是0 , 因为它还没有被赋值 

正确的做法是在类调用构造器之前初始化randomNumber

public class CycleCorrected {
    
    private static final int randomNumber = (int)(Math.random()*100);
    private final int result;
    private static final CycleCorrected self = new CycleCorrected();
    
    public CycleCorrected(){
        System.out.println("randomNumber:"+randomNumber);
        result = randomNumber - 10;
    }
    
    public static void main(String args[]){
        System.out.println(self.result);
    }
}

其实上面的代码稍作改动, 还可以观察到一个Exception in thread “main” java.lang.StackOverflowError

当然前提是我的内存堆够大,所以出现了Stack溢出而不是堆内存溢出

1. 把self变量改成非静态的, 这样main函数需要new一个实例

2. 当你new出一个App的实例时, 由于它包含一个self, 所以它需要调用new App()来初始化这个实例, 但是被new出来的实例又包含了一个self…

    其实是一个new方法的构造函数递归

3. 为什么之前没有出现溢出呢? 因为static变量的self, 只会被初始化一次

public class App {
    private final int result;
    private final App self = new App();
    private static final int randomNumber = (int)(Math.random()*100);
    
    public App(){
        System.out.println("randomNumber:"+randomNumber);
        result = randomNumber - 10;
    }
    
    public static void main(String args[]){
        
        System.out.println(new App().result);
    }
}

Java 如何比较数组

先看下面的代码

int[] a = new int[10];
 int[] b = new int[10];
        
System.out.println(a==b);

上面代码会直接打印出 false , 正常来讲初始化的int数组值应该都是0 , 那为什么两个数组不相等呢

rootcause在于, 数组并不会重写Object.equals() 方法, 而原始的equals方法比较的是引用

两个数组的引用不同, 自然结果是false 

正确的做法应该是使用Arrays.equals(a,b)

import java.util.Arrays;

public class Test {
    public static void main(String args[]){
        int[] a = new int[10];
        int[] b = new int[10];
        
        System.out.println(a[0]);
        
        System.out.println(a==b);
        System.out.println(Arrays.equals(a, b));
        
    }
}

如何通过反射调用(黑进)私有方法

私有方法的api不一定就是完全安全的, 单例模式的私有构造器也不一定能保证自己一定是单例

在产品平台上做开发, 有时候你总会需要jad, 继承, cglib 或者 反射

package name.lizhe;

public class Employee {
    
    private void setSalary(int i){
        System.out.println("changed salary to:"+i);
    }
    
}
package name.lizhe;

import java.lang.reflect.InvocationTargetException;

public class Executor {

    public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException,
            IllegalArgumentException, InvocationTargetException {

        Employee emp = new Employee();

        Class[] cArg = new Class[1];
        cArg[0] = int.class;

        java.lang.reflect.Method m = Employee.class.getDeclaredMethod("setSalary", cArg);
        m.setAccessible(true);
        m.invoke(emp, 10000);
    }

}
 

如果是私有构造方法的话略微 有些不同

package name.lizhe;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Executor {

    public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException,
            IllegalArgumentException, InvocationTargetException, InstantiationException {

        Employee emp = Employee.getInstance();

        Class[] cArg = new Class[1];
        cArg[0] = int.class;

        Constructor<Employee> m = Employee.class.getDeclaredConstructor();
        m.setAccessible(true);
        Employee emp2 = (Employee) m.newInstance(null);
        
        System.out.println(emp==emp2);
    }

}

Java rmi

以下内容的sample source 放在 https://github.com/zl86790/download/blob/master/java_rmi_sample.zip

Speaker

package name.lizhe.inter;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface Speaker extends Remote {
    public String sayHello(String name) throws RemoteException;    
}

SpeakerImpl

package name.lizhe.impl;

import java.io.Serializable;
import java.rmi.RemoteException;

import name.lizhe.inter.Speaker;

public class AppleSpeaker implements Speaker,Serializable {
    
    @Override
    public String sayHello(String name) throws RemoteException {
        
        return "hello "+name;
    }

}

server启动类

package name.lizhe.exe;

import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.util.Scanner;

import name.lizhe.impl.AppleSpeaker;
import name.lizhe.inter.Speaker;

public class Executor {
    public static void main(String args[]) throws RemoteException, MalformedURLException, AlreadyBoundException{
        Speaker speaker = new AppleSpeaker(); 
        LocateRegistry.createRegistry(8888);  
        Naming.bind("rmi://127.0.0.1:8888/speaker", speaker);  
        Scanner sc = new Scanner(System.in);
        sc.nextLine();
    }
}
 

client

package name.lizhe.exe;

import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;

import name.lizhe.inter.Speaker;

public class Client {

    public static void main(String[] args) throws MalformedURLException, RemoteException, NotBoundException {
        Speaker speaker = (Speaker) Naming.lookup("rmi://127.0.0.1:8888/speaker");  
        System.out.println(speaker.sayHello("world")); 
    }

}

下面是spring版本

不过很奇怪的是一开始我使用4.2.2版本, 会出现

java.lang.NoClassDefFoundError: org/springframework/expression/ParserContext

换成4.1.6版本之后消失了

新建两个maven项目, pom的内容都一样

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>sprmiclient</groupId>
    <artifactId>sprmiclient</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <properties>
        <spring-version>4.1.6.RELEASE</spring-version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>${spring-version}</version>
        </dependency>
    </dependencies>

</project>

然后主要的interface 和impl文件和上面的一样

server project 的 applicationContext.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jee="http://www.springframework.org/schema/jee" 
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd   
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd   
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd   
       http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd   
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

    <bean id="appleSpeakerImpl" class="name.lizhe.impl.AppleSpeaker"> </bean>
    
    <bean id="myRMIServer"  class="org.springframework.remoting.rmi.RmiServiceExporter">
      <property name="serviceName" value="speaker"></property>
      <property name="service" ref="appleSpeakerImpl"></property>
      <property name="serviceInterface" value="name.lizhe.inter.Speaker"></property>
      <property name="registryPort" value="8888"></property>
    </bean>

</beans>

然后启动文件为

package name.lizhe.exe;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Executor {
    public static void main(String args[]){
        new ClassPathXmlApplicationContext("applicationContext.xml");  
    }
}

spring容器会自己阻塞,所以不需要scanner了

启动server项目之后会看到

然后是client project

applicationContext内容是

<?xml version="1.0" encoding="UTF-8" standalone="no"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
       xmlns:aop="http://www.springframework.org/schema/aop"  
       xmlns:context="http://www.springframework.org/schema/context"  
       xmlns:jee="http://www.springframework.org/schema/jee"   
       xmlns:tx="http://www.springframework.org/schema/tx"  
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
       xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd     
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd     
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd     
       http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd     
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">  
  
    <bean id="speakerClient"  class="org.springframework.remoting.rmi.RmiProxyFactoryBean">  
      <property name="serviceInterface" value="name.lizhe.inter.Speaker"></property>  
      <property name="serviceUrl" value="rmi://127.0.0.1:8888/speaker"></property>  
    </bean>  
</beans> 

启动文件为

package name.lizhe;  
  
import java.rmi.RemoteException;

import org.springframework.context.ApplicationContext;  
import org.springframework.context.support.ClassPathXmlApplicationContext;

import name.lizhe.inter.Speaker;  
  
public class SpeakerClient {  
      
    public static void main(String[] args) throws RemoteException {  
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");  
        Speaker speaker =  applicationContext.getBean("speakerClient",Speaker.class);  
        System.out.println(speaker.sayHello("world"));  
    }  
  
}  

启动client项目之后

tomcat classloader 加载class顺序

今天下午被同事问到如果我有两个不同版本的class分别放在两个module里如何处理(我们项目里的module是分布在不同的war包里的)

然后引出下面的问题

1. 完全相同(同包同名)的class如何加载

2. 完全相同的class如果在不同war包里如何加载

印象中应该是不同war包各自加载自己的, 猜测是因为会启动各自的application级别classloader, 稍微调查之后发现自己原来的认识不够深入

参考 http://tomcat.apache.org/tomcat-7.0-doc/class-loader-howto.html

我们知道jvm加载class默认是采用 父类委托 模型, 这个模型在之前的文章中我提到过

参考 http://lizhe.name.csdn.net/node/91

java默认提供3种classloader

1. Bootstrp loader 

加载%JAVA_HOME%/jre/lib,-Xbootclasspath参数指定的路径以及%JAVA_HOME%/jre/classes中的类

2. ExtClassLoader  

加载%JAVA_HOME%/jre/lib/ext,此路径下的所有classes目录以及java.ext.dirs系统变量指定的路径中类库

3. AppClassLoader

加载classpath所指定的位置的类或者是jar文档,它也是Java程序默认的类加载器

getSystemClassLoader 返回的就是这个加载器

当一个classloader需要去加载类com.test.A时, 它首先会在自己的加载缓存中查找这个类,如果发现就直接返回,如果没有发现并不会直接加载,而是让自己的父类加载器去加载

等一下, 父类加载器会立即加载这个类么, 当然不是, 父类加载器也会采用同样的策略, 在自己的缓存中查找,如果没有找到, 就委托给自己的父类, 一直到Bootstrp loader 

如果一直到Bootstrp loader 这一层仍然找不到这个类, classloader才会自己加载它

tomcat实际上采用了不同的类加载机制

这里tomcat的Bootstrap实际上包含了jvm的Bootstrap和ExtClassLoader ($JAVA_HOME/jre/lib/ext), 加载了一些jvm最基本的类

System classloader 忽略系统本身的classpath, 加载

$CATALINA_HOME/bin/bootstrap.jar — Contains the main() method that is used to initialize the Tomcat server, and the class loader implementation classes it depends on.

$CATALINA_BASE/bin/tomcat-juli.jar or $CATALINA_HOME/bin/tomcat-juli.jar — Logging implementation classes. These include enhancement classes to java.util.logging API, known as Tomcat JULI, and a package-renamed copy of Apache Commons Logging library used internally by Tomcat. See logging documentation for more details.

If tomcat-juli.jar is present in $CATALINA_BASE/bin, it is used instead of the one in $CATALINA_HOME/bin. It is useful in certain logging configurations

$CATALINA_HOME/bin/commons-daemon.jar — The classes from Apache Commons Daemon project. This JAR file is not present in the CLASSPATH built by catalina.bat|.sh scripts, but is referenced from the manifest file of bootstrap.jar.

Common classloader 用于加载tomcat lib目录下的jar和class, 不要把webapp相关jar放在这里

unpacked classes and resources in $CATALINA_BASE/lib
JAR files in $CATALINA_BASE/lib
unpacked classes and resources in $CATALINA_HOME/lib
JAR files in $CATALINA_HOME/lib
  • annotations-api.jar — JavaEE annotations classes.
  • catalina.jar — Implementation of the Catalina servlet container portion of Tomcat.
  • catalina-ant.jar — Tomcat Catalina Ant tasks.
  • catalina-ha.jar — High availability package.
  • catalina-tribes.jar — Group communication package.
  • ecj-*.jar — Eclipse JDT Java compiler.
  • el-api.jar — EL 2.2 API.
  • jasper.jar — Tomcat Jasper JSP Compiler and Runtime.
  • jasper-el.jar — Tomcat Jasper EL implementation.
  • jsp-api.jar — JSP 2.2 API.
  • servlet-api.jar — Servlet 3.0 API.
  • tomcat-api.jar — Several interfaces defined by Tomcat.
  • tomcat-coyote.jar — Tomcat connectors and utility classes.
  • tomcat-dbcp.jar — Database connection pool implementation based on package-renamed copy of Apache Commons Pool and Apache Commons DBCP.
  • tomcat-i18n-**.jar — Optional JARs containing resource bundles for other languages. As default bundles are also included in each individual JAR, they can be safely removed if no internationalization of messages is needed.
  • tomcat-jdbc.jar — An alternative database connection pool implementation, known as Tomcat JDBC pool. See documentation for more details.
  • tomcat-util.jar — Common classes used by various components of Apache Tomcat.
  • tomcat7-websocket.jar — WebSocket 1.1 implementation
  • websocket-api.jar — WebSocket 1.1 API

下面重点来了

WebappX  加载器闪亮登场

WebappX — A class loader is created for each web application that is deployed in a single Tomcat instance. All unpacked classes and resources in the /WEB-INF/classes directory of your web application, plus classes and resources in JAR files under the /WEB-INF/lib directory of your web application, are made visible to this web application, but not to other ones.

也就是说, 每个webapp都会拥有一个独立的WebappX加载器, 它会加载/WEB-INF/classes和/WEB-INF/lib下的所有资源, 只对这个webapp可见,不共享给其他webapp

这个行为是基于 Servlet Specification, version 2.4, section 9.7.2 Web Application Classloader 有兴趣的朋友可以去读一下

WebappX  会先尝试自己加载需要的类,而不是使用父类委托模型委托给自己的父加载器, 但是这里有一些例外, JRE base classes不会被这个加载器处理,还有就是Servlet API classes也不会被这个加载器加载, 所以不要把这些例外放在自己的WEB-INF/lib文件夹里

默认的加载顺序是

  • Bootstrap classes of your JVM
  • /WEB-INF/classes of your web application
  • /WEB-INF/lib/*.jar of your web application
  • System class loader classes (described above)
  • Common class loader classes (described above)

如果设置了<Loader delegate=”true”/> 则加载顺序是

  • Bootstrap classes of your JVM
  • System class loader classes (described above)
  • Common class loader classes (described above)
  • /WEB-INF/classes of your web application
  • /WEB-INF/lib/*.jar of your web application

最后我要加入一个自己以往的经验

测试的时候如果需要在tomcat中放入同包同名的类(有时候为了测试方便,比如com.lizhe.Test在一个已经被部署的jar包里,但是测试的时候为了覆盖掉)

可以直接在classes文件夹里放入com.lizhe.Test.class 这个class会被优先加载

如果需要覆盖一个jar包, 比如test.jar,可以将新的jar包命名为aaaaa.jar , 因为根据实际测试, tomcat加载同路径下的jar包时会按照字母顺序排序,aaaaa.jar会优先于test.jar加载

java 自定义 annotation

package myanno;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAutoValue {
    public String value() default "";
}

package myanno;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;

public class Test {
    
    @MyAutoValue("hello world")
    public static String value;
    
    public static void main(String args[]) throws ClassNotFoundException, NoSuchFieldException, SecurityException{
        Class clazz = Class.forName("myanno.Test");
        Field field = clazz.getField("value");
        Annotation[] ans = field.getAnnotations();
        System.out.println(((MyAutoValue) ans[0]).value());
    }
}

如何正确停止线程

这个话题完全是由于 http://lizhe.name.csdn.net/node/102 带出来的

在谈论这个话题之前,首先我要申明一个概念, java本身不推荐你杀死任何线程而是推荐你要”让它自己运行完毕”,然后我们来看下面几个概念

1. stop是不安全的, stop会释放持有的全部锁然后直接杀死线程,可能会造成数据不一致,而且已经过期,会立即杀死线程

2. interruput 不会立即杀死线程

    当线程处于运行状态时,interrupt不会终止线程,只是设置了一个表示位

    当线程处于阻塞状态(如调用sleep、wait、join等地方) 会抛出一个异常InterruptedException,并且中断状态也将被清除,这样线程就得以退出阻塞的状态

3. ExecutorService shutdown方法会让线程池停止接受新任务,但是不会终止或暂停任何当前持有的任务

4. ExecutorService shutdownNow方法会让线程池停止接受新任务,然后调用对所有任务调用interruput方法(具体做了什么参考第二条), 所以也不会立即终止持有的线程

5. 如果需要使用interruput方法来终止线程

方案1 : 使用interruput自带的标志位

public class Test {
    public static void main(String args[]) throws InterruptedException{
        
        Thread t = new Thread(new Runnable(){

            @Override
            public void run() {
                
                while(!Thread.currentThread().isInterrupted()){
                    System.out.println("running...");
                }
                
                
            }
            
        });
        
        t.start();
        Thread.sleep(1000);
        t.interrupt();
        
    }
}

方案2: 使用volatile关键字的表示位

public class Test {
    
    volatile static boolean stop = false;
    
    public static void main(String args[]) throws InterruptedException{
        
        
        
        Thread t = new Thread(new Runnable(){

            @Override
            public void run() {
                
                while(!stop){
                    System.out.println("running...");
                }
                
                
            }
            
        });
        
        t.start();
        Thread.sleep(1000);
        stop=true;
        
    }
}
 

方案3: 使用中断让线程抛出InterruptedException

public class Test {

    
    public static void main(String args[]) throws Exception{
        
        
        
        Thread t = new Thread(new Runnable(){

            @Override
            public void run() {
                
                while(true){
                    try {
                        Thread.currentThread().sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        break;
                    }
                    System.out.println("running...");
                }
                
                
            }
            
        });
        
        t.start();
        Thread.sleep(1000);
        
        t.interrupt();
        
    }
}
 

也就是说在http://lizhe.name.csdn.net/node/102中我尝试通过shutdownNow方法来停止所有线程的方法是完全错误的

实际上这个例子更好的实现方式应该是使用序列+消费者模型

将所有students压入一个栈, 注意栈的pop方法是线程安全的(synchronized),如果你要自己实现队列需要注意这一点

然后通过4个线程轮询去消费栈中的内容,一旦某个线程找到了我们需要的对象,就修改volatile关键字标示的一个flag标志位,然后终止其它线程

不过这里还是有一个坑, 你不能直接使用一个简单的布尔值来作为表示位 比如 volatile static boolean done = false;

这个对象在传入线程对象的时候 比如 new Worker(data, done) 实际上会传递一个值拷贝(而不是引用拷贝),所以无论你在子线程中如何修改这个表示位,都不会影响原值(被拷贝的值)

你需要使用数组或者是一个类的对象引用来持有这个表示位,这样传递的是类的引用拷贝, 和经典的swap函数是一个道理

具体代码如下

package com.lz.exe;

import java.util.List;
import java.util.Stack;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.stream.Collectors;

import com.lz.bean.Flag;
import com.lz.bean.Student;
import com.lz.tool.DataFactory;
import com.lz.tool.Worker;

public class Executor {
    
    volatile static boolean done = false; //错误的方式
    
    public static void main(String args[]) {

        // List<Student> data = DataFactory.getRandomDummyData(10000000);
        Stack<Student> data = DataFactory.getDummyData(20);

        ExecutorService executor = Executors.newFixedThreadPool(2);
        Flag flag = new Flag();


        String result = "NONE";
        
        /** 错误的方式
        Future<String> f1 = executor.submit(new Worker(data, done));
        Future<String> f2 = executor.submit(new Worker(data, done));
        Future<String> f3 = executor.submit(new Worker(data, done));
        Future<String> f4 = executor.submit(new Worker(data, done));
        **/

        Future<String> f1 = executor.submit(new Worker(data, flag));
        Future<String> f2 = executor.submit(new Worker(data, flag));
        Future<String> f3 = executor.submit(new Worker(data, flag));
        Future<String> f4 = executor.submit(new Worker(data, flag));

        Long start = System.currentTimeMillis();
        System.out.println("started");

        try {

            if (f1.get() != "") {
                result = f1.get();
                System.out.println(1);
            }

            if (f2.get() != "") {
                result = f2.get();
                System.out.println(2);
            }

            if (f3.get() != "") {
                result = f3.get();
                System.out.println(3);
            }

            if (f4.get() != "") {
                result = f4.get();
                System.out.println(4);
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            executor.shutdownNow();
        }

        Long end = System.currentTimeMillis();

        executor.shutdown();
        System.out.println(result);
        System.out.println(end - start);
    }
}
package com.lz.tool;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.lz.bean.Student;

public class DataFactory {
    public static List<Student> getRandomDummyData(int count){
        List<Integer> list = Stream.generate(Math::random).distinct().filter(i -> i < 1).limit(count).map(i -> i * 100000)
                .map(Double::intValue).collect(Collectors.toList());
        
        List<Student> students = new ArrayList<Student>();
        
        for(Integer i:list){
            Student stu = new Student("name"+i,i);
            students.add(stu);
        }
        
        return students;
    }
    
    public static Stack<Student> getDummyData(int count){
        
        Stack<Student> students = new Stack<Student>();
        
        for(int i=0;i<count;i++){
            Student stu = new Student("name"+i,i);
            students.add(stu);
        }
        
        return students;
    }
}
package com.lz.tool;

import java.util.Stack;
import java.util.concurrent.Callable;

import com.lz.bean.Flag;
import com.lz.bean.Student;

public class Worker implements Callable<String>{
    
    private Stack<Student> students;
    volatile private boolean done= false; //错误的方式
    private Flag flag;

    public Worker(Stack<Student> students, boolean done) {
        this.students = students;
        this.done = done;
    }
    
    public Worker(Stack<Student> students, Flag flag) {
        this.students = students;
        this.flag = flag;
    }
    
    @Override
    public String call() throws Exception {
        
        String result = "";
        
        while(!flag.done){
            if(!students.isEmpty()){
                Student stu = students.pop();
                System.out.println("doing "+stu.getAge()+" "+Thread.currentThread() + " " + flag.done);
                if(stu.getAge()==10){
                    result = stu.getName();
                    flag.done = true;
                    break;
                }
            }
            
        }
        return result;
    }

}
package com.lz.bean;

public class Student {
    private String name = "";
    private int age;
    
    public Student(String name, int age){
        this.name = name;
        this.age = age;
    }
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
//        System.out.println("call get age for"+age+" "+Thread.currentThread());
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    
    
}
package com.lz.bean;

public class Flag {
    public volatile boolean done = false;
}
 

ExecutorService shutdown 之后 Future.get() 方法的阻塞问题

今天碰到这个问题记录一下

需求很简单, 差不多是在10万条数据库记录里查找一个特定账户

不过接口是产品包提供的,每次查询都需要进行一次slelect, 不能直接写sql

所以这里我的思路是启动4个线程,然后分别去查找10万/4 条记录,一旦某个线程执行结束, 就关闭全部线程,终止查找

这里只是一个POC,

数据类

package com.lz.bean;

public class Student {
    private String name = "";
    private int age;
    
    public Student(String name, int age){
        this.name = name;
        this.age = age;
    }
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
//        System.out.println("call get age for"+age+" "+Thread.currentThread());
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    
    
}

用来做dummy数据的测试类

package com.lz.tool;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.lz.bean.Student;

public class DataFactory {
    public static List<Student> getRandomDummyData(int count){
        List<Integer> list = Stream.generate(Math::random).distinct().filter(i -> i < 1).limit(count).map(i -> i * 100000)
                .map(Double::intValue).collect(Collectors.toList());
        
        List<Student> students = new ArrayList<Student>();
        
        for(Integer i:list){
            Student stu = new Student("name"+i,i);
            students.add(stu);
        }
        
        return students;
    }
    
    public static List<Student> getDummyData(int count){
        
        List<Student> students = new ArrayList<Student>();
        
        for(int i=0;i<count;i++){
            Student stu = new Student("name"+i,i);
            students.add(stu);
        }
        
        return students;
    }
}
 

线程类

package com.lz.tool;

import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;

import com.lz.bean.Student;

public class Worker implements Callable<String>{
    
    private List<Student> students;
    private ExecutorService executor;

    public Worker(List<Student> students, ExecutorService executor) {
        this.students = students;
        this.executor = executor;
    }
    
    
    
    public List<Student> getStudents() {
        return students;
    }

    public void setStudents(List<Student> students) {
        this.students = students;
    }

    public ExecutorService getExecutor() {
        return executor;
    }

    public void setExecutor(ExecutorService executor) {
        this.executor = executor;
    }

    @Override
    public String call() throws Exception {
        
        String result = "";
        
        for(Student stu:students){
            if(stu.getAge()==7300000){
                result = stu.getName();
                break;
            }
        }
        
        if(result!=""){
            System.out.println(Thread.currentThread()+" i shutdown the pool");
            executor.shutdownNow();
        }
        
        System.out.println(Thread.currentThread()+"/"+result);
        
        return result;
    }

}

主启动类

package com.lz.exe;

import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.stream.Collectors;

import com.lz.bean.Student;
import com.lz.tool.DataFactory;
import com.lz.tool.Worker;

public class Executor {
    public static void main(String args[]) {

        // List<Student> data = DataFactory.getRandomDummyData(10000000);
        List<Student> data = DataFactory.getDummyData(10000000);

        ExecutorService executor = Executors.newFixedThreadPool(2);

        String result = "NONE";

        Future<String> f1 = executor.submit(new Worker(data.subList(0, data.size() / 4 * 1), executor));
        Future<String> f2 = executor
                .submit(new Worker(data.subList(data.size() / 4 * 1, data.size() / 4 * 2), executor));
        Future<String> f3 = executor
                .submit(new Worker(data.subList(data.size() / 4 * 2, data.size() / 4 * 3), executor));
        Future<String> f4 = executor
                .submit(new Worker(data.subList(data.size() / 4 * 3, data.size() / 4 * 4), executor));

        Long start = System.currentTimeMillis();
        System.out.println("started");

        try {

            if (f1.get() != "") {
                result = f1.get();
                System.out.println(1);
            }

            if (f2.get() != "") {
                result = f2.get();
                System.out.println(2);
            }

            if (f3.get() != "") {
                result = f3.get();
                System.out.println(3);
            }

            if (f4.get() != "") {
                result = f4.get();
                System.out.println(4);
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            executor.shutdownNow();
        }

        Long end = System.currentTimeMillis();

        executor.shutdown();
        System.out.println(result);
        System.out.println(end - start);
    }
}

这里的问题是

当线程池设置为4或者2时, 程序正常运行并且结束

当线程池设置为1时, 程序无法正常退出

f3执行完成之后, f4.get() 会永久阻塞

started
Thread[pool-1-thread-1,5,main]/
Thread[pool-1-thread-1,5,main]/
Thread[pool-1-thread-1,5,main] i shutdown the pool
Thread[pool-1-thread-1,5,main]/name7300000
3

方法定义:public List<Runnable> shutdownNow()

(1)线程池的状态立刻变成STOP状态,此时不能再往线程池中添加新的任务。

(2)终止等待执行的线程,并返回它们的列表;

(3)试图停止所有正在执行的线程,试图终止的方法是调用Thread.interrupt(),但是,如果线程中没有sleep 、wait、Condition、定时锁等应用, interrupt()方法是无法中断当前的线程的。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出

isInterrupted()是判断线程的中断标记是不是为true。当线程处于运行状态,并且我们需要终止它时;可以调用线程的interrupt()方法,使用线程的中断标记为true,即isInterrupted()会返回true。此时,就会退出while循环。
注意:interrupt()并不会终止处于“运行状态”的线程!它会将线程的中断标记设为true。

也就是说

1 当线程处于sleep状态时调用interrupt方法,会得到一个异常

2 当线程在运行状态时, 你需要使用while(isInterrupted())这样的结构

java volatile CAS 性能影响 (伪共享)

volatile 的作用请参阅

http://lizhe.name.csdn.net/node/94

关于CAS

http://lizhe.name.csdn.net/node/96

本文讨论volatile和CAS因false sharing的原因对性能造成的影响

先看下面的例子

package testatomic;

public class Data {
    public volatile long value1;
    public volatile long value2;
    public volatile long value3;
    public volatile long value4;
}
 
package testatomic;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class TestAtomic3 extends Thread{
    
    
    public static void main(String args[]) throws InterruptedException{
        Data data = new Data();
        ExecutorService es = Executors.newFixedThreadPool(4);
        long start = System.currentTimeMillis();
        int loopcont = 1000000000;
        Thread t[] = new Thread[4];
        
        t[0] = new Thread( () -> {
            for(int i=0;i<loopcont;i++){
                data.value1 = data.value1+i;
            }
        } );
        
        t[1] = new Thread( () -> {
            for(int i=0;i<loopcont;i++){
                data.value2 = data.value2+i;
            }
        } );
        
        t[2] = new Thread( () -> {
            for(int i=0;i<loopcont;i++){
                data.value3 = data.value3+i;
            }
        } );
        
        t[3] = new Thread( () -> {
            for(int i=0;i<loopcont;i++){
                data.value4 = data.value4+i;
            }
        } );
        
        for(Thread item:t){
            es.submit(item);
        }
        
        for(Thread item:t){
            item.join();
        }
        
        es.shutdown();
         es.awaitTermination(9999999, TimeUnit.SECONDS);
        
        long end = System.currentTimeMillis();
        System.out.println(end-start);  

    }
    
}

线程数使用Volatile不使用volatile
1233461284
457429673

我们一个一个来解释测试结果

单线程 时, 由于需要实时回写主内存, 无法使用高速缓存, 所以使用volatile比不使用volatile耗时要长一些

多线程时, volatile这种实时回写主内存的方式也会带来不小的性能消耗

不使用volatile时, 单线程的耗时要比4线程长

这些都是正常的, 但是如果你注意到在使用volatile时, 4个线程反而比单线程耗时更长,这就奇怪了,多线程反而更慢

这是为什么呢? 罪魁祸首其实是cpu伪共享 (false sharing)

jvm会尽可能的开辟连续内存空间用来存储Data类中的4个long型数据, 也就是说在物理内存上它们是连续的

当value1 被加载进内存之后, cpu会将value2,value3,value4一同加载进cpu的高速缓存中,构成一个缓存行,比如下图中的变量x和y,这样做的意义在于加载了value1的代码很可能也需要访问其他变量

这种加载方式有助于提高高速缓存的命中率, 不过这种方式也有缺点,每次程序更新了本地缓存(比如这里的L3级缓存)中的某个值, 当前内核必需通知其他内核重新加载其缓存行

比如上图中内核1修改x之后,内核2需要重新加载该缓存行,内核2修改了y,内核1也需要重新加载缓存行,正如我们上面的代码例子中的value1,value2,value3…

本质上伪共享与volatile没有必要联系,但是为什么这里我们只有使用了volatile才会对性能造成影响呢

因为如果变量不是volatile的, jvm会将这些值存储在线程栈中,不会及时回写给主内存(不会触及L3寄存器), 各个线程之间也就不会互相干扰

java atomic CAS

java.util.concurrent.atomic 包中的类使用CAS , 而不是传统意义上的同步

CAS (Compare and Swap) 在处理竞争问题时使用的是乐观锁机制 ( 乐观锁在读的时候不会独占,在写的时候会进行检查 )

CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做

不过上面的步骤实际上会来带ABA问题, 就是线程1 读取了A,然后线程2先将原始值改成了B然后又改回A, 线程1在这之后察觉不到版本变化

要避免ABA问题可以使用AtomicStampedReference

在竞争不激烈的情况下, atomic包下的原子类可以获得更好的性能, 不过即使在竞争激烈的情况下, 新的原子类也可以获得比synchronized更好的性能,只是会消耗更多内存(下面提到的数组)

当多个线程同时更新某个原子类实例时, 这个类可以独立的保存每个线程所做的更新

操作值会被保存在一个数组里,  每个线程都可以快速返回, 当某个线程是图获取当前值时, 操作值会被累加起来

package testgc;

import java.util.concurrent.atomic.AtomicInteger;

public class TestVolatitle {
     
    public static AtomicInteger count = new AtomicInteger(0);
 
    public static void inc() {
 
        //这里延迟1毫秒,使得结果明显
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
        }
 
        count.getAndAdd(1);
        
        //我修改后的输出位置
        System.out.println("运行结果:Counter.count=" + TestVolatitle.count);
    }
 
    public static void main(String[] args) throws InterruptedException {
 
        //同时启动10个线程,去进行i++计算,看看实际结果
 
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                public void run() {
                    TestVolatitle.inc();
                }
            }).start();
        }
        
        Thread.sleep(3000);

        //原程序的输出位置在这里
        System.out.println("主线程输出:Counter.count=" + TestVolatitle.count);
    }
}
运行结果:Counter.count=5
运行结果:Counter.count=5
运行结果:Counter.count=5
运行结果:Counter.count=6
运行结果:Counter.count=8
运行结果:Counter.count=5
运行结果:Counter.count=10
运行结果:Counter.count=5
运行结果:Counter.count=9
运行结果:Counter.count=7
主线程输出:Counter.count=10
运行结果:Counter.count=1
运行结果:Counter.count=7
运行结果:Counter.count=6
运行结果:Counter.count=5
运行结果:Counter.count=8
运行结果:Counter.count=4
运行结果:Counter.count=3
运行结果:Counter.count=2
运行结果:Counter.count=9
运行结果:Counter.count=10
主线程输出:Counter.count=10