ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

并发编程相关面试题

2020-03-29 15:53:45  阅读:152  来源: 互联网

标签:面试题 run Thread void 编程 并发 线程 执行 public


1.进程和线程还有协程之间的关系

  进程:

    进程简单理解就是我们平常使用的程序,如QQ,浏览器,网盘等。进程拥有自己独立的内存空间地址,

    拥有一个以上的线程。

  线程:

    线程可以理解为轻量级的进程,是程序执行的最小单元。在某个进程启动后,会默认产生一个主线程,

    主线程可以创建多个子线程,

            

 

              

 

 

       

 

 

 

  协成:

      协成,又称微线程,纤程。协成是一个线程执行,但执行有点像多线程,协成的执行效率极高

      简单点说协成是进程和 线程的升级版,进程喝 线程都面临着内内核态和用户态的切换问题而

    耗费许多切换时间 ,而且协成就是用户自己控制切换的时机,不再需要 陷入系统的内核态。

 

  一个程序至少有一个进程 ,一个进项至少有一个线程。线程不能独立执行,必须依存在进程中。

  协成,应用程序级别(而非操作系统)控制切换,以此来提升 效率。


2.并发和并行之间的区别

  并行(parallel):

        指在同一时刻,有多条指令在多个处理器上同时执行。所以无论从微观还是宏观 来看

        二者都是一起执行的。

        

  小结:

    当系统有一个以上 CPU 时,则线程的操作有可能非并发。当一个 CPU 执行一个线程时,另一个

     CPU 可以执行另一个线程,两个线程互不抢占 CPU 资源,可以同时进行,这种方式我们称之为并行(Parallel)。

 

 

 


  并发(concurrency):

        指在同一时刻只能有一条指令执行,但多个线程指令被快速的轮换执行,使得

        在宏观上具有多个线程同时执行的效果 ,但微观上并不是同时执行的,只是把

        时间分成若干段,使多个进程快速交替的执行

            

  小结:

    当有多个线程在操作时,如果系统只有一个 CPU,则它根本不可能真正同时进行一个以上的线程,

    它只能把 CPU 运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间段的

    线程代码运行时,其它线程处于挂起状态.这种方式我们称之为并发(Concurrent)。

  
3.Java中多线程实现的方式
  Java多线程实现方式主要有四种:继承Thread类、实现Runnable接口、实现Callable接口通过FutureTask包装器来创建Thread线程、

  使用ExecutorService、Callable、Future实现有返回结果的多线程。

  其中前两种方式线程执行完后都没有返回值,后两种是带返回值的。


  1、继承Thread类创建线程
    Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。

    start()方法是一个native方法,它将启动一个新线程,并执行run()方法。这种方式实现多线程很简单,通过自己的类直接extend Thread,

    并复写run()方法,就可以启动新线程并执行自己定义的run()方法。例如:  

public class MyThread extends Thread {  
  public void run() {  
   System.out.println("MyThread.run()");  
  }  
}  
 
MyThread myThread1 = new MyThread();  
MyThread myThread2 = new MyThread();  
myThread1.start();  
myThread2.start(); 

  

  2、实现Runnable接口创建线程
  如果自己的类已经extends另一个类,就无法直接extends Thread,此时,可以实现一个Runnable接口,如下:  

public class MyThread extends OtherClass implements Runnable {  
  public void run() {  
   System.out.println("MyThread.run()");  
  }  
}  

  为了启动MyThread,需要首先实例化一个Thread,并传入自己的MyThread实例: 

MyThread myThread = new MyThread();  
Thread thread = new Thread(myThread);  
thread.start();  

  3、实现Callable接口通过FutureTask包装器来创建Thread线程

  Callable接口(也只有一个方法)定义如下:     

public interface Callable<V>   { 
  V call() throws Exception;   } 
public class SomeCallable<V> extends OtherClass implements Callable<V> {

    @Override
    public V call() throws Exception {
        // TODO Auto-generated method stub
        return null;
    }

}
Callable<V> oneCallable = new SomeCallable<V>();   
//由Callable<Integer>创建一个FutureTask<Integer>对象:   
FutureTask<V> oneTask = new FutureTask<V>(oneCallable);   
//注释:FutureTask<Integer>是一个包装器,它通过接受Callable<Integer>来创建,它同时实现了Future和Runnable接口。 
  //由FutureTask<Integer>创建一个Thread对象:   
Thread oneThread = new Thread(oneTask);   
oneThread.start();   
//至此,一个线程就创建完成了。

 

4.Callable和Future模式

 https://www.cnblogs.com/szhhhh/p/12553278.html

5.线程池创建的方式(一般不适用Excecutors.nexxxx创建,一般使用ThreadPoolExecutor)

  Java通过Executors(jdk1.5并发包)提供四种线程池,分别为:
  newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。  

public static void main(String[] args) {
        //1.创建可缓存的线程池,可重复利用
        ExecutorService newExecutorService = Executors.newCachedThreadPool();
        //创建了10个线程
        for (int i = 0; i < 10; i++) {
            int temp = i;
            newExecutorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("threadName;"+Thread.currentThread().getName()+",i"+temp);
                }
            });
        }

    }

 


  newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

 public static void main(String[] args) {
        //1.创建可固定长度的线程池
        ExecutorService newExecutorService = Executors.newFixedThreadPool(3);
        //创建了10个线程
        for (int i = 0; i < 10; i++) {
            int temp = i;
            newExecutorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("threadName;"+Thread.currentThread().getName()+",i"+temp);
                }
            });
        }

    }

 


  newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

 public static void main(String[] args) {
        //1.创建可定时线程池
        ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(5);
        for (int i = 0; i < 10; i++) {
            final int temp = i;
            newScheduledThreadPool.schedule(new Runnable() {
                public void run() {
                    System.out.println("i:" + temp);
                }
            }, 3, TimeUnit.SECONDS);
        }

    }

 


  newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

public static void main(String[] args) {
        //1.创建单线程
        ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            newSingleThreadExecutor.execute(new Runnable() {

                @Override
                public void run() {
                    System.out.println("index:" + index);
                    try {
                        Thread.sleep(200);
                    } catch (Exception e) {
                        // TODO: handle exception
                    }
                }
            });
        }
        newSingleThreadExecutor.shutdown();
    }

 


6.Java当中线程状态有哪些

  1. 创建状态:在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态。

  2.就绪状态:当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,

    此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态。

  3.运行状态:线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。

  4.阻塞状态:线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend,wait等方法都可以导致线程阻塞。

  5.死亡状态:如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡。对于已经死亡的线程,无法再使用start方法令其进入就绪。

 

 

7.多线程中的常用方法  

  7.1 public void start()  使该线程开始执行;Java 虚拟机调用该线程的 run 方法。

  7.2 public void run() 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。

  7.3. public final void setName(String name) 改变线程名称,使之与参数 name 相同

  7.4 public final void setPriority(int piority) 更改线程的优先级。

  7.5 public final void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。

  7.6 public final void join(long millisec) 等待该线程终止的时间最长为 millis 毫秒。

  7.7 public void interrupt() 中断线程。

  7.8 public final boolean isAlive() 测试线程是否处于活动状态。

  7.9 public static void static yield() 暂停当前正在执行的线程对象,并执行其他线程。

  7.10 public static void sleep(long millisec) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。

  7.11 public static Thread currentThread() 返回对当前正在执行的线程对象的引用。

  

 

 

 


8.线程状态流程图
    
9.volatile关键字有什么用途,和Synchronize有什么区别

  volatile:

    volatile用来修饰变量例如:Thread类里面的表示名字的字符数组其作用是保证数据的可见性和有序性,但它并不能保证数据的原子性

    

 

 

 

    因为volatile不能保证原子性,所以就需要关键字 synchronized

  synchronized:

    它用于修饰代码块和方法,可以弥补volatile关键字的不足,即它能保证对数据操作的原子性,在多个线程对数据进行操作时,保证线程的安全

  小结:    

    1. 修饰对象不同,volatile用于修饰变量,synchronized用与对语句和方法加锁;
    2. 各自作用不同,volatile保证数据的可见性和有序性,但它并不能保证数据的原子性,synchronized可以保证原子性;
    3. volatile不会造成线程堵塞,而synchronized会造成线程堵塞;

  脏读:

    脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,

    另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是脏数据,

    依据脏数据所做的操作可能是不正确的。

10.指令重排和先行发生原则
  指令重排:

     在计算机执行指令的顺序在经过程序编译器编译之后形成的指令序列,一般而言,这个指令序列是会输出确定的结果;

    以确保每一次的执行都有确定的结果。但是,一般情况下,CPU和编译器为了提升程序执行的效率,会按照一定的规则

    允许进行指令优化,在某些情况下,这种优化会带来一些执行的逻辑问题,主要的原因是代码逻辑之间是存在一定的先后顺序,

    在并发执行情况下,会发生二义性,即按照不同的执行逻辑,会得到不同的结果信息。

  先行发生原则 :

    先行发生是Java内存模型中定义的两项操作之间的偏序关系,如果说操作A先行发生于操作B,

    就是说A产生的影响能被B观察到,“影响”包括修改了内存中的共享变量值、发送了消息、调用了方法等。

    例如:   

// 线程A中执行
i = 1;

// 线程B中执行
j = i;

// 线程C中执行
i = 2;

  如果说线程A是先行发生于线程B的,那么可以确定在线程B执行之后 j=1,因为根据先行发生原则,A操作 i = 1 的结果可以被B观察到,并且线程C还没有执行。


11.并发编程线程安全三要素

  当多个线程要共享一个实例对象的值得时候,那么在考虑安全的多线程并发编程时就要

  保证下面3个要素:     

    原子性(Synchronized, Lock):

      即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

    有序性(Volatile,Synchronized, Lock):

      即程序执行的顺序按照代码的先后顺序执行。

    可见性(Volatile,Synchronized,Lock):

      指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

      当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取共享变量时,它会去内存中读取新值。



12.进程和线程间调度算法:

  1. 先来先服务(队列)

  2. 最短优先(优先队列)

  3. 高优先权优先调度算

    3.1 优先权调度算法的类型

    3.2 高响应比优先调度算法

  4. 基于时间片的轮转调度算法

    4.1 时间片轮转法

    4.2  多级反馈队列调度算法

  5. 电梯调度算法


13.Java开发中用过哪些锁:

  乐观锁:

      顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间

    别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,在Java中java.util.concurrent.atomic、

    包下面的原子变量类就是使用了乐观锁的一种实现方式CAS(Compare and Swap 比较并交换)实现的。

  悲观锁:

      总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,

    这样别人想拿这个数据就会阻塞直到它拿到锁。比如Java里面的同步原语synchronized关键字的实现就是悲观锁。

  

  独享锁:

      是指该锁一次只能被一个线程所持有。

  共享锁:

      是指该锁可被多个线程所持有。

  

  互斥锁:

      在Java中的具体实现就是ReentrantLock。

  读写锁:

      在Java中的具体实现就是ReadWriteLock。

  可重入锁:

      又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。

  

  公平锁:

      是指多个线程按照申请锁的顺序来获取锁。

  非公平锁:

      是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。

  分段锁:

      其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。

  自旋锁:

      是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。


 分布式中:
  分布式锁:

      分布式锁是控制分布式系统或不同系统之间共同访问共享资源的一种锁实现,如果不同的系统或同一个系统的不同主机之间共享了某个资源时,

      往往需要互斥来防止彼此干扰来保证一致性。
 数据库中:
  行锁:

     开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率低,并发度高

  表锁:

    开销小,加锁快;不会出现死锁;锁定力度大,发生锁冲突概率高,并发度最低

  页锁:

    开销和加锁速度介于表锁和行锁之间;会出现死锁;锁定粒度介于表锁和行锁之间,并发度一般


14.sync关键字理解
    Synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
  

标签:面试题,run,Thread,void,编程,并发,线程,执行,public
来源: https://www.cnblogs.com/szhhhh/p/12592691.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有