Java中的关键字——this、static、final、volatile

1、this

Java关键字this必须放在非静态方法里面, this只能用于方法方法体内。当一个对象创建后,Java虚拟机(JVM)就会给这个对象分配一个引用自身的指针,这个指针的名字就是 this。因此,this只能在类中的非静态方法中使用,静态方法和静态的代码块中绝对不能出现this,并且this只和特定的对象关联,而不和类关联,同一个类的不同对象有不同的this。

this指向调用该方法的对象,作为对象的默认引用有两种形式:

1、构造器中引用该构造器执行初始化;

2、在方法中引用调该方法的对象。

this可以让类中的一个方法访问类中的另一个方法或属性。当this在某个方法体中代表的对象是当前类。

this主要要3种用法:

1、表示对当前对象的引用;

2、表示用类的成员变量,而非函数参数 ;

3、用于在构造方法中引用满足指定参数类型的构造器(其实也就是构造方法)。但是这里必须非常注意:只能引用一个构造方法且必须位于开始!

public class TestThis
{
    public static void main(String[] args)
    {
        Dog dog = new Dog();
        dog.run();
        
        //get();//静态方法不能访问非静态方法
    }
    
    public void get()
    {
        System.out.println("test");
    }
}

class Dog
{
    public void jump()
    {
        System.out.println("jump方法");
    }
    
    // run方法借助jump方法
    public void run()
    {
        this.jump();
        System.out.println("run方法");
    }
}

博客:https://blog.csdn.net/u012176204/article/details/54580232

2、static

Java把内存分为栈内存和堆内存,其中栈内存用来存放一些基本类型的变量、数组和对象的引用,堆内存主要存放一些对象。在JVM加载一个类的时候,若该类存在static修饰的成员变量和成员方法,则会为这些成员变量和成员方法在固定的位置开辟一个固定大小的内存区域。static声明的属性与普通属性(非static)最大的区别就是:保存内存区域的不同。

static用来修饰成员变量、成员方法和代码块。

static修饰的成员变量和成员方法独立于该类的任何对象,它不依赖类特定的实例,被类的所有实例共享。可以使用类直接调用该方法,Static修饰的方法不能使用this引用,因为无法指向适合的对象,所以static修饰的方法不能访问不使用static修饰的普通成员,即静态成员不能访问非静态成员。

1、static变量

static修饰的变量为静态变量,没有用static修饰的变量称之为实例变量,两者的区别是:静态变量是随着类加载时被完成初始化的,它在内存中仅有一个,且JVM也只会为它分配一次内存,同时类所有的实例都共享静态变量,可以直接通过类名来访问它。

2、static方法

static方法就是没有this的方法。方便在没有创建对象的情况下来进行调用(方法/变量)。在static方法内部不能调用非静态方法,反过来是可以的

static修饰的方法为静态方法,通过类名对其进行直接调用。在不创建对象的情况下调用某个方法,就可以设置为static。我们最常见的static方法就是main方法,至于为什么main方法必须是static的,现在就很清楚了。因为程序在执行main方法的时候没有创建任何对象,因此只有通过类名来访问。

3、static代码块

被static修饰的代码块,我们称之为静态代码块,以优化程序性能。

通过以上说明:

(1)使用static定义的属性不在堆内存之中保存,保存在全局数据区;

(2)使用static定义的属性表示类属性,类属性可以由类名称直接进行调用;

(3)static属性虽然定义在类中,但是其可以在没有实例化对象的时候进行调用(普通属性保存在堆内存里,而static属性保存在全局数据区之中)。

Java把内存分为栈内存和堆内存,其中栈内存用来存放一些基本类型的变量、数组和对象的引用,堆内存主要存放一些对象。在JVM加载一个类的时候,若该类存在static修饰的成员变量和成员方法,则会为这些成员变量和成员方法在固定的位置开辟一个固定大小的内存区域。static声明的属性与普通属性(非static)最大的区别就是:保存内存区域的不同。

static用来修饰成员变量、成员方法和代码块。

static修饰的成员变量和成员方法独立于该类的任何对象,它不依赖类特定的实例,被类的所有实例共享。可以使用类直接调用该方法,Static修饰的方法不能使用this引用,因为无法指向适合的对象,所以static修饰的方法不能访问不使用static修饰的普通成员,即静态成员不能访问非静态成员。

1、static变量

static修饰的变量为静态变量,没有用static修饰的变量称之为实例变量,两者的区别是:静态变量是随着类加载时被完成初始化的,它在内存中仅有一个,且JVM也只会为它分配一次内存,同时类所有的实例都共享静态变量,可以直接通过类名来访问它。

2、static方法

static方法就是没有this的方法。方便在没有创建对象的情况下来进行调用(方法/变量)。在static方法内部不能调用非静态方法,反过来是可以的

static修饰的方法为静态方法,通过类名对其进行直接调用。在不创建对象的情况下调用某个方法,就可以设置为static。我们最常见的static方法就是main方法,至于为什么main方法必须是static的,现在就很清楚了。因为程序在执行main方法的时候没有创建任何对象,因此只有通过类名来访问。

3、static代码块

被static修饰的代码块,我们称之为静态代码块,以优化程序性能。

通过以上说明:

(1)使用static定义的属性不在堆内存之中保存,保存在全局数据区;

(2)使用static定义的属性表示类属性,类属性可以由类名称直接进行调用;

(3)static属性虽然定义在类中,但是其可以在没有实例化对象的时候进行调用(普通属性保存在堆内存里,而static属性保存在全局数据区之中)。

博客:https://www.cnblogs.com/dolphin0520/p/3799052.html

 


public class MyObject
{
    private static String str1 = "static property";
    private String str2 = "property";

    public MyObject()
    {
    }

    /**
     * 非静态方法可以访问静态方法和变量
     */
    public void print1()
    {
        String str = "static property";
        System.out.println(str1);
        System.out.println(str2);
        print2();
    }

    public static void print2()
    {
        System.out.println(str1);
        // System.out.println(str2);//静态不能访问非静态
        // print1();
    }

    public static void main(String args[])
    {
        MyObject object = new MyObject();
        object.print1();//通过实例访问
        MyObject.print2();//通过类名访问
    }
}

博客:https://www.cnblogs.com/dolphin0520/p/3799052.html

3、final

final:“不可变的”

final关键字可以用来修饰类、方法和变量(包括成员变量和局部变量)。

1、修饰类

当用final修饰一个类时,表明这个类不能被继承。

public final class People
{
   public People(){      
   }
}

class Man extends People{
    
}

2、修饰方法

    把方法锁定,该方法无法被重写,以防任何继承类修改它的含义。

    因此,如果只有在想明确禁止该方法在子类中被覆盖的情况下才将方法设置为final的。

注:类的private方法会隐式地被指定为final方法。

3、修饰变量

final修饰基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象,但可以改变引用对象的值。

public class Test
{
    public static void main(String[] args)
    {
        final int value1 = 1;
        //value1 = 4;//final修饰一旦在初始化之后便不能更改
       
        final MyClass myClass = new MyClass();
        System.out.println(++myClass.i);//输出结果为1。这说明引用变量被final修饰之后,虽然不能再指向其他对象,但是它指向的对象的内容是可变的。
        //myClass = new MyClass();//不能再指向其他对象
    }
}

class MyClass {
    public int i = 0;
}

博客:https://www.cnblogs.com/dolphin0520/p/3736238.html、https://www.cnblogs.com/dotgua/p/6357951.html

4、volatile

volatile是一种轻量级的同步机制,它主要有2个特性:

1、保证共享变量对所有线程的可见性;

2、禁止指令重排序优化。

在并发编程中我们一般都会遇到这三个基本概念:原子性、可见性、有序性。

1、原子性:一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

volatile关键字保证变量在多线程之间的可见性。

2、可见性:一个线程修改了变量之后,新值对其他Java线程都是可以立即得知,即线程每次获取volatile变量的值都是最新的。一句话:当一个变量被volatile修饰之后保证这个变量对所有的Java线程可见性。

       实现:各个线程中volatile变量可以不一致,但是每次读操作都要先刷新从主内存中获取最新值。每一个写操作后会立即刷新到主内存。不用其修饰的不会立即刷新到主内存。

volatile变量具有 synchronized 的可见性特性,但是不具备原子特性。

3、有序性

有序性:即程序执行的顺序按照代码的先后顺序执行。

Java提供volatile来保证一定的有序性,禁止指令重排序优化。最著名的例子就是单例模式里面的DCL(双重检查锁)。

在Java内存模型中,为了效率允许编译器和处理器对指令进行重排序,重排序它不会影响单线程的运行结果,但是对多线程会有影响。

重排序是指编译器和处理器为了优化程序性能而对指令序列进行排序的一种手段。但是重排序也需要遵守一定规则: 1、重排序操作不会对存在数据依赖关系的操作进行重排序。

比如:a=1;b=a;;这个指令序列,由于第二个操作依赖于第一个操作,所以在编译时和处理器运行时这两个操作不会被重排序。2.重排序是为了优化性能,但是不管怎么重排序,单线程下程序的执行结果不能被改变。

如:a=1;b=2;c=a+b这三个操作,第一步(a=1)和第二步(b=2)由于不存在数据依赖关系,所以可能会发生重排序,但是c=a+b这个操作是不会被重排序的,因为需要保证最终的结果一定是c=a+b=3。

同时需要注意的是,volatile对于单个的共享变量的读/写具有原子性,但是像num++这种复合操作,volatile无法保证其原子性。用volatile不能保证线程安全。

解决num++操作的原子性问题:

import java.util.concurrent.CountDownLatch;

public class CounterA
{
    // 使用原子操作类
    public static volatile int num = 0;
    // 使用CountDownLatch来等待计算线程执行完
    static CountDownLatch countDownLatch = new CountDownLatch(30);

    public static void main(String[] args) throws InterruptedException
    {
        // 开启30个线程进行累加操作
        for (int i = 0; i < 30; i++)
        {
            new Thread()
            {
                public void run()
                {
                    for (int j = 0; j < 10000; j++)
                    {
                        num++;// 自加操作
                    }
                    countDownLatch.countDown();
                }
            }.start();
        }
        // 等待计算线程执行完
        countDownLatch.await();
        System.out.println(num);
    }
}

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

public class Counter
{
    // 使用原子操作类
    public static AtomicInteger num = new AtomicInteger(0);
    // 使用CountDownLatch来等待计算线程执行完
    static CountDownLatch countDownLatch = new CountDownLatch(30);

    public static void main(String[] args) throws InterruptedException
    {
        // 开启30个线程进行累加操作
        for (int i = 0; i < 30; i++)
        {
            new Thread()
            {
                public void run()
                {
                    for (int j = 0; j < 10000; j++)
                    {
                        num.incrementAndGet();// 原子性的num++,通过循环CAS方式
                    }
                    countDownLatch.countDown();
                }
            }.start();
        }
        // 等待计算线程执行完
        countDownLatch.await();
        System.out.println(num);
    }

}

 

通过关键字sychronize可以防止多个线程进入同一段代码,在某些特定场景中,volatile相当于一个轻量级的sychronize,因为不会引起线程的上下文切换,但是使用volatile必须满足两个条件:1、对变量的写操作不依赖当前值,如多线程下执行a++,是无法通过volatile保证结果准确性的;2、该变量没有包含在具有其它变量的不变式中。

优秀参考博文:https://www.cnblogs.com/chengxiao/p/6528109.html、https://www.cnblogs.com/shoshana-kong/p/9066888.html

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值