java 关键字总结

java 关键字总结


Java有50个关键字,它们是:html

abstract      do     implements      private       throw      Boolean     double     import      protected      
throws        break     else    instanceof     public    transient     byte      extends    int      return
true      case       false     interface     short     try    catch    final      long      static      void
char     finally       native    super       volatile       class    float       new        switch      while
continue    for       null        synchronized     const    default       if        package      this       goto

接下来对其中经常使用的几个关键字进行归纳。java


public private protectedweb

public,protected,private是Java里用来定义成员的访问权限的,另外还有一种是“default”,也就是在成员前不加任何权限修饰符。
这四个修饰词de访问权限见下:面试

-- 类内部 package内 子类 其余
public 容许 容许 容许 容许
protected 容许 容许 容许 不容许
default 容许 容许 不容许 不容许
private 容许 不容许 不容许 不容许

好比:用protected修饰的成员(变量或方法),在类内部能够调用,同一个package下的其余类也能够调用,子类里也能够调用,其余地方则不能够调用,也就是说在其余。 spring

在java中,除了这四种修饰词外,还有其余如abstract、static、final等11个修饰词。数组

  • public

使用对象:类、接口、成员
介绍:不管它所处在的包定义在哪,该类(接口、成员)都是可访问的缓存

  • private

使用对象:成员
介绍:成员只能够在定义它的类中被访问安全

  • static

使用对象:类、方法、字段、初始化函数
介绍:成名为static的内部类是一个顶级类,它和包含类的成员是不相关的。静态方法是类方法,是被指向到所属的类而不是类的实例。静态字段是类字段,不管该字段所在的类建立了多少实例,该字段只存在一个实例被指向到所属的类而不是类的实例。初始化函数是在装载类时执行的,而不是在建立实例时执行的。多线程

  • final

使用对象:类、方法、字段、变量
介绍:被定义成final的类不容许出现子类,不能被覆盖(不该用于动态查询),字段值不容许被修改。并发

  • abstract

使用对象:类、接口、方法
介绍:类中包括没有实现的方法,不能被实例化。若是是一个abstract方法,则方法体为空,该方法的实如今子类中被定义,而且包含一个abstract方法的类必须是一个abstract类

  • protected

使用对象:成员
介绍:成员只能在定义它的包中被访问,若是在其余包中被访问,则实现这个方法的类必须是该成员所属类的子类。

  • native

使用对象:成员
介绍:与操做平台相关,定义时并不定义其方法,方法的实现被一个外部的库实现。native能够与全部其它的java标识符连用,可是abstract除外。

public native int hashCode();
  • strictfp

使用对象:类、方法
介绍:strictfp修饰的类中全部的方法都隐藏了strictfp修饰词,方法执行的全部浮点计算遵照IEEE 754标准,全部取值包括中间的结果都必须表示为float或double类型,而不能利用由本地平台浮点格式或硬件提供的额外精度或表示范围。

  • synchronized

使用对象:方法
介绍:对于一个静态的方法,在执行以前jvm把它所在的类锁定;对于一个非静态类的方法,执行前把某个特定对象实例锁定。

  • volatile
    使用对象:字段

介绍:由于异步线程能够访问字段,因此有些优化操做是必定不能做用在字段上的。volatile有时能够代替synchronized。

  • transient
    使用对象:字段

介绍:字段不是对象持久状态的一部分,不该该把字段和对象一块儿串起。


volatile

先补充一下概念:Java 内存模型中的可见性、原子性和有序性。

可见性:

  可见性是一种复杂的属性,由于可见性中的错误老是会违背咱们的直觉。一般,咱们没法确保执行读操做的线程能适时地看到其余线程写入的值,有时甚至是根本不可能的事情。为了确保多个线程之间对内存写入操做的可见性,必须使用同步机制。

  可见性,是指线程之间的可见性,一个线程修改的状态对另外一个线程是可见的。也就是一个线程修改的结果。另外一个线程立刻就能看到。好比:用volatile修饰的变量,就会具备可见性。volatile修饰的变量不容许线程内部缓存和重排序,即直接修改内存。因此对其余线程是可见的。可是这里须要注意一个问题,volatile只能让被他修饰内容具备可见性,但不能保证它具备原子性。好比 volatile int a = 0;以后有一个操做 a++;这个变量a具备可见性,可是a++ 依然是一个非原子操做,也就是这个操做一样存在线程安全问题。

  在 Java 中 volatile、synchronized 和 final 实现可见性。

原子性:

  原子是世界上的最小单位,具备不可分割性。好比 a=0;(a非long和double类型) 这个操做是不可分割的,那么咱们说这个操做时原子操做。再好比:a++; 这个操做实际是a = a + 1;是可分割的,因此他不是一个原子操做。非原子操做都会存在线程安全问题,须要咱们使用同步技术(sychronized)来让它变成一个原子操做。一个操做是原子操做,那么咱们称它具备原子性。java的concurrent包下提供了一些原子类,咱们能够经过阅读API来了解这些原子类的用法。好比:AtomicInteger、AtomicLong、AtomicReference等。

  在 Java 中 synchronized 和在 lock、unlock 中操做保证原子性。

有序性:

  Java 语言提供了 volatile 和 synchronized 两个关键字来保证线程之间操做的有序性,volatile 是由于其自己包含“禁止指令重排序”的语义,synchronized 是由“一个变量在同一个时刻只容许一条线程对其进行 lock 操做”这条规则得到的,此规则决定了持有同一个对象锁的两个同步块只能串行执行。

用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。volatile很容易被误用,用来进行原子性操做。
volatile强制要求了全部线程在使用变量的时候要去公共内存堆中获取值, 不能够偷懒使用本身的.
volatile绝对不保证原子性, 原子性只能用Synchronized同步修饰符实现.
下面看一个例子:

public class Counter {
 
    public static int count = 0;
 
    public static void inc() {
 
        //这里延迟1毫秒,使得结果明显
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
        }
 
        count++;
    }
 
    public static void main(String[] args) {
 
        //同时启动1000个线程,去进行i++计算,看看实际结果
 
        for (int i = 0; i < 1000; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Counter.inc();
                }
            }).start();
        }
 
        //这里每次运行的值都有可能不一样,可能为1000
        System.out.println("运行结果:Counter.count=" + Counter.count);
    }
}

运行结果:Counter.count=995
实际运算结果每次可能都不同,本机的结果为:运行结果:Counter.count=995,能够看出,在多线程的环境下,Counter.count并无指望结果是1000。
不少人觉得,这个是多线程并发问题,只须要在变量count以前加上volatile就能够避免这个问题,那咱们在修改代码看看,看看结果是否是符合咱们的指望

public class Counter {
 
    public volatile static int count = 0;
 
    public static void inc() {
 
        //这里延迟1毫秒,使得结果明显
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
        }
 
        count++;
    }
 
    public static void main(String[] args) {
 
        //同时启动1000个线程,去进行i++计算,看看实际结果
 
        for (int i = 0; i < 1000; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Counter.inc();
                }
            }).start();
        }
 
        //这里每次运行的值都有可能不一样,可能为1000
        System.out.println("运行结果:Counter.count=" + Counter.count);
    }
}

运行结果:Counter.count=992

运行结果仍是没有咱们指望的1000,下面咱们分析一下缘由:

在 java 垃圾回收整理一文中,描述了jvm运行时刻内存的分配。其中有一个内存区域是jvm虚拟机栈,每个线程运行时都有一个线程栈,线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候,首先经过对象的引用找到对应在堆内存的变量的值,而后把堆内存变量的具体值load到线程本地内存中,创建一个变量副本,以后线程就再也不和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,在修改完以后的某一个时刻(线程退出以前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。

image
read and load 从主存复制变量到当前工做内存
use and assign 执行代码,改变共享变量值
store and write 用工做内存数据刷新主存相关内容

其中use and assign 能够屡次出现
可是这一些操做并非原子性,也就是在read load以后,若是主内存count变量发生修改以后,线程工做内存中的值因为已经加载,不会产生对应的变化,因此计算出来的结果会和预期不同。

对于volatile修饰的变量,jvm虚拟机只是保证从主内存加载到线程工做内存的值是最新的

例如假如线程1,线程2 在进行read,load操做中,发现主内存中count的值都是5,那么都会加载这个最新的值。在线程1堆count进行修改以后,会write到主内存中,主内存中的count变量就会变为6,线程2因为已经进行read,load操做,在进行运算以后,也会更新主内存count的变量值为6,致使两个线程及时用volatile关键字修改以后,仍是会存在并发的状况。


测试volatile、AtomicInteger

这是美团一面面试官的一个问题:i++;在多线程环境下是否存在问题?若是存在,那怎么解决?。。。大部分人会说加锁或者synchronized同步方法。那有没有更好的方法?
示例代码:

public class IncrementTestDemo {

    public static int count = 0;
    public static Counter counter = new Counter();
    public static AtomicInteger atomicInteger = new AtomicInteger(0);
    volatile public static int countVolatile = 0;
    
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread() {
                public void run() {
                    for (int j = 0; j < 1000; j++) {
                        count++;
                        counter.increment();
                        atomicInteger.getAndIncrement();
                        countVolatile++;
                    }
                }
            }.start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("static count: " + count);
        System.out.println("Counter: " + counter.getValue());
        System.out.println("AtomicInteger: " + atomicInteger.intValue());
        System.out.println("countVolatile: " + countVolatile);
    }
    
}

class Counter {
    private int value;

    public synchronized int getValue() {
        return value;
    }

    public synchronized int increment() {
        return ++value;
    }

    public synchronized int decrement() {
        return --value;
    }
}

输出结果为:  
static count: 9952
Counter: 10000
AtomicInteger: 10000
countVolatile: 9979

经过上面的例子说明,要解决自增操做在多线程环境下线程不安全的问题,能够选择使用Java提供的原子类,或者使用synchronized同步方法。
而经过Volatile关键字,并不能解决非原子操做的线程安全性。

结论分析:虽然递增操做++i是一种紧凑的语法,使其看上去只是一个操做,但这个操做并不是原子的,于是它并不会做为一个不可分割的操做来执行。实际上,它包含了三个独立的操做:读取count的值,将值加1,而后将计算结果写入count。这是一个“读取 - 修改 - 写入”的操做序列,而且其结果状态依赖于以前的状态。

使用建议:在两个或者更多的线程访问的成员变量上使用volatile。当要访问的变量已在synchronized代码块中,或者为常量时,没必要使用。

因为使用volatile屏蔽掉了VM中必要的代码优化,因此在效率上比较低,所以必定在必要时才使用此关键字。
参考文献:Java中Volatile关键字详解
参考文献:Java自增原子性问题(测试Volatile、AtomicInteger)


transient

Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,咱们不想   
用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,能够在这个域前加上关键字transient。   
transient是Java语言的关键字,用来表示一个域不是该对象串行化的一部分。当一个对象被串行化的时候,transient型变量的值不包括在串行化的表示中,然而非transient型的变量是被包括进去的。 

注意static变量也是能够串行化的

下面使用实例能够看出效果:

public class Login implements java.io.Serializable   
{   
    private Date now = new Date();   
    private String uid;   
    private transient String pwd;   
      
    LoggingInfo(String username, String password)   
    {   
        uid = username;   
        pwd = password;   
    }   
    public String toString()   
    {   
        String password=null;   
        if(pwd == null)   
        {   
        password = "NOT SET";   
        }   
        else  
        {   
            password = pwd;   
        }   
        return "logon info: \n   " + "username: " + uid +   
            "\n   login date : " + now.toString() +   
            "\n   password: " + password;   
    }   
}

如今咱们建立一个这个类的实例,而且串行化(serialize)它 ,而后将这个串行化对象写如磁盘。

Login login = new Login("yy", "123456");   
System.out.println(login.toString());   
try  
{   
   ObjectOutputStream o = new ObjectOutputStream(   
                new FileOutputStream("login.out"));   
   o.writeObject(login);   
   o.close();   
}   
catch(Exception e) {//deal with exception}   
  
To read the object back, we can write   
  
try  
{   
   ObjectInputStream in =new ObjectInputStream(   
                new FileInputStream("logInfo.out"));   
   LoggingInfo logInfo = (LoggingInfo)in.readObject();   
   System.out.println(logInfo.toString());   
}   
catch(Exception e) {//deal with exception}

若是咱们运行这段代码,咱们会注意到从磁盘中读回(read——back (de-serializing))的对象打印password为"NOT SET"。这是当咱们定义pwd域为transient时,所指望的正确结果。
如今,让咱们来看一下粗心对待transient域可能引发的潜在问题。假设咱们修改了类定义,提供给transient域一个默认值,
代码以下:

public class Login implements java.io.Serializable   
{   
    private Date now = new Date();   
    private String uid;   
    private transient String pwd;   
      
    Login()   
    {   
        uid = "guest";   
        pwd = "guest";   
    }   
    public String toString()   
    {   
        //same as above   
     }   
}

如今,若是咱们串行化Login的一个实例,将它写入磁盘,而且再将它从磁盘中读出,咱们仍然看到读回的对象打印password 为 "NOT SET"。当从磁盘中读出某个类的实例时,实际上并不会执行这个类的构造函数, 而是载入了一个该类对象的持久化状态,并将这个状态赋值给该类的另外一个对象。

参考文献:Java transient关键字


static

Java语言的关键字,用来定义一个变量为类变量。类只维护一个类变量的拷贝,无论该类当前有多少个实例。"static" 一样可以用来定义一个方法为类方法。类方法经过类名调用而不是特定的实例,而且只能操做类变量。

static这块面试常常问的则是:Java的初始化块、静态初始化块、构造函数的执行顺序及用途
执行顺序:
写一个简单的demo来实验:

class A {
    static {
        System.out.println("Static init A.");
    }

    {
        System.out.println("Instance init A.");
    }

    A() {
        System.out.println("Constructor A.");
    }
}

class B extends A {
    static {
        System.out.println("Static init B.");
    }

    {
        System.out.println("Instance init B.");
    }

    B() {
        System.out.println("Constructor B.");
    }
}

class C extends B {

    static {
        System.out.println("Static init C.");
    }

    {
        System.out.println("Instance init C.");
    }

    C() {
        System.out.println("Constructor C.");
    }
}

public class Main {

    static {
        System.out.println("Static init Main.");
    }

    {
        System.out.println("Instance init Main.");
    }

    public Main() {
        System.out.println("Constructor Main.");
    }

    public static void main(String[] args) {
        C c = new C();
        //B b = new B();
    }

固然这里不使用内部类,由于==内部类不能使用静态的定义==;而用静态内部类就失去了通常性。
执行main方法,结果为:

Static init Main.
Static init A.
Static init B.
Static init C.
Instance init A.
Constructor A.
Instance init B.
Constructor B.
Instance init C.
Constructor C.

由以上结果咱们能够发现:
先执行了Main类的静态块,接下来分别是A、B、C类的静态块,而后是A、B、C的初始化块和构造函数。其中,Main类的构造函数没有执行。
全部的静态初始化块都优先执行,其次才是非静态的初始化块和构造函数,它们的执行顺序是:

  1. 父类的静态初始化块
  2. 子类的静态初始化块
  3. 父类的初始化块
  4. 父类的构造函数
  5. 子类的初始化块
  6. 子类的构造函数

总结

  • 静态初始化块的优先级最高,也就是最早执行,而且仅在类第一次被加载时执行
  • 非静态初始化块和构造函数后执行,而且在每次生成对象时执行一次
  • 非静态初始化块的代码会在类构造函数以前执行。所以若要使用,应当养成把初始化块写在构造 函数以前的习惯,便于调试;
  • 静态初始化块既能够用于初始化静态成员变量,也能够执行初始化代码;
  • 非静态初始化块能够针对多个重载构造函数进行代码复用。

拓展

在spring中,若是在某一个类中使用初始化块或者静态块的话,要注意一点:不能再静态块或者初始化块中使用其余注入容器的bean或者带有@Value注解的变量值,由于该静态块或者初始化块会在spring容器初始化bean以前就执行,这样的话,在块中拿到的值则为null。可是若是只是要执行一下其余的操做(没有引用其余注入容器的bean或者带有@Value注解的变量值)时,则能够代替@PostConstruct或者implement InitializingBean 类。


synchronized

synchronized,Java同步关键字。用来标记方法或者代码块是同步的。Java同步块用来避免线程竞争。同步块在Java中是同步在某个对象上。全部同步在一个对象上的同步块在同时只能被一个线程进入并执行操做。全部其余等待进入该同步块的线程将被阻塞,直到执行该同步块中的线程退出。
有四种不一样的同步块:

  • 实例方法
  • 静态方法
  • 实例方法中的同步块
  • 静态方法中的同步块

上述同步块都同步在不一样对象上。实际须要那种同步块视具体状况而定。

1.实例方法同步

下面是一个同步的实例方法:

public synchronized void add(int value){
    this.count += value;
}

在方法声明synchronized关键字,告诉Java该方法是同步的。
Java实例方法同步是同步在拥有该方法的对象上。只有一个线程可以在实例方法同步块中运行。若是有多个实例存在,那么一个线程一次能够在一个实例同步块中执行操做。一个实例一个线程。

2.静态方法同步

Java静态方法同步以下示例:

public static synchronized void add(int value){
    this.count += value;
}

一样,这里synchronized 关键字告诉Java这个方法是同步的。

静态方法的同步是指同步在该方法所在的类对象上。由于在Java虚拟机中一个类只能对应一个类对象,而静态方法是类对象所持有的,因此同时只容许一个线程执行同一个类中的静态同步方法。

3.实例方法中的同步块

在非同步的Java方法中的同步块的例子以下所示:

public void add(int value){
    synchronized(this){
       this.count += value;
    }
}

注意Java同步块构造器用括号将对象括起来。在上例中,使用了“this”,即为调用add方法的实例自己。在同步构造器中用括号括起来的对象叫作监视器对象。上述代码使用监视器对象同步,同步实例方法使用调用方法自己的实例做为监视器对象。

一次只有一个线程可以在同步于同一个监视器对象的Java方法内执行。

下面两个例子都同步他们所调用的实例对象上,所以他们在同步的执行效果上是等效的。

public class MyClass {
    public synchronized void log1(String msg1, String msg2){
        log.writeln(msg1);
        log.writeln(msg2);
    }
    public void log2(String msg1, String msg2){
        synchronized(this){
            log.writeln(msg1);
            log.writeln(msg2);
       }
    }
}

在上例中,每次只有一个线程可以在两个同步块中任意一个方法内执行。

若是第二个同步块不是同步在this实例对象上,那么两个方法能够被线程同时执行。

4.静态方法中的同步块

public class MyClass {
    public static synchronized void log1(String msg1, String msg2){
        log.writeln(msg1);
        log.writeln(msg2);
    }
    public static void log2(String msg1, String msg2){
        synchronized(MyClass.class){
            log.writeln(msg1);
            log.writeln(msg2);
       }
    }
}

这两个方法不容许同时被线程访问。
若是第二个同步块不是同步在MyClass.class这个对象上。那么这两个方法能够同时被线程访问。

关于synchronized的用法,不少人用它的时候都会理解误差。咱们来看一个例子,下面这个例子也是很经典的。

public class Demo {
    public void synchronize A() {
        //do something...
    }
    public void synchronized B() {
        //do something...
    }
    public void C() {
        synchronized(this){
            //do something
        }
    }
}

不少人认为在多线程状况下,线程执行A方法和B方法时能够同时进行的,实际上是错的。若是是不一样的实例,固然不会有影响,可是那样synchronized就会失去意义。还有一种状况下,在使用spring进行web开发时,ApplicationContext容器默认全部的bean都是单例的,因此在这种状况下,同一时间,只能有一个线程进入A方法或者B方法。这样的话,在A方法和B方法上分别加synchronized就失去了高并发的意义。C方法意义和A、B方法是同样的,都是使用当前实例做为对象锁。因此咱们尽可能避免这样使用。能够参考下面的作法。

public class Demo {
    private byte[] lock1 = new byte[0];//java中生成一个0长度的byte数组比生成一个普通的Object容易;
    private byte[] lock2 = new byte[0];
    public void synchronize A() {
        //do something...
    }
    public void B() {
        synchronized(lock) {
            //do something...    
        }
        
    }
    public void C() {
        synchronized(lock2){
            //do something
        }
    }
}

除此以外,咱们还要注意死锁的状况。在JAVA环境下 ReentrantLock和synchronized都是可重入锁,必定状况下避免了死锁。详情请参考可重入锁

相关文章
相关标签/搜索