java面试题库冲刺2-多线程基础知识

114 阅读8分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

1.并发和并行有和区别

并发:多个任务在同一个CPU上,按照细分的时间片轮流交替执行,由于时间很短,看上去好像是同时进行的。 并行:单位时间内,多个处理器或多核处理器同时处理多个任务,是真正意义上的同时进行。 串行:有n个任务,由一个线程按照顺序执行。

2.线程与进程

进程:内存中运行的运用程序,每个进程都有自己独立的内存空间,一个进程可以由多个线程,例如在Windows系统中,xxx.exe就是一个进程。 线程:进程中的一个控制单元,负责当前进程中的程序执行,一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可以共享数据。

3.死锁

死锁是指两个或两个以上进程(线程)在执行过程中,由于竞争资源或由于彼此通信造成的一种堵塞的现象,若无外力的作用下,都将无法推进,此时的系统处于死锁状态。 四个必要条件 1.互斥条件:线程(进程)对所分配的资源具有排它性,即一个资源只能被一个进程占用,直到该进程被释放。 2.请求与保持条件:一个进程(线程)因请求被占有资源而发生堵塞时,对已获取的资源保持不放。 3.不剥夺条件:线程(进程)已获取的资源在未使用完之前不能被其他线程强行剥夺,只有等自己使用完才释放资源。 4.循环等待条件:当发生死锁时,所等待的线程(进程)必定形成一个环路,死循环造成永久堵塞。 如何避免死锁 我们只需破坏形参死锁的四个必要条件之一即可。 1.破坏互斥条件:无法破坏,我们的🔒本身就是来个线程(进程)来产生互斥 2.破坏请求与保持条件:一次申请所有资源 3.破坏不剥夺条件:占有部分资源的线程尝试申请其它资源,如果申请不到,可以主动释放它占有的资源。 4.破坏循环等待条件:按序来申请资源。

3.创建线程的四种方式

1.继承Thread类

public class MyThread extends Thread{//继承Thread类
    @Override
  public void run(){
  }
}

public class Main {
  public static void main(String[] args){
    new MyThread().start();//创建并启动线程
  }
}

朗姆塔表达式写法

        Thread t1 = new Thread(){
            @Override
            public void run() {
                System.out.println("第1种方式:new Thread 1");
            }
        };
        t1.start();

2.实现Runnable接口

public class MyThread implements Runnable{//继承Thread类
    @Override
  public void run(){
  }
}

public class Main {
  public static void main(String[] args){
       MyThread myThread=new MyThread();
    new Thread(myThread).start();//创建并启动线程
  }
}

朗姆塔表达式写法

      Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("第2种方式:new Thread 2");
            }
        });
        t2.start();

3.实现Callable接口

call()方法可以有返回值 call()方法可以声明抛出异常


public static class MyThread3 implements Callable{
 
    @Override
    public Object call() throws Exception {
        return 5;
    }
}

public class Main {
  public static void main(String[] args){
   MyThread3 th=new MyThread3();
     //使用FutureTask类来包装Callable对象
   FutureTask<Integer> future=new FutureTask<Integer>(
    (Callable<Integer>)()->{undefined
      return 5;
    }
    );
   new Thread(future,"有返回值的线程").start();//实质上还是以Callable对象来创建并启动线程
    try{
    System.out.println("子线程的返回值:"+future.get());//get()方法会阻塞,直到子线程执行结束才返回
    }catch(Exception e){undefined
    ex.printStackTrace();
   }
  }
}

朗姆塔表达式写法

 FutureTask<String> ft = new FutureTask<>(new Callable<String>() {
            @Override
            public String call() throws Exception {
                String result = "第3种方式:new Thread 3";
                return result;
            }
        });
        Thread t3 = new Thread(ft);
        t3.start();

4.Executors工具类创建线程池 朗姆塔表达式写法

        ExecutorService pool=Executors.newCachedThreadPool(); 
        //可以用继承Thread类或者实现Runnable接口 只是启动方式不同
        Thread t4 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("第2种方式:new Thread 2");
            }
        });
        pool.execute(t4);

4.线程声明周期的6种状态

新创建:又称初始化状态,这个时候Thread才刚刚被new出来,还没有被启动。 可运行状态:表示已经调用Thread的start方法启动了,随时等待CPU的调度,此状态又被称为就绪状态。 被终止:死亡状态,表示已经正常执行完线程体run()中的方法了或者因为没有捕获的异常而终止run()方法了。 计时状态:调用sleep(参数)或wait(参数)后线程进入计时状态,睡眠时间到了或wait时间到了,再或者其它线程调用notify并获取到锁之后开始进入可运行状态。另一种情况,其它线程调用notify没有获取到锁或者wait时间到没有获取到锁时,进入堵塞状态。 无线等待状态:获取锁对象后,调用wait()方法,释放锁进入无线等待状态 锁堵塞状态:wait(参数)时间到或者其它线程调用notify后没有获取到锁对象都会进入堵塞状态,只要一获取到锁对象就会进入可运行状态。

5.Java线程同步和线程调度的相关方法

wait():调用后线程进入无限等待状态,并释放所持对象的锁 sleep():使一个线程进入休眠状态(堵塞状态),带有对象锁,是一个静态方法,需要处理InterruptException异常。 notify():唤醒一个处于等待状态的线程(无线等待或计时等待),如果多个线程在等待,并不能确切的唤醒一个线程,与JVM确定唤醒那个线程,与其优先级有关。 notityAll():唤醒所有处于等待状态的线程,但是并不是将对象的锁给所有的线程,而是让它们去竞争,谁先获取到锁,谁先进入就绪状态。

6.sleep()和wait()有什么区别

类不同:sleep()是Thread下的静态方法,wait()是Object类下的方法 是否释放锁:sleep()不释放锁,wait()释放锁 用处不同:wait()常用于线程间的通信,sleep()常用于暂停执行。 用法不同:wait()用完后,线程不会自动执行,必须调用notify()或notifyAll()方法才能执行,sleep()方法调用后,线程经过过一定时间会自动苏醒,wait(参数)也可以传参数使其苏醒。它们苏醒后还有所区别,因为wait()会释放锁,所以苏醒后没有获取到锁就进入堵塞状态,获取到锁就进入就绪状态,而sleep苏醒后之间进入就绪状态,但是如果cpu不空闲,则进入的是就绪状态的堵塞队列中。

7. 如何停止一个正在运行的线程?

使用stop方法终止,但是这个方法已经过期,不被推荐使用。 使用interrupt方法终止线程 run方法执行结束,正常退出

8.线程安全问题

线程安全是指某个方法在多线程的环境下被调用时,能够正确处理多线程之间的共享变量,能程序能够正确完成。 Java中是如何保证多线程安全的? 使用安全类,比如 java.util.concurrent 下的类,使用原子类AtomicInteger 使用自动锁,synchronized锁 Lock lock = new ReentrantLock(),使用手动锁lock .lock(),lock.unlock()方法

9. 你对线程优先级有什么理解?

每个线程都具有优先级的,一般来说,高优先级的在线程调度时会具有优先被调用权。我们可以自定义线程的优先级,但这并不能保证高优先级又在低优先级前被调用,只是说概率有点大。 线程优先级是1-10,1代表最低,10代表最高。 Java的线程优先级调度会委托操作系统来完成,所以与具体的操作系统优先级也有关,所以如非特别需要,一般不去修改优先级。

10.谈谈你对乐观锁和悲观锁的理解?

乐观锁:每个去拿数据的时候都认为别人不会修改,所以不会都不会上锁,但是在更新的时候会判断一下在此期间有没有去更新这个数据。所以乐观锁使用了多读的场合,这样可以提高吞吐量,像数据库提供的类似write_condition机制,都是用的乐观锁,还有那个原子变量类,在java.util.concurrent.atomic包下 悲观锁:总是假设最坏的情况,每次去拿数据的时候都会认为有人会修改,所以每次在拿数据的时候都会上锁。这样别的对象想拿到数据,那就必须堵塞,直到拿到锁。传统的关系型数据库用到了很多这种锁机制,比如读锁,写锁,在操作之前都会先上锁,再比如Java的同步代码块synchronized/方法用的也是悲观锁。