tosting 方法

public class ClassA {
    
    private String name;
    private int age=1;
    
    @Override//方法的重写
    public String toString() { //为了直接获取A类的名和变量值
        return "ClassA [name=" + name + ", age=" + age + "]";
    }
    
    public static void main(String[] args) {
        ClassA a1 = new ClassA();
        System.out.println(a1);
    }
}

该种方法,可以用来检查代码的内容的正确性。

 当一个共享变量被volatile修饰时,它会保证修改的值立即被更新到主存“, 这里的”保证“ 是如何做到的?和 JIT的具体编译后的CPU指令相关吧?

  volatile特性

  内存可见性:通俗来说就是,线程A对一个volatile变量的修改,对于其它线程来说是可见的,即线程每次获取volatile变量的值都是最新的。

  volatile的使用场景

  通过关键字sychronize可以防止多个线程进入同一段代码,在某些特定场景中,volatile相当于一个轻量级的sychronize,因为不会引起线程的上下文切换,但是使用volatile必须满足两个条件:

  1、对变量的写操作不依赖当前值,如多线程下执行a++,是无法通过volatile保证结果准确性的;

  2、该变量没有包含在具有其它变量的不变式中,这句话有点拗口(目前本人暂未研究明白)。

public class TestVolatile {
    
    public static void main(String[] args) {
        ThreadDemo td = new ThreadDemo();
        new Thread(td).start();//启动一个线程,start方法会去调用下面重写的run方法
        while (true) {
            if (td.isFlag()){
                System.out.println("xxxxxxxxxxxxxxx");
                break;
            }
        }
    }

}
//从上面知道了,该函数一共会启动两个线程,并且这个两个线程共用了flag变量,(同进程中,线程是共享进程中的数据)

class ThreadDemo implements Runnable {
    
    private  boolean flag = false;

    @Override
    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        flag = true;
        System.out.println("flag= " + flag);
    }
    
    public boolean isFlag() {
        return flag;
    }
    
}
普通变量

从执行结果可以看到:在有些情况下,不能执行以下语句:System.out.println("xxxxxxxxxxxxxxx");

原因:就是因为未进行内存可见性,因为主线程和次线程从工作内存中拷贝的flag都是false。

public class TestVolatile {
    
    public static void main(String[] args) {
        ThreadDemo td = new ThreadDemo();
        new Thread(td).start();
        while (true) {
            if (td.isFlag()){
                System.out.println("xxxxxxxxxxxxxxx");
                break;
            }
        }
    }

}

class ThreadDemo implements Runnable {
    
    private volatile boolean flag = false;//仅仅在此处多加了一个关键字:volatile

    @Override
    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        flag = true;
        System.out.println("flag= " + flag);
    }
    
    public boolean isFlag() {
        return flag;
    }
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
    
}
volatile变量

从执行结果可以看到:在有些情况下,执行了以下语句:System.out.println("xxxxxxxxxxxxxxx");

原因:就是因为进行了内存可见性。

如何保证内存可见性?

 下面看看操作普通变量和volatile变量有什么不同:

  1、对于普通变量:读操作会优先读取工作内存的数据,如果工作内存中不存在,则从主内存中拷贝一份数据到工作内存中;写操作只会修改工作内存的副本数据,这种情况下,其它线程就无法读取变量的最新值。

  2、对于volatile变量,读操作时JMM会把工作内存中对应的值设为无效,要求线程从主内存中读取数据;写操作时JMM会把工作内存中对应的数据刷新到主内存中,这种情况下,其它线程就可以读取变量的最新值。

CPU为了处理性能,并不直接和内存进行通信,而是将内存的数据读取到内部缓存(L1,L2)再进行操作,但操作完并不能确定何时写回到内存,如果对volatile变量进行写操作,当CPU执行到Lock前缀指令时,会将这个变量所在缓存行的数据写回到内存,不过还是存在一个问题,就算内存的数据是最新的,其它CPU缓存的还是旧值,所以为了保证各个CPU的缓存一致性,每个CPU通过嗅探在总线上传播的数据来检查自己缓存的数据有效性,当发现自己缓存行对应的内存地址的数据被修改,就会将该缓存行设置成无效状态,当CPU读取该变量时,发现所在的缓存行被设置为无效,就会重新从内存中读取数据到缓存中。

但是volatile是原子性的吗?

public class Test {
    public volatile int inc = 0;
     
    public void increase() {
        inc++;
    }
     
    public static void main(String[] args) {
        final Test test = new Test();
        for(int i=0;i<10;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<1000;j++)
                        test.increase();
                };
            }.start();
        }
         
        while(Thread.activeCount()>1)  //保证前面的线程都执行完
            Thread.yield();
        System.out.println(test.inc);
    }
}
示例

大家想一下这段程序的输出结果是多少?也许有些朋友认为是10000。但是事实上运行结果每次不一致而且都是一个小于10000的数字。

  可能有的朋友就会有疑问,不对啊,上面是对变量inc进行自增操作,由于volatile保证了可见性,那么在每个线程中对inc自增完之后,在其他线程中都能看到修改后的值啊,所以有10个线程分别进行了1000次操作,那么最终inc的值应该是1000*10=10000。

假如某时刻变量inc的值为10,

  线程1对变量进行自增操作,线程1先读取了变量inc值10,然后线程1被阻塞了(注意时间点:线程1已 经读取了内存数据,并往下开始执行了,在此时他被阻塞了。);

  然后线程2对变量进行自增操作,线程2也去读取变量inc的原始值,由于线程1只是对变量inc进行读取操作,还没有对变量进行修改操作,所以不会导致线程2的工作内存中缓存变量inc的缓存行无效,所以线程2会直接去主存读取inc的值,发现inc的值时10,然后进行加1操作,并把11写入工作内存,最后写入主存。

  然后线程1接着进行加1操作,由于已经读取了inc的值,注意此时在线程1的工作内存中inc的值仍然为10,所以线程1对inc进行加1操作后inc的值为11,然后将11写入工作内存,最后写入主存。

  那么两个线程分别进行了一次自增操作后,inc却只增加了1。

  解释到这里,可能有朋友会有疑问,不对啊,前面不是保证一个变量在修改volatile变量时,会让缓存行无效吗?我们一定要注意时间点,线程1对变量进行读取操作之后,被阻塞了的话,并没有对inc值进行修改。然后虽然volatile能保证线程2对变量inc的值读取是从内存中读取的,但是线程1没有进行修改,所以线程2根本就不会看到修改的值。

  这里面的误区是:volatile关键字能保证可见性没有错,但是上面的程序错在没能保证原子性。可见性只能保证每次读取的是最新的值,但是volatile没办法保证对变量的操作的原子性。注意:自增操作是不具备原子性的,它包括读取变量的原始值、进行加1操作、写入工作内存。那么就是说自增操作的 一共包含三个子操作,由于时间片的原因,这三步就由可能会分割开执行。那就导致线程1读取了inc ,然后就被切换了时间片,此时线程2又去读inc值。并往下执行。

解释原子性:https://www.cnblogs.com/dolphin0520/p/3920373.html

父类构造方法和子类构造方法

1.当有构造方法的时候,建议再写一份无参数的构造方法,

2.当父类有构造方法,子类必须写构造方法,并在构造方法中去调用父类的构造方法

public class tostring {
    public String name="xxx";
    int age;
    public tostring(int age){ //有参数的构造方法
        this.age=age;
    }
    public tostring(){//无参数的构造方法,如果需调用,就必须写
        this.age=age;
    }
    public static void main(String[] args) {
        tostring a1 = new tostring();//调用无参构造方法
        tostring a2 = new tostring(1);//调用有参构造方法
    }
}
父类程序
public class ClassA extends tostring{
    
    public ClassA(int age) {  //只能使用子类的构造放方法去调用父类构造方法
        super(age); //调用父类构造方法
        // TODO Auto-generated constructor stub
    }

    public static void main(String[] args) {
        tostring a1 = new tostring();  //直接调用无参的构造方法
        ClassA aa=new ClassA(10);   
        System.out.println(aa.age);  //调用父类的变量
        System.out.println(a1.name);
    }
    
}
子类程序

从以上学习,我们应该对子父类的继承有所认识了。

posted on 2018-06-30 13:09  进_进  阅读(112)  评论(0)    收藏  举报