Concurrent face questions summary

 

 1. What are threads and processes?

Process : the ability to run independently of the operating system, and as a basic unit of resource allocation. It indicates that the program running. The system is running a program is a process from creation, to run the process of extinction.

Thread : is a smaller unit than the execution of the process, to complete a functional process, also known as lightweight processes. A process may spawn multiple threads during its execution.

[Note] threads and processes is different: the same kind of multiple threads share the process of heap and method area resources , but each thread has its own program counter , the virtual machine stack and native method stacks , so the system generates a thread, or when is switching between threads for work, the burden is much smaller than the process.

Why is the program counter, stack and virtual machine thread private native method stacks is it? Why heap and method area is shared by the threads of it?

  • Why the program counter is private?

The program counter are the following two functions:

    1. Bytecode interpreter program counter is changed by sequentially reading instruction, in order to achieve flow control code, such as: the order of execution, selection cycles, exception handling.
    2. In the case of multiple threads, the recording position of the program counter for execution of the current thread, so that when the thread is switched back to know where the thread last ran to.

(Note that, if you perform a native method, then the program counter record is undefined address, only the execution of Java code that is when the next instruction of the program counter address recorded.)

Therefore, the program counter is mainly to private after thread switching can be restored to the correct execution position .

  •  VM stack and native method stacks Why is private?
  • VM stackeach Java method is created in the same time frame for the implementation of a stack local variables to store information table, operand stack, constant pool references and so on. Until completion of the execution from the call process method, it corresponds to a process of pushing and popping the stack frame of the stack in the Java virtual machine .
  • Native method stacks : stack and the virtual machine is very similar to the role, the difference is: VM stack for the Java virtual machine to perform method (ie bytecode) services, native method stacks, the virtual machine to use a method of Native Service . In the HotSpot virtual machine and the Java virtual machine stacks combined.

Therefore, in order to ensure the thread local variables are not accessible to other threads , virtual machine stacks and stacks native method is thread private.

 

Heap and method area is shared by all threads of resources , including:

  • The heap is the biggest piece of the process of memory, mainly used to store the newly created object (all objects allocate memory here) .
  • A method for storing area is mainly based information has been loaded, a constant, static variables, the real-time data such as the code compiler .

 2. What is the context switch?

  Even if single-core processor also supports multi-threaded code execution, CPU mechanism to achieve this by allocating CPU time slices to each thread . CPU time slice is assigned to each thread of time, because time is very short piece, so the CPU by constantly switching threads of execution, makes us feel multiple threads are executing simultaneously. (Time slice is typically tens of milliseconds)

  It will switch to the next task allocation algorithm by the CPU cycles to perform the task time slice, a time slice of the current task execution. However, before the switch will save the state of a task, so that the next state to switch back to the task, you can then load this task. So the task is to save a context switch from the loading process . Context switching speed will affect the execution of multiple threads .

 3, concurrent and parallel?

   Concurrency refers to the number of tasks alternate, parallel refers to "simultaneously" in the true sense .

  In fact, if only one of the CPU, the use of multi-threading, not parallel in a real system environment, the system can only be switched alternately by way of a time slice, for concurrently executing tasks. The real parallel can only occur in a system with multiple CPU.

 4, life cycle and thread status? (Important!)

 Java threads running at a given moment in the life cycle may only be in the following six different states in one of the states:

  The initial state, running state, blocking state, wait state, a timeout wait state, end state

In the life cycle of the thread is not fixed in one state, but with the execution of the code to switch between different states :

  • After the thread is created it will be in the initial state (NEW), call the  start() start method to run after, this time in the thread  can run state (READY).
  • Threads running state can be obtained after a CPU time slice is in  the operating state (RUNNING).
  • 线程执行 wait()方法之后,线程进入 等待状态(WAITING),进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态【notify()】。 而 超时等待状态(TIME_WAITING)相当于在等待状态的基础上增加了超时限制,【sleep(long millis)/wait(long millis)】,当超时时间到达后 Java 线程将会返回到运行状态
  • 当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到阻塞状态(BLOCKED)。
  • 线程在执行 Runnable 的run()方法之后将会进入到 终止状态(TERMINATED)。

5、什么是线程死锁?如何避免死锁?

   多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

  假如线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。

避免死锁的几个常见方法:

  • 避免一个线程同时获取多个锁
  • 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
  • 尝试使用定时锁,使用 lock.tryLock(timeout) 来代替使用内部锁机制。
  • 对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。

 6、sleep() 方法和 wait() 方法区别和共同点?(重要!)

 相同点:

  两者都可以暂停线程的执行,都会让线程进入阻塞状态。

不同点:

  • sleep()方法没有释放锁,而 wait()方法释放了锁。
  • sleep()方法属于Thread类的静态方法,作用于当前线程;而wait()方法是Object类的实例方法,作用于对象本身。
  • 执行sleep()方法后,可以通过超时或者调用interrupt()方法interrupt()方法唤醒休眠中的线程;执行wait()方法后,通过调用notify()或notifyAll()方法唤醒等待线程。

7、为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?

  new 一个 Thread,线程进入初始状态;调用 start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。 而直接执行 run() 方法,会把 run 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作

总结: 调用 start 方法可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行

8、 synchronized 关键字

synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。

synchronized关键字最主要的三种使用方式:修饰实例方法:、修饰静态方法、修饰代码块。

  • 对于普通同步方法,锁是当前实例对象。
  • 对于静态同步方法,锁是当前类的Class对象。
  • 对于同步方法块,锁是synchronized括号里配置的对象。

  当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。

synchronized在JVM里是怎么实现的?

  synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor的持有权。当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。

(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 

synchronized用的锁是存在哪里的?

  synchronized用到的锁是存在Java对象头里的。

9、说说 JDK1.6 之后的synchronized 关键字底层做了哪些优化,可以详细介绍一下这些优化吗

  JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。

  锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。

  关于这几种优化的详细信息可以查看这篇文章:https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/java/Multithread/synchronized.md

10、synchronized和ReentrantLock(重入锁) 的区别?

  • 两者都是可重进入锁,就是能够支持一个线程对资源的重复加锁。sychnronized关键字隐式的支持重进入,比如一个sychnronized修饰的递归方法,在方法执行时,执行线程在获取了锁之后仍能连续多次地获取该锁。ReentrantLock虽然没能像sychnronized关键字一样隐式的重进入,但是在调用lock()方法时,已经获取到锁的线程,能够再次调用lock()方法获取锁而不被阻塞
    • 线程重复n次获取了锁,随后在第n次释放该锁后,其他线程能够获取到该锁。锁的最终释放要求锁对于获取进行计数自增,计数表示当前锁被重复获取的次数,而锁被释放时,计数自减,当计数等于0时表示锁已经成功被释放
  • synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API。ReentrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成)
  • ReentrantLock 比 synchronized 增加了一些高级功能,主要有3点:①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)
    • ReentrantLock提供了一种能够中断等待锁的线程的机制,也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。通过lock.lockInterruptibly()来实现这个机制。
    • ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。(公平锁就是先等待的线程先获得锁)
    • synchronized关键字与wait()和notify()/notifyAll()方法相结合可以实现等待/通知机制。ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。用ReentrantLock类结合Condition实例可以实现“选择性通知” 。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程

11、volatile关键字

  保证共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。

  把变量声明为volatile,这就指示 JVM每次使用它都到主存中进行读取。

12、synchronized 关键字和 volatile 关键字的区别

  • volatile关键字是线程同步的轻量级实现,所以volatile性能比synchronized关键字要好。但是volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块
  • 多线程访问volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞。、
  • volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized关键字解决的是多个线程之间访问资源的同步性
  • volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字两者都能保证。

13、使用线程池的好处?

  1. 降低资源消耗。通过重复利用已创建的线程,降低线程创建和销毁造成的消耗
  2. 提高响应速度当任务到达时,任务可以不需要等到线程创建就能立即执行
  3. 提高线程的可管理性线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控

14、说一说几种常见的线程池即适用场景?

  可以创建(Executors.newXXX)3种类型的ThreadPoolExecutor:FixedThreadPoolSingleThreadExecutorCachedThreadPool

  • FixedThreadPool可重用固定线程数的线程池。(适用于负载比较重的服务器)
    • FixedThreadPool使用无界队列LinkedBlockingQueue作为线程池的工作队列
    • 该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。
  • SingleThreadExecutor只会创建一个线程执行任务。(适用于需要保证顺序执行各个任务;并且在任意时间点,没有多线程活动的场景。)
    • SingleThreadExecutorl也使用无界队列LinkedBlockingQueue作为工作队列
    • 若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。
  • CachedThreadPool:是一个会根据需要调整线程数量的线程池(大小无界,适用于执行很多的短期异步任务的小程序,或负载较轻的服务器)
    • CachedThreadPool使用没有容量的SynchronousQueue作为线程池的工作队列,但CachedThreadPool的maximumPool是无界的。
    • 线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。
  • ScheduledThreadPool:继承自ThreadPoolExecutor。它主要用来在给定的延迟之后运行任务,或者定期执行任务。使用DelayQueue作为任务队列。

15、线程池都有哪几种工作队列?

  • ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序。
  • LinkedBlockingQueue:是一个基于链表结构的阻塞队列,此队列按FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
  • SynchronousQueue:是一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于Linked-BlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
  • PriorityBlockingQueue:一个具有优先级的无限阻塞队列

16、创建线程的几种方式?

有4种方式:继承Thread类实现Runnable接口实现Callable接口使用Executor框架来创建线程池

(1)通过继承Thread类创建线程

public class MyThread extends Thread {//继承Thread类

  //重写run方法

  public void run(){

  }

}

----------------------------------------------------------------------------------

public class Main {

  public static void main(String[] args){

    new MyThread().start(); //创建并启动线程

  }

}

(2)通过实现Runnable接口来创建线程

 

public class MyThread2 implements Runnable {//实现Runnable接口

  //重写run方法

  public void run(){

  }

}

------------------------------------------------------------------------------------------

public class Main {

  public static void main(String[] args){

    //创建并启动线程

    MyThread2 myThread=new MyThread2();

    Thread thread=new Thread(myThread);

    thread().start();

    //或者    new Thread(new MyThread2()).start();

  }

}

不管是继承Thread还是实现Runnable接口,多线程代码都是通过运行Thread的start()方法来运行的。

(3)实现Callable接口来创建线程

  与实现Runnable接口类似,和Runnable接口不同的是,Callable接口提供了一个call() 方法作为线程执行体,call()方法比run()方法功能要强大:call()方法可以有返回值、call()方法可以声明抛出异常。

public class Main {

  public static void main(String[] args){

   MyThread3 th=new MyThread3();

   //使用Lambda表达式创建Callable对象

     //使用FutureTask类来包装Callable对象

   FutureTask<Integer> future=new FutureTask<Integer>(

    (Callable<Integer>)()->{

      return 5;

    }

    );

   new Thread(task,"有返回值的线程").start();//实质上还是以Callable对象来创建并启动线程

    try{

    System.out.println("子线程的返回值:"+future.get());//get()方法会阻塞,直到子线程执行结束才返回

    }catch(Exception e){

    ex.printStackTrace();

   }

  }

}

(4)使用Executor框架来创建线程池

  Executors.newXXXX: newFixedThreadPool(int )、newSingleThreadExecutor、newCachedThreadPool、newScheduledThreadPool(int)

   通过Executors的以上四个静态工厂方法获得 ExecutorService实例而后可以执行Runnable任务或Callable任务。

  • Executor执行Runnable任务

  通过Executors的以上四个静态工厂方法获得 ExecutorService实例,而后调用该实例的execute(Runnable command)方法即可。一旦Runnable任务传递到execute()方法,该方法便会自动在一个线程上。

public class TestCachedThreadPool{   
    public static void main(String[] args){   
        ExecutorService executorService = Executors.newCachedThreadPool();    
        for (int i = 0; i < 5; i++){   
            executorService.execute(new TestRunnable());   
            System.out.println("************* a" + i + " *************");   
        }   
        executorService.shutdown();   
    }   
}   
class TestRunnable implements Runnable{  
//重写run方法
public void run(){ System.out.println(Thread.currentThread().getName() + "线程被调用了。"); }
  • Executor执行Callable任务:

  当将一个Callable的对象传递给ExecutorService的submit方法,则该call方法自动在一个线程上执行,并且会返回执行结果Future对象。同样,将Runnable的对象传递给ExecutorService的submit方法,则该run方法自动在一个线程上执行,并且会返回执行结果Future对象,但是在该Future对象上调用get方法,将返回null。

public class CallableDemo{   
    public static void main(String[] args){   
        ExecutorService executorService = Executors.newCachedThreadPool();   
        List<Future<String>> resultList = new ArrayList<Future<String>>();   
  
        //创建10个任务并执行   
        for (int i = 0; i < 10; i++){   
            //使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中   
            Future<String> future = executorService.submit(new TaskWithResult(i));   
            //将任务执行结果存储到List中   
            resultList.add(future);   
        }   
  
        //遍历任务的结果   
        for (Future<String> fs : resultList){   
                try{   
                    while(!fs.isDone);//Future返回如果没有完成,则一直循环等待,直到Future返回完成  
                    System.out.println(fs.get());     //打印各个线程(任务)执行的结果   
                }catch(InterruptedException e){   
                    e.printStackTrace();   
                }catch(ExecutionException e){   
                    e.printStackTrace();   
                }finally{   
                    //启动一次顺序关闭,执行以前提交的任务,但不接受新任务  
                    executorService.shutdown();   
                }   
        }   
    }   
}   
class TaskWithResult implements Callable<String>{   
    private int id;   
  
    public TaskWithResult(int id){   
        this.id = id;   
    }   
  
    // 重写call()方法
    public String call() throws Exception {  
        System.out.println("call()方法被自动调用!!!    " + Thread.currentThread().getName());   
        //该返回结果将被Future的get方法得到  
        return "call()方法被自动调用,任务返回的结果是:" + id + "    " + Thread.currentThread().getName();   
    }   
}  

 

实现Runnable接口和Callable接口的区别?

 Runnable接口或Callable接口实现类都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。两者的区别在于 Runnable 接口不会返回结果但是 Callable 接口可以返回结果

执行execute()方法和submit()方法的区别是什么呢?

1) execute() 方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;

2) submit() 方法用于提交需要返回值的任务。线程池会返回一个Future类型的对象,通过这个Future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。

 

Guess you like

Origin www.cnblogs.com/toria/p/11234323.html