java基础——有多少是你不知道的?

java基础——有多少是你不知道的?

  • 一、&&和||
  • 二、Integer和int
  • 三、String、StringBuffer、StringBuilder的区别
  • 四、i+1
  • 五、一脸懵逼的null问题
  • 六、整数除法向上取整你知道多少种?
  • 七、这也能运行?
  • 八、多线程的bug?

一、&&和||

1、下面的程序你猜会打印什么呢?
java基础——有多少是你不知道的?_第1张图片

结果为以下:
java基础——有多少是你不知道的?_第2张图片

2、解释
&&符号当左边为false的时候就不再执行右边的判断了,因为程序已经可以确定为false了,所以b++并没有执行

||符号当左边为true的时候就不在执行左边的判断了,因为程序已经可以确定为true了,所以d++被执行了

3、这种判断机制的用途
可以解决先后判断问题,例如以下程序
java基础——有多少是你不知道的?_第3张图片
先判断非空再判断字符串长度是没有错的,因为s为空的时候,就不会去执行s.length()==5

反过来,先判断字符串长度的话会立马抛出空指针异常
java基础——有多少是你不知道的?_第4张图片

二、Integer和int

1、下面的题你能做对吗?输出结果是什么呢?
java基础——有多少是你不知道的?_第5张图片

结果:
java基础——有多少是你不知道的?_第6张图片

2、解释
Integer是int的包装类,Integer引用的是堆内存新建的对象,int引用的是java常量池里的数据

Integer和int会有拆箱装箱机制
int和Integer比较时,Integer会自动拆箱为int
int赋值Integer数据时,Integer会自动拆箱为int
Integer赋值int数据时,int会自动装箱为Integer
int和int用==比较时,比较的是值的大小
int和Integer用==比较时,Integer会自动拆箱为int,比较的是值的大小
Integer和Integer用==比较时,比较的是两个对象是否是同一个的引用

int自动装箱的小细节:对于-128~127的数据,int会判断缓存里有没有相对应值的Integer对象,如果有,直接返回该对象,否则新创建一个对象,并把对象放到缓存中。这样做是为了对于频繁使用的数据只保存一份数据,减少内存的浪费

package com.wu.hello.main;

public class Main {
    public static void main(String[] args) {
        int a=0;
        int b=0;
//        true,int和Integer用==比较时,Integer会自动拆箱为int,比较的是值的大小
        System.out.println(a==b);

        Integer c=new Integer(1);
        Integer d=new Integer(1);
//        false,Integer和Integer用==比较时,比较的是两个对象是否是同一个的引用
        System.out.println(c==d);

        Integer e=2;
        Integer f=2;
//        true,int自动装箱的小细节:对于-128~127的数据,int会判断缓存里有没有相对应值的Integer对象,
//        如果有,直接返回该对象,否则新创建一个对象,并把对象放到缓存中。
        System.out.println(e==f);

        Integer g=200;
        Integer h=200;
//        false,int自动装箱的小细节:对于-128~127的数据,int会判断缓存里有没有相对应值的Integer对象,
//        如果有,直接返回该对象,否则新创建一个对象,并把对象放到缓存中。
        System.out.println(g==h);

        Integer i=new Integer(200);
        int j=new Integer(200);
//        true,int和Integer用\=\=比较时,Integer会自动拆箱为int,比较的是值的大小
        System.out.println(i==j);
    }
}

三、String、StringBuffer、StringBuilder的区别

1、区别

String StringBuffer StringBuilder
内存位置 java常量池/堆空间 堆空间 堆空间
线程安全问题 线程安全 线程安全 线程不安全
是否可变 不可变 可变 可变
效率 最慢 一般 最快
使用场景 不需要更改时使用 多线程 单线程

2、String是不可变的是什么意思?
例如字符串s=“abc”,如果想要s=“adc”,不可以直接把字符 ‘b’ 修改为 ‘d’ ,而要新创建一个对象"adc",让s去引用它

3、String为什么是最慢的?
因为String对象是不可变的,每次修改的时候都需要产生一个新的对象
例如字符串拼接:“abc”+“d”,需要新创建一个字符串对象"abcd",而StringBuffer和StringBuilder可以直接修改当前对象

4、什么线程不安全?
在多线程的情况下会出现错误,例子:多个线程同时修改StringBuilder对象

package com.wu.hello.main;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        StringBuilder s=new StringBuilder();
        for (int i = 0; i < 10; i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 1000; j++){
                        s.append("a");
                    }
                }
            }).start();
        }
        Thread.sleep(1000);
        System.out.println(s.length());
    }
}

java基础——有多少是你不知道的?_第7张图片

报了异常,按道理应该输出10000的,因为多线程问题,使得结果发生了错误,这就是线程不安全

5、StringBuilder线程不安全的原因

因为多个线程操作的是同一个StringBuilder对象,而StringBuilder的方法调用时没有加锁,导致多个线程同时进入方法,出现不一致问题,例如线程1进入方法后获取了数据,线程2修改了字符串,那么线程1拿到的就是脏数据

而StringBuffer的每个方法是加了synchronized的,所以多线程是没问题的

java基础——有多少是你不知道的?_第8张图片

6、同样地,HashMap 、HashSet都是非线程安全的,如果在多线程环境下,可以使用 ConcurrentHashMap、ConcurrentHashSet来代替,当然效率也会降低一些

四、i+1

1、笔试题:是否存在数字 i+1
在计算机里面是存在的,Int类型的数占32位,当int是最大值的时候,再加1,那么就会变为最小值

package com.wu.hello.main;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        int maxValue = Integer.MAX_VALUE;
        System.out.println(maxValue);

        System.out.println(maxValue+1<maxValue);
    }
}

java基础——有多少是你不知道的?_第9张图片

五、一脸懵逼的null问题

1、先看程序

package com.wu.hello.main;

public class NULL {

    public static void haha(){
        System.out.println("haha");
    }

    public static void main(String[] args) throws InterruptedException {
        ((NULL)null).haha();
    }
}

2、这嘛玩意?这个程序真的可以跑的吗?真的!
java基础——有多少是你不知道的?_第10张图片

3、解释
其实NULL是类的名字,在java里面,null可以转成任何对象。转了之后也就相当于以下程序:

package com.wu.hello.main;

public class NULL {

    public static void haha(){
        System.out.println("haha");
    }

    public static void main(String[] args) throws InterruptedException {
        NULL n=null;
        n.haha();
    }
}

所以,你大概懂了吧!haha()是static方法,null对象同样可以调用!当然的,不是static方法的话就会报空指针异常了

java基础——有多少是你不知道的?_第11张图片

六、整数除法向上取整你知道多少种?

package com.wu.hello.main;

public class Main {

    public static void main(String[] args) {
        int a=20;
        int b=6;

        //方法1
        System.out.println((int)Math.ceil((double) a/b));

        //方法2
        System.out.println(a/b+(((a%b)!=0)?1:0));

        //方法3
        System.out.println((a+b-1)/b);

    }
}


java基础——有多少是你不知道的?_第12张图片

最常用的向上取整就是方法3啦

七、这也能运行?

1、先看程序

package com.wu.hello.main;

public class Main {

    public static void main(String[] args) {
        https://www.baidu.com/;
        System.out.println("haha");
    }
}

2、你觉得这个程序可以运行吗?
答案是可以滴
java基础——有多少是你不知道的?_第13张图片

3、解释
实际上的程序是这样的:

package com.wu.hello.main;

public class Main {

    public static void main(String[] args) {
        https:
        System.out.println("haha");
    }
}

实际上https: 是goto语句的写法,这种写法已经快被弃用了,原因是goto语句会使程序变得杂乱无章,程序维护困难。

可以看以下程序理解goto用法:

package com.wu.hello.main;

public class Main {

    public static void main(String[] args) {
        https:
        while (true){
            break https;
        }
        System.out.println("haha");
    }
}

java对c++的goto语法做了优化,只能接在break、continue后面,跳出循环或者是重新执行循环

八、多线程的bug?

1、认真看看以下程序,线程1和线程3都可以证明stop等于true,线程2为什么会停不下来呢?

package com.wu.hello.main;


public class Main {

    static boolean stop = false;

    public static void main(String[] args) {


//        线程1
        new Thread(()->{
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            stop=true;
            System.out.println("线程1执行完毕,stop="+stop);
        }).start();

//        线程2
        new Thread(()->{
            int i=0;
            while (!stop){
                i++;
            }
            System.out.println("线程2执行完毕,i="+i);
        }).start();

//        线程3
        new Thread(()->{
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("线程3执行完毕,stop="+stop);
        }).start();

    }
}


java基础——有多少是你不知道的?_第14张图片

2、解释

首先我们看百度的解释:
(1)、JIT

即时编译(Just-in-time Compilation,JIT)是一种通过在运行时将字节码翻译为机器码,从而改善字节码编译语言性能的技术。

(2)、热点代码

在Java的编译体系中,一个Java的源代码文件变成计算机可执行的机器指令的过程中,需要经过两段编译,第一段是把.java文件转换成.class文件。第二段编译是把.class转换成机器指令的过程。

当JVM发现某个方法或代码块运行特别频繁的时候,就会认为这是“热点代码”(Hot Spot Code)。JIT会把部分“热点代码”class翻译成本地机器相关的机器码,并进行优化,然后再把翻译后的机器码缓存起来,以备下次使用。

正是因为jit的问题,while循环被多次执行后被jit给优化了,程序已经被修改了

3、证明
在VM中添加-Xint参数,使用解释执行的意思
java基础——有多少是你不知道的?_第15张图片
java基础——有多少是你不知道的?_第16张图片
可以看到,程序执行了22428641次,最终停了下来,解决了这个问题,但是不是用jit会大大减低程序的性能,因为虚拟机不会给我们做优化了,使得被优化不会出错的程序也得不到优化

4、一个好得解决方案
对于多线程的共享资源,可以使用volatile来修饰变量,volatile可以帮我们保证程序的可见性、有序性,但是仍然不能保证原子性

package com.wu.hello.main;


public class Main {

    static volatile boolean stop = false;

    public static void main(String[] args) {


//        线程1
        new Thread(()->{
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            stop=true;
            System.out.println("线程1执行完毕,stop="+stop);
        }).start();

//        线程2
        new Thread(()->{
            int i=0;
            while (!stop){
                i++;
            }
            System.out.println("线程2执行完毕,i="+i);
        }).start();

//        线程3
        new Thread(()->{
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("线程3执行完毕,stop="+stop);
        }).start();

    }
}

java基础——有多少是你不知道的?_第17张图片

5、详细说说volatile
线程安全要考虑三个方面:可见性、有序性、原子性

可见性: 一个线程对共享变量修改,另一个线程能看到最新的结果

有序性: 一个线程内代码按编写顺序执行

原子性: 一个线程内多行代码以一个整体运行,期间不能有其它线程代码插队

程序的可见性怎么就如本例子,解决了线程2读不到stop最新值的问题

原子性就是,比如以下程序,一个线程对a+=5,另一个线程a-=5,那么a的结果一定为0?答案是否定的,5和-5也有可能出现,因为a+=5虽然是一行代码,但是编译了过后是多条指令,包括了取数据,计算,回存等等,这些操作不能被保证是原子的,线程1取到数据后,线程2把数据给修改了,线程1就读到了脏数据

package com.wu.hello.main;


import java.util.concurrent.CountDownLatch;

public class Main {
    
    static int a=0;

    public static void main(String[] args) throws InterruptedException {
        
        CountDownLatch latch = new CountDownLatch(2);

        //        线程1
        new Thread(() -> {
            a += 5;
            latch.countDown();
        }).start();

        //        线程2
        new Thread(() -> {
            a -= 5;
            latch.countDown();
        }).start();

        latch.await();

        System.out.println("a=" + a);
    }

}

有序性就是,下面的一个程序,显而易见打印的情况可能有3种:
x=0
y=0

x=1
y=1

x=0
y=1

那么,有没可能出现x=1而y=0的情况呢?答案是可能的,因为程序在编译的时候会对程序进行优化,在不改变单个线程结果的情况下,代码的执行顺序是可以改变的,也是说线程1可能会先 执行x=1;再执行y=1;,导致出现x=1而y=0的情况

package com.wu.hello.main;


import java.util.concurrent.CountDownLatch;

public class Main {

    static int x=0;
    static int y=0;

    public static void main(String[] args) throws InterruptedException {

        CountDownLatch latch = new CountDownLatch(2);

        //        线程1
        new Thread(() -> {
            y=1;
            x=1;
        }).start();

        //        线程2
        new Thread(() -> {
            System.out.println("x="+x);
            System.out.println("y="+y);
        }).start();
    }

}

volatile修饰变量可以解决这个问题

package com.wu.hello.main;


import java.util.concurrent.CountDownLatch;

public class Main {

    static volatile int x=0;
    static volatile int y=0;

    public static void main(String[] args) throws InterruptedException {

        CountDownLatch latch = new CountDownLatch(2);

        //        线程1
        new Thread(() -> {
            y=1;
            x=1;
        }).start();

        //        线程2
        new Thread(() -> {
            System.out.println("x="+x);
            System.out.println("y="+y);
        }).start();
    }

}

所以总结时volatile可以保证可见性、有序性,而原子性需要用锁机制解决

觉得写得不错就点个赞吧!

你可能感兴趣的:(java开发,java,算法,jvm)