java随机笔记二(面试复习用)

一.抽象类与接口在使用中的区别:

一)抽象类:使用格式为:public abstract class A{}

1、在抽象类中的方法可以定义抽象方法,也可以是具体的成员方法,不能是(static)类方法。

public  void get(){ System.out.println("ok")};

public  abstract void geta(String abc);//ok

public  static abstract void geta(String abc);//错误

2、在抽象类中属性可以是成员属性,也可以是类属性。

private String name;//ok
private static String age;//ok

3、在抽象类中有构造方法,默认是无参(没有参数列表)的,也可以有参数的构造方法,但是不能用来实例化对象(不能使用new去创建对象)。

4、抽象类是用来充当父类,给子类去继承与发展的,当子类继承抽象类时,方法需要定义访问限定符,而且子类必须重写所有的抽象方法,且子类不能减小可见范围(访问限定符的设定不能减小)。

5、一个类只能继承一个抽象类。

继承的方法如下:public class B extends A{}

二)接口

使用格式为:public interface C{}

1、在接口中的方法只能定义抽象方法,不能有方法体,定义时默认为public abstract,可以省略。接口中的方法默认是public,也只能是public。

eg: [public abstract] void I; ----无返回值

[public abstract] int J; ----有返回值

2、接口中属性的定义时固定的:

public static final 数据类型 变量名 =初始值;

eg: public static final int a =0;

public static final String aa =null;

3、接口中不能有构造方法,更加不能创建对象。

4、接口也是用来充当父类的,给子类去实现与扩展的,当子类实现接口时,必须重写接口中所有的方法。且子类不能减小可见范围(访问限定符的设置不能减小)。

5、一个类可以实现多个接口。

public class E inmplements C,D{}

一个类可以先继承一个类,再实现多个接口

public class F extends E implements C,D{}

二.classpath和path的用处和区别

一)计算机编程中classpath和path的用处和区别

一般它们在什么时候使用

java来举例,我们想要运行java文件,比如我们安装jdk的时候,我们需要将bin指定到path,这样才行,当我们运行jar包时,需要把jar包放在当前路径,或者classpath下,才可以运行成功,那么你们有没有想过为什么要这样做,他们二者的区别是什么?

path,classpath配置好之后有什么用?

  1. path配置好之后,让java jdk\bin目录下的工具,可以在任意目录下运行,原因是,将该工具所在目录告诉了系统,当使用该工具时,由系统帮我们去找指定的目录。

  2. 如果没有定义环境变量classpath,java启动jvm后,会在当前目录下查找要运行的类文件,如果指定了classpath,那么会在指定的目录下查找要运行的类文件。还会在当前目录找吗?两种情况:第一种,如果classpath的值结尾处有分号,在具体路径中没有找到运行的类,会默认在当前目录再找一次。第二种,如果classpath的值结果出没有分号,在具体的路径中没有找到运行的类,不会再当前目录找。常用:一般不指定分号,如果没有在指定目录下找到要运行的类文件,就报错,这样可以调试程序

3.path配置方式

永久配置方式:JAVA_HOME=%安装路径%\Java\jdk path=%JAVA_HOME%\bin搭配配置

临时配置方式:set path=%path%;C:\Program Files\Java\jdk\bin

4.classpath如何配置

永久配置方式:classpath=.;c:\;e:\

临时配置方式:set classpath=.;c:\;e:\

三.写出满意的单例模式

单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例。

今天我们不谈单例模式的用途,只说一说如果在面试的时候面试官让你敲一段代码实现单例模式的情况下怎样写出让面试官眼前一亮的单例代码。因为笔者学的是Java,所以接下来的实例将用Java语言编写。

说到单例模式,第一个想到的是该类中有一个初始化为null的自身引用,且被private修饰符修饰,其它类不得直接访问。除此之外,单例模式的类还需要有private的构造方法,这一点不难理解,如果构造方法是public的,那么类外部可以直接调用该类的构造方法,如此一来便不具备单例的特性。那么怎么获取该类唯一的实例呢?这就需要一个公有的获取器,该方法返回值类型是单例模式类,返回的结果自然是该类中唯一的实例。思路有了,我们便可以实现最简单的单例模式类:

不得不说,这样的做法确实达到了单例模式的要求,正常情况下系统中只有一个Singleton的对象。但是如果存在并发的情况呢?两个用户同时访问该类的获取器,此时假设Singleton对象还未被实例化,那么系统将会两次调用构造方法,这样一来系统中就会存在两个Singleton类的实例。说明这种方式的单例没有考虑到并发情况,说明面试者只是粗略的了解单例模式,并没有加以深入思考,想让面试官满意?

Java相较于C++而言个人认为编程的难易度上来说要容易很多。在考虑线程同步时一个synchronized关键字便能解决普通加锁问题。synchronized关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。也就是说当两个线程同时访问类中synchronized方法或代码块时,只能有一个线程执行其代码,另一个只能等待当前线程调用结束后才能访问。这下子单例的实现就so easy了!只要对代码稍加改动即可:

这样的写法面试官会觉得你这个面试者在思考问题的时候比较全面,考虑到并发的情况,相较之前的方式面试官会觉得:少年,很有前途哦!

然而光是让面试官看好是不够的,我们要让他欣赏,通过单例这样的小问题便能拿到offer。也就是说第二种实现方式是可以进行优化的。如何优化呢?我们看到,当前系统中每次调用获取方法时便会进行加锁,而加锁需要的时间便是我们可以进行优化的地方。现在我所想的是我们只需要在第一次调用时加一次锁往后便再也不不需要加锁了,这样一来便省下了每次调用加锁的时间,虽然计算机执行加锁的时间很短但久而久之也是相当长的一段时间。

那么怎么实现呢?这需要引入另一个关键字volatile。volatile修饰的话就可以确保instance = new Singleton();对应的指令不会重排序(JVM当发现代码执行顺序变化但结果不变时可能会改变执行顺序来提升自身性能。好坑。。。),也是线程安全的。

volatile关键字的含义

在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉。

Java语言是支持多线程的,为了解决线程并发的问题,在语言内部引入了 同步块 和 volatile 关键字机制。

synchronized 

同步块大家都比较熟悉,通过 synchronized 关键字来实现,所有加上synchronized 和 块语句,在多线程访问的时候,同一时刻只能有一个线程能够用

synchronized 修饰的方法 或者 代码块。

volatile

用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最新值。volatile很容易被误用,用来进行原子性操作。

下面看一个例子,我们实现一个计数器,每次线程启动的时候,会调用计数器inc方法,对计数器进行加一

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);

    }

}

 

 

1

运行结果: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到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,

在修改完之后的某一个时刻(线程退出之前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。下面一幅图

描述这写交互

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关键字修改之后,还是会存在并发的情况。


对于值引用来说,多线程操作的是变量的副本,操作完后刷新到主存中。而对于地址引用,多线程是通过地址操作的是同一个变量。volatitle关键字告诉编译器,直接去通过地址操作变量,而不是变量的副本

 

 

转载于:https://my.oschina.net/u/1054538/blog/816326

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值