Java中需要知道的关键字

Java中有一些或常用,或不常用,但却不得不知关键字,本篇文章将讨论这些关键字的作用。

transient

transient关键字可能用的不是那么频繁,但却是一个很重要的关键字,它的作用是在对象序列化过程中体现的。如果一个类的变量被transient修饰,那么这个对象在序列化过程中,不会序列化这个变量,同时,在反序列化过程中,也不会去反序列这个变量。

笔者有时候会遇到这样一种情况,可能是因为笔者经验不足。在使用JPA进行数据库操作的时候,某些对象在序列化到数据库的时候,会有一些变量并不想要去存到数据库中,这个时候就可以用transient来修饰变量,解决这个问题。

instanceof

这个关键字就比较常用了,尤其是在一些大量采用反射的框架中,在需要判断某一个对象是否是某一类型(可以是接口,父类,父类的父类等)的时候,可以采用这个关键字。比如说,判断UserServiceImpl是否是UserService接口的类型,可以这样做:

if(userServiceImpl instanceof UserService){
    //do some thing
}

笔者在自己的IOC框架中就用到了这个关键字,在将进行接口依赖注入的时候,使用该关键字判断容器中是否有相应接口的实例,然后将实例注入。

final

final关键字可以用来修饰变量、类、方法。

final修饰的变量,一旦在被赋值之后,将不能再次赋值,也就是说这个变量在后续的使用过程中,只能采取读的方式,变量具有不可变性。这里需要做一下区别,就是基本数据类型和引用数据类型在被final修饰后的情况。基本数据类型的不可变性往往体现在变量的值永远不会再变化,而引用类型则不是,引用类型的不变性是体现在引用的不变性。引用类型的变量一旦为一个引用数据类型赋值,那么变量就会指向对象在内存中的地址,final关键字修饰过后,变量所指向的地址就不能再被改变,但对象本身的状态还是可以改变的,可以看一下下面这段代码:

final int i = 0;

i = 1; //error

final int[] j = new int[10];

j = new int[20]; //error

j[0] = 1; //right

final修饰过的变量有一个好处,即它会是线程安全的。在Java中,final变量会进行指令重排序,确保所有线程在访问该变量的时候,变量已经被初始化过了。虽然在旧的版本中,会出现对象引用在构造函数中“逸出”的情况,但自从jsr133增强了final的内存语义之后,所有线程在看到final变量时,看到的都是已经初始化之后的值。final变量初始化之后又不会再改变,所以它是线程安全的。

final修饰的类,将具有不可继承性,即不能有子类。

final修饰的方法,将不可被重写,但可以重载。

static

static关键字可以用来修饰变量,方法。static修饰的变量和方法,将只属于类,可以通过类名.变量名(或方法名)的方式来引用,使用方式如下:

public class Demo{
    public static int i = 0;
    public static void hello(){
        System.out.println(i);
    }
}
//可以这么访问
int j = Demo.i;
Demo.hello();

其中static变量将会常驻内存中,不会在垃圾回收的过程中被回收掉,甚至会成为垃圾回收中的GC Root。static变量的初始化只能在static代码块中执行,它先于构造函数执行,使用方式如下:

public class Demo{
    public static int i;
    static{
        i=0;
    }
}

volatile

这个关键字是一个很重要的关键字,可以修饰变量。如果要进行多线程编程,那么这个关键字将会是一个重点。因为它有两个特性:可见性和原子性。

可见性是指,对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。volatile变量再被修改的时候,如果有其他线程读取了该变量,它会通知其他线程变量已经实现,重新读取最新的值。

原子性是指,对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种符合操作不具有原子性。其原理是因为volatile具有一定程度上的锁的语义,但并没有像synchronized那么重量级。

它的使用方式如下,以最常见的单例模式为示例。一般来说,我们比较喜欢使用double-check的方式实现单例模式,因为它既可以做延迟初始化又可以保证线程的安全,代码如下:

public class Demo{
    private static Demo instance;
    private Demo(){
        
    }
    public static Demo getInstance(){
        if(instance == null){ //第一步,判断instance是否为null
            synchronized(Demo.class){ //第二步,加锁
                if(instance == null){ //第三步,再判断instance是否为null
                    instance = new Demo(); //第四步,实例化
                }
            }
        }
        return instance;
    }
}

这段代码看似线程安全,但却存在一个很大的缺陷。如果有两个线程,两个线程都执行到了第二步,一个线程在拿到锁并进行实例化之后,另一个线程继续进入同步代码块,这个线程读到的instance可能还是null,之后就会导致线程安全问题。为什么会这样呢?其实在Java中,对象的实例化可以分为以下三个子阶段:

memory = allocate();  // 1:分配对象的内存空间
ctorInstance(memory); // 2:初始化对象
instance = memory;  // 3:设置instance指向刚分配的内存地址

由于Java会对指令进行重排序,可能会导致3和2的指令顺序相反,即

memory = allocate();  // 1:分配对象的内存空间
instance = memory;  // 3:设置instance指向刚分配的内存地址
ctorInstance(memory); // 2:初始化对象

问题就出现在这里,如果线程一的还没有执行完初始化对象这个子阶段,另一个线程将会认为这个实例为空,将会导致线程安全。要解决这个问题,可以加上volatile关键字,代码如下:

public class Demo{
    private volatile static Demo instance;
    private Demo(){
        
    }
    public static Demo getInstance(){
        if(instance == null){ //第一步,判断instance是否为null
            synchronized(Demo.class){ //第二步,加锁
                if(instance == null){ //第三步,再判断instance是否为null
                    instance = new Demo(); //第四步,实例化
                }
            }
        }
        return instance;
    }
}

volatile会禁止指令重排序,使得这个操作变得具有原子性,这样线程就可以读取到变量最新的状态,保证了线程的安全。

synchronized

synchronized可以说是一个非常常见的关键字了,在jdk1.6之前,它被称为重量级锁,不过jdk1.6之后经过优化和升级,已经没有那么夸张了。通过使用synchronized可以使代码同步,解决多线程环境下的一些问题。一般来说,它可以使用下面三种形式的锁:

  • 对于普通同步方法,锁是当前实例对象。
  • 对于静态同步方法,锁是当前类的Class对象。
  • 对于同步方法块,锁是synchronized括号里配置的对象。

当一个线程试图去访问同步代码块时,它必须先得到锁,退出或抛出异常时必须释放锁。其底层原理是通过Monitor对象来实现方法同步和代码块同步。代码块同步是使用monitorenter和monitorexit指令实现的,方法同步是使用另一种方法实现的。

monitorenter方法是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM要保证每个monitorenter都有一个monitorexit与之配对。任何对象都有一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor所有权,即尝试获得对象的锁。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容