标签归档:java

记一次使用高版本jdk接口被坑的经历

最近在一个项目中,使用了一个jdk1.8才有支持的方法:List.sort,开发的时候没有发现注释那写着“since 1.8”,然后打包,发布了。而线上的环境用的是1.7!!!所以在线上访问含有用到这个高版本接口的逻辑的api时,就报500错误了。
我的开发工具是eclipse,本机的jdk版本是1.8,项目是用maven打包的,并且打包时jdk版本设置为1.6。
后来经过研究,发现以下3种情况:

  • 在preference->java->install jres中,将Java se 7选中的话,代码直接报错了。应该是直接改变了整个工作空间的运行环境版本,所以有正确的检验。
    WechatIMG27
  • 如果不改installed jres,而是将server中的运行环境,则会在运行时报error(是error,不是异常)。 通过这样,就可以在本地测试时发现问题了。
    WechatIMG47
    WechatIMG48

  • 改项目的编译版本不起作用,还不知道为什么。

所以后面做项目,看来不仅要将编译等级改成和线上的一致,同时还要将installed jres中改成相同的版本或在server使用的运行环境中设置

Java并发编程的特性

1. Java内存模型:

java内存模型

       java的对象都是在主内存(物理内存)中创建的;而各个线程在执行的时候,为了隔离相互之间的影响,以及降低CPU和内存IO之间速率相差太大的影响,会将主内存中的变量读取到工作内存中(高速缓存,寄存器),然后在工作内存中操作,完成之后再写回到主内存中。
       主内存和工作内存中的操作由以下几部分操作组成,每部分均是原子性的:
内存操作

       首先通过read、load将变量的值放入工作内存中;当执行引擎需要使用变量时,使用use操作,读取工作内存的变量,操作完之后使用assign,将新的变量值写回工作内存;当需要将变量值同步回主内存时,通过store、write操作将工作内存的值刷新到主内存中。
       java虚拟机规定read和load,store和write必须成对出现。
       每次lock一个变量,将会清空该变量在工作内存中的值。

2. 并发编程的3个特性

2.1 原子性:

  • 基本变量的读取和赋值是原子性的,结合上面的操作顺序,即可得知,因为每次read和write均是原子性的。但是java对于64位的变量,允许拆分成两部分的32位分别操作,因此对于double和long的读写,可能没有原子性。但实际上大多数jvm在具体实现时,也都将这两种类型的读写实现为原子性了。
  • synchronized。必须先lock才能进入同步代码块,因此一个线程在unlock之前,其他线程无法进入同步代码块,保证了同步代码块的原子性。

2.2 可见性

  • volatile。被volatile修饰的变量,在每次use之前,都需要先read和load,因此保证了每次读取的都是主内存的最新值;而在每次assign的时候,都需要store和write回主内存,因此保证了最新值可以刷新到主内存。
  • synchronized。每次进入同步代码块之前,都会先lock住变量,然后清空工作内存中共享变量的值;每次退出同步代码块之前,都会先将工作内存中的共享变量刷新到主内存中,然后unlock(关于synchronized的可见性,可以见:synchronized可见性)。

2.3 有序性

  • volatile。JVM规定,在volatile之前的操作,不能重排序到volatile之后。
  • synchronized。同步代码块对于不同线程来说串行进入的。

3. 一段同步代码的分析

import java.util.Date;
import java.util.concurrent.TimeUnit;

public class ThreadTest extends Thread {
 private static boolean stop;
 // private static volatile boolean stop;

 synchronized void f() {}

 @Override
 public void run() {
  System.out.println("start");
  while (!stop) {
   // f();
   // System.out.println("loop");
  }
  System.out.println("end");
 }

 public static void main(String[] args) {
  try {
   ThreadTest t = new ThreadTest();
   t.start();
   Thread.sleep(2000);
   t.stop = true;
   System.out.println("stop");
   Thread.sleep(2000);
  } catch (InterruptedException e) {
   System.out.println(e.getMessage());
  }
 }
}

       由于stop变量的不可见性的,所以线程并不会在stop设为true之后停止,原因是stop不具有可见性,但是如果用volatile修饰stop,或者在循环中调用synchronized方法,则可以使线程停下来;另外循环中调用控制台输出,也可以停下线程,猜测是输出到控制台的时候调用了synchronized方法。
       这段类似的代码在《Effective Java》中提到,编译器在优化的时候也可能优化为:

while(!stop){
    while(true){
        // do something
    }
}

4. 双重锁单例模式的一个细节分析

private static volatile TestSingleton instance = null;  

    public static TestSingleton getInstance() {  
           if (instance == null) {    
             synchronized (TestSingleton.class) {    
                if (singleton == null) {    
                   singleton = new TestSingleton();   
                }    
             }    
           }   
           return instance;  
    }  

       上面是一个典型的单例实现方式,关于volatile的作用,大部分地方会解释为可见性,但是此处使用synchronized已经保证了singleton的可见性,为何还要使用volatile。其实这里更多的是考虑有序性,防止代码重排序,如果在创建TestSingleton的时候,先将TestSingleton对象的内存地址返回给singleton了,而TestSingleton还没有完成初始化,此时,其他线程可能拿到一个不完整的singleton。具体可见:单例模式的实现,在这篇博文的评论中有说到。

参考资料:

  • 《深入理解Java虚拟机》
  • 《Effective Java》
  • http://blog.csdn.net/jason0539/article/details/23297037
  • http://www.imooc.com/video/6780

maven nexus+nginx配置

由于项目中需要用到第三方的推送sdk,而这些sdk都是只提供jar包下载,而我们的项目中是使用maven进行jar包管理。所以只能在自己的服务器上搭建一个maven私服,将这些第三方sdk放到这个maven私服里面进行管理了。

在网上搜了一圈,发现最简单的搭建方法就是使用nexus(不是谷歌亲儿子nexus哦)这个软件进行搭建了。

Nexus官网:http://www.sonatype.org/nexus/

在linux上安装nexus也比较简单,在百度上一搜一大把。以下的步骤引用自:http://blog.163.com/sz2273_pr/blog/static/4126429620135811573231/

nexus安装步骤开始
 1, Nexus 下载

Nexus 官方下载, 最新版下载: nexus-2.4.0-09-bundle.tar.gz

Nexus war下载: nexus-2.4.0-09.war

nexus有两种安装实现方式,一种是war包的方式,将它直接放在例如tomcat下就可以启用的,还有一种源码的方式,做为一个运维还是喜欢源码方式的说哈。开始吧。。。

2, Nexus 安装

解压tar.gz文件:

#cd /home/tools

#tar zxvf nexus-2.4.0-09-bundle.tar.gz

移动目录

#mv nexus-2.4.0-09 /usr/local/nexus

3, Nexus 启动

nexus启动是在bin目录下,首先看一下启动/关闭/重启等命令, 输入命令:

#cd /usr/local/nexus/bin

#./nexus

出现如下选项:

[root@test01 bin]# ./nexus

Usage: ./nexus { console | start | stop | restart | status | dump }

启动nexus:

./nexus  start

关闭nexus:

./nexus  stop

4, Nexus 验证

启动nexus后,在本机浏览器输入地址: http://localhost:8081/nexus

出现上述页面,说明配置nexus成功!

点击右上角“Log in”, 输入用户名和密码(默认用户名:admin      密码: admin123)登录

5.设置

1.点击左侧 Repositories将所有Type 是 proxy 的 configuration配置选项中的 Download Remote Index 配置改为 True

如果需要代理将配置最下面的Override Http Proxy Setting 勾上并填写代理服务器地址和端口,最后保存设置

2.然后在列表中分别右键点击 ReIndex

3、添加自己的jar到nexus

选中 3rd party , Artifact Upload 标签

GAV Definition 选择GAV Parameters

填写 Group Artifact version packaging

然后选择jar包 上传提交即可。

nexus安装步骤结束

另外:在解压出来的nexus目录中,{nexus}/conf/ 下有个名为nexus.properties配置文件,在这里可以配置nexus的端口,仓库的文件的路径等等

其中有一项nexus-webapp-context-path=/nexus的配置,这是指定url中ip:port/XXX中的XXX的,所以如果配置成/nexus的话,你要访问nexus,则要输入ip:port/nexus。

如果要配合nginx使用,则这一项要改成nexus-webapp-context-path=/,即配制成使用ip:port就能访问到nexus。

然后就是配置,nginx了。

我的配置如下:

server {
listen 80;
server_name nexus.kainaodong.com;
location / {
expires -1;
index /;
proxy_pass http://127.0.0.1:9001;
}
include /alidata/server/nginx/conf/rewrite/default.conf;
access_log /alidata/log/nginx/access/default.log;
}

proxy_pass就是配置将对nexus.kainaodong.com子域名的访问转到http://127.0.0.1:9001,也即是我的服务器的nexus上。

一开始,我的nexus的nexus-webapp-context-path这一下没有配置成“/”,而是将nginx的proxy_pass配置成http://127.0.0.1:9001/nexus,这样虽然能访问到nexus,但是里面的仓库内容时访问不到的,也不能进行登录。

然后试着在nexus的server配置中,将base url强制为子域名,这时仓库的内容可以访问了,但是还是登录不了。

 

 

 

QQ截图20150606204706

 

最后在网上又搜了一圈,发现nexus的nexus-webapp-context-path这个属性是可以配置访问路径,所以直接将它改为”/”,nginx中的proxy_pass配置成http://127.0.0.1:9001。

这样,nexus终于能使用子域名来访问了。