Java常用关键字

static

字面上,意思是静态的,一旦被static修饰,说明被修饰的对象在一定范围内是共享的,这时候需要注意并发读写的问题。

static 修饰类成员

static 修饰类成员时,如何保证线程安全是我们常常需要考虑的。当多个线程同时对共享变量进行读写时,很有可能会出现并发问题,如我们定义了:public static List<String> list = new ArrayList();这样的共享变量。这个 list 如果同时被多个线程访问的话,就有线程安全的问题,这时候一般有两个解决办法:

  1. 把线程不安全的 ArrayList 换成 线程安全的 CopyOnWriteArrayList;
  2. 每次访问时,手动加锁。

static 修饰方法

当 static 修饰方法时,代表该方法和当前类是无关的,任意类都可以直接访问(如果权限是 public 的话)。
有一点需要注意的是,该方法内部只能调用同样被 static 修饰的方法,不能调用普通方法。
我们常用的 util 类里面的各种方法,我们比较喜欢用 static 修饰方法,好处就是调用特别方便,无需每次new出一个对象。
static 方法内部的变量在执行时是没有线程安全问题的。方法执行时,数据运行在栈里面,栈的数据每个线程都是隔离开的,所以不会有线程安全的问题。

static 修饰方法块

当 static 修饰方法块时,我们叫做静态代码块,静态代码块常常用于在类启动之前,初始化一些值。

final

final 的意思是不变的、最终的,一般来说用于以下三种场景:

  1. 被 final 修饰的类,表明该类是无法继承的;
  2. 被 final 修饰的方法,表明该方法是无法覆写的;
  3. 被 final 修饰的变量,说明该变量在声明的时候,就必须初始化完成,而且以后也不能修改其内存地址


注意第三点,无法修改其内存地址,并没有说无法修改其值。因为对于 List、Map 这些集合类或传址的对象来说,被 final 修饰后,是可以修改其内部值的,但却无法修改其初始化时的内存地址。

try、catch、finally


这三个关键字常用于捕捉异常,try 用来确定代码执行的范围,catch 捕捉可能会发生的异常,finally 用来执行一定要执行的代码块,除了这些,我们还需要清楚,每个地方如果发生异常会怎么样,我们举一个例子来演示一下:

public static void testError () {
    try {
        System.out.println("try code");
        if (true) {
            throw new RuntimeException("try exception");
        }
    } catch (Exception e) {
        System.out.println("catch code");
        if (true) {
            throw new RuntimeException("catch exception");
        }
    } finally {
        System.out.println("finally code");
    }
}

运行结果
image.png
可以看到两点:

  1. finally 先执行后,再抛出 catch 的异常;
  2. 最终捕获的异常是 catch 的异常,try 抛出来的异常已经被 catch 吃掉了。所以当我们遇见 catch 也有可能会抛出异常时,我们可以先打印出 try 的异常,这样 try 的异常在日志中就会有所体现。

default

default 关键字被很多源码使用,它是在jdk8时,被引入的。default 关键字用在接口的方法上,意思是对于该方法,实现类是无需强制实现的,但自己必须有默认实现。

volatile

volatile在牛津词典中,释义为易变的;无定性的;无常性的;不稳定的。在java中,它常用来修饰某个共享变量,意思是当共享变量的值被修改后,会及时通知到其它线程上,其它线程就能知道当前共享变量的值已经被修改了。个人理解它的语义:是在声明时就告诉jvm,这个变量是容易改变的,需要时刻注意它的状态。
背景知识:在多核 CPU 下,为了提高效率,线程在拿值时,是直接和 CPU 缓存打交道的,而不是内存。主要是因为 CPU 缓存执行速度更快。比如线程要拿值 C,会直接从 CPU 缓存中拿, CPU 缓存中没有,就会从内存中拿,所以线程读的操作永远都是拿 CPU 缓存的值。
这时候会产生一个问题,CPU 缓存中的值和内存中的值可能并不是时刻都同步,导致线程计算的值可能不是最新的,共享变量的值有可能已经被其它线程所修改了,但此时修改是机器内存的值,CPU 缓存的值还是老的,导致计算会出现数据不一致的问题。
这时候有个机制,就是内存会主动通知 CPU 缓存。当前共享变量的值已经失效了,你需要重新来获取一份,CPU 缓存就会重新从内存中拿取一份最新的值。
volatile 关键字就会触发这种机制,加了 volatile 关键字的变量,就会被识别成共享变量,内存中值被修改后,会通知到各个 CPU 缓存,使 CPU 缓存中的值也对应被修改,从而保证线程从 CPU 缓存中拿取出来的值是最新的。
image.png
从图中我们可以看到,线程 1 和线程 2 一开始都读取了 C 值,CPU 1 和 CPU 2 缓存中也都有了 C 值,然后线程 1 把 C 值修改了,这时候内存的值和 CPU 2 缓存中的 C 值就不等了,内存这时发现 C 值被 volatile 关键字修饰,发现其是共享变量,就会使 CPU 2 缓存中的 C 值状态置为无效,CPU 2 会从内存中重新拉取最新的值,这时候线程 2 再来读取 C 值时,读取的已经是内存中最新的值了。

面试题

为什么使用了volatile变量i,而i++还是无法保证线程安全

答:volatile 可以保证可见性,但无法保证原子性,i++已经不是原子操作了,所以即使使用 volatile 修饰,也无法保证其线程安全,比较好的做法可以参考 AtomicInteger,值使用 volatile 修饰,保证多核下的可见性,数据修改使用 unsafe 方法,保证原子性。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值