关于一些Java题目

聊聊synchronized的CPU原语级别实现

加锁,monitorenter指令是在编译后插入到同步代码块的开始位置
放锁,monitorexit是插入到方法结束处和异常处

https://gourderwa.blog.csdn.net/article/details/103590985
偏向锁(01):一段同步代码只能被一个线程访问,不存在竞争.
轻量级锁(00):当锁是偏向锁的时候,被其他线程访问了,这时偏向锁就升级为轻量级锁.其他线程会不断自旋获取锁,不会阻塞,从而提高性能.
重量级锁(10):当只有一个等待线程,则该线程自旋等待.但自旋到一定的次数,或一个等待,一个持有锁,当第三个过来时,轻量就会变为重量级锁.
一个获取锁,其他线程进行阻塞,而不是自旋等待.

有一千万个数,写一个程序进行高效求和

1
2
3
4
5
6
7
8
9
10
11
12
13
//正常求和
static void normalSum()
{
long startTime = System.currentTimeMillis();
long N = 1000000000L;// 10亿
long sum = 0;// 0~10亿相加后的结果
for (long i = 1; i <= N; i++)
{
sum += i;
}
System.out.println(sum);// 500000000500000000
System.out.println("耗时: " + (System.currentTimeMillis() - startTime) + "ms");// 耗时: 360ms
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
//多线程求和
static void moreThreadSum() throws Exception
{
long startTime = System.currentTimeMillis();
long N = 1000000000L;//10亿
int threadNum = 10;// 线程数
long sum = 0;// 0~10亿相加后的结果
ExecutorService pool = Executors.newFixedThreadPool(threadNum);
List<Future<Long>> futures = new ArrayList<>();
for (int i = 0; i < threadNum; i++)
{
// 每个线程的:开始数 结束数
// 第一个线程计算[1,10亿]
// 第二个线程计算[10亿+1,20亿]
// ...
long start = (i * N) / threadNum;
long end = (i + 1) * N / threadNum;

Future<Long> future = pool.submit(new TempCallable(start, end));
futures.add(future);
}
pool.shutdown();// 线程池用完关闭
for (Future f : futures)
{
sum += (Long) f.get();// 把10组数据相加,求最终结果
}
System.out.println(sum);// 500000000500000000
System.out.println("耗时: " + (System.currentTimeMillis() - startTime) + "ms");// 耗时: 215ms
}

static class TempCallable implements Callable<Long>
{
long start;
long end;

public TempCallable(long start, long end)
{
this.start = start;
this.end = end;
}

@Override
public Long call() throws Exception
{
long sum = 0;
for (long i = start+1; i <= end; i++)
{
sum += i;
}
return sum;
}
}

已知根号2为1.414,如何不用数学库,求开平方的值,精确到小数点儿后面10位

未解决.

编码实现两个线程,线程A不断打印1-10的数字,要求在打印到第五个数字的时候通知线程B

  1. ReentrantLock,Condition实现
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    public class ThreadTest
    {
    public static void main(String[] args)
    {
    Lock lock = new ReentrantLock();
    Condition condition_A = lock.newCondition();
    Condition condition_B = lock.newCondition();

    new Thread(() ->
    {
    while (true)
    {
    lock.lock();
    try
    {
    for (int i = 1; i < 11; i++)
    {
    if (i == 5)
    {
    condition_B.signal();
    condition_A.await();
    continue;
    }
    System.out.println(Thread.currentThread().getName() + "---" + i);
    }
    } catch (Exception e)
    {
    e.printStackTrace();
    } finally
    {
    lock.unlock();
    }
    }
    }, "A").start();


    new Thread(() ->
    {
    while (true)
    {
    lock.lock();
    try
    {
    condition_A.signal();
    condition_B.await();
    System.out.println("通知了5...");
    } catch (Exception e)
    {
    e.printStackTrace();
    } finally
    {
    lock.unlock();
    }
    }
    }, "B").start();
    }
    }
  2. synchronized,Object实现
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    public class ThreadTest2
    {
    public static void main(String[] args)
    {
    Object object=new Object();

    new Thread(() ->
    {
    while (true)
    {
    synchronized (object)
    {
    for (int i = 1; i < 11; i++)
    {
    if (i == 5)
    {
    object.notify();
    try
    {
    object.wait();
    } catch (InterruptedException e)
    {
    e.printStackTrace();
    }
    continue;
    }
    System.out.println(Thread.currentThread().getName() + "---" + i);
    }
    }
    }
    }, "A").start();

    new Thread(() ->
    {
    while (true)
    {
    synchronized (object)
    {
    object.notify();
    try
    {
    object.wait();
    } catch (InterruptedException e)
    {
    e.printStackTrace();
    }
    System.out.println("通知了5...");
    }
    }
    }, "B").start();
    }
    }

自定义线程池需要指定哪7个参数,为什么不建议使用JUC内置线程池?

核心,最大,存活时间,时间单位,队列,工厂,策略.

使用fix和single允许队列长度是Integer.Max,可能堆积大量请求,OOM
使用cache和schedule允许创建线程数是Integer.Max,可能创建大量线程,OOM

高并发、任务执行时间短的业务怎样使用线程池?

线程池线程数可以设置为CPU核数+1,减少线程上下文的切换

并发不高、任务执行时间长的业务怎样使用线程池?

a)假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要让所有的CPU闲下来,可以加大线程池中的线程数目,让CPU处理更多的业务
b)假如是业务时间长集中在计算操作上,也就是计算密集型任务,这个就没办法了,和(1)一样吧,线程池中的线程数设置得少一些,减少线程上下文的切换

并发高、业务执行时间长的业务怎样使用线程池?

解决这种类型任务的关键不在于线程池而在于整体架构的设计,看看这些业务里面某些数据是否能做缓存是第一步,增加服务器是第二步,至于线程池的设置,设置参考(2)。
最后,业务执行时间长的问题,也可能需要分析一下,看看能不能使用中间件对任务进行拆分和解耦。

设计一个12306网站,能够撑住最高百万级别TPS(淘宝最高54万TPS),你该如何实现?

未解决.

为什么等待和通知是在 Object 类而不是 Thread 中声明的?

wait()等待线程和notify()之间是通过锁来关联的
等待和通知都基于锁进行使用,而锁是对象持有的,而且每个对象只有一个.

为什么Java中不允许多重继承?

假设 A 类里有个方法A 返回是 int 1 ;B类有个方法也叫A,返回2;
当C 继承A ,同时也继承B,调用父类的方法A是来自A类还是B类,返回1还是2。

String 在 Java 中是为什么不可变?

因为它设置了final,
String对象缓存在String池中的,这些缓存的字符串是共享数据,多线程可以安全共享.
HashMap使用它作为key,如果key要是可变的,就会找不到对应值的情况.

为什么HashMap线程不安全?

首先HashMap是线程不安全的,其主要体现:

  1. 在jdk1.7中,在多线程环境下,扩容时会造成环形链或数据丢失。(不作分析)
  2. 在jdk1.8中,在多线程环境下,会发生数据覆盖的情况。
    1
    2
    6         if ((p = tab[i = (n - 1) & hash]) == null) // 如果没有hash碰撞则直接插入元素
    7 tab[i] = newNode(hash, key, value, null);
    这是jdk1.8中HashMap中put操作的主函数, 注意第6行代码,如果没有hash碰撞则会直接插入元素。
    如果线程A和线程B同时进行put操作,刚好这两条不同的数据hash值一样,并且该位置数据为null,所以这线程A、B都会进入第6行代码中。
    假设一种情况,线程A进入后还未进行数据插入时挂起,而线程B正常执行,从而正常插入数据,然后线程A获取CPU时间片,此时线程A不用再进行hash判断了,
    问题出现:线程A会把线程B插入的数据给覆盖,发生线程不安全。

为什么重写equals方法就必须重写hashcode方法?

因为两个对象equals相同,hashcode一定相同.
如果hashcode相同(hash冲突),equals可能true,可能false.
hashcode不同(hash不冲突),equals绝对不同.

对于对象集合的判重,如果一个集合含有10000个对象实例,仅仅使用equals()方法的话,那么对于一个对象判重就需要比较10000次,随着集合规模的增大,时间开销是很大的。
但是同时使用哈希表的话,就能快速定位到对象的大概存储位置,并且在定位到大概存储位置后,后续比较过程中,如果两个对象的hashCode不相同,也不再需要调用equals()方法,从而大大减少了equals()比较次数。

比如new 两个学生出来,两个学生的name和age一样,如果仅仅自定义重写了equals方法(比较name和age),那么这两个学生是相同的,视为同一个学生,
但是事实在堆中实际上是有两个学生对象,是两个不同的地址,hashCode是jdk根据对象的地址或者字符串或者数字算出来的int类型的数值(百度来的),
所以说这两个对象的hashcode有很大概率不同,如果把这两个学生,put到HashMap中,或者任何以哈希表为数据结构的集合中,都会出现两个”一样”的学生,这就违背了我们的初衷,我们使用HashMap,HashSet就是想为了去重复和效率,
但是如果重写了equals而不重写hashcode,就达不到这样的效果.因为把这些对象塞进集合之前,会先判断hashcode,hashcode不同,直接塞进集合中,不去比较equals方法,这样你何必重写equals?所以产生矛盾.何必呢?

为什么IO流中read(byte[] b) 更有效率呢?

FileInputStream fis = new FileInputStream(“demo.mp4”);
fis.read()与fis.read(byte[] b)有什么区别?
为什么说后者比前者效率更高?

我们知道前者一次读一个字节,后者一次读一个字节数组.
就像喝水,一次喝一滴一次喝一杯的区别,但是,一杯水的形成不还是得一滴一滴得积攒吗?
也就是说,后者一次读一个字符数组不还是要先一个一个的把字符读进数组里吗?
这样来看,后者仅仅是做了一个封装操作,这样怎么提高效率了呢?

看了一些资料,我的理解是.
如果是读一个,写一个.那就是从硬盘读n次,然后n次.
如是是读一批,写一批,那就是从硬盘读n次,然后放在内存中的这个字节数组b中,然后写一次,

假如有5个字节.加入byte[] b = new byte[5];

  1. 第一种方法read(),write(),需要读硬盘5次,写硬盘5次.
  2. 第二种方法read(b),write(new string(b,0,5)),需要读硬盘5次,写硬盘1次.
    如果字节很多,如果b的容量更大,这样效率优势会更加明显,因为一个是暂存到内存中,俗称缓冲区,然后集体写入.