Thread 多线程问题解答

 
1.Java 实现线程得方式有几种
 继承Thread类  (无返回值)
 实现Runnable接口  (无返回值)
 实现Callable接口,同时通过FutureTask包装器来创建线程(有返回值)
(所有线程启动必须用start(),run方法是不会创建新线程得,它只是一个普通方法,注意看源码)
 
2.如何停止一个线程
    调用Thread中得stop(),suspend(),resume()(不推荐使用这种方法,这三个方法本身也是过期得,因为是强制停止,所以内存啊,数据啥的都可能有毛病。)
    正确应该使用interrupt() 或者 在线程中Boolean判断;
    上代码,演示一下几种情况:
    第一种: 
    class Thr1 extends Thread{
        @Override
        public void run(){
             try {
                 TimeUnit.SECONDS.sleep(1); 
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
        }
    }
 
 
  上边代码中,除了可以用sleep外还能用wait,join 这些方法都需要捕获InterruptedException(中断异常),此时Thr1线程在运行中可以直接调用      interrupt()方法终端。
   第二种
           Thread t3 = new Thread(new Runnable() {                
                public void run() {
                      while(true) {
                           System.err.println("这样子写的话就不能直接用interrupt中断了");
                           //所以这里需要手动判断一下
                           if(Thread.interrupted()) {
                                 break;
                           }
                      }
                }
           });
线程中没用用到sleep等方法时就需要用到上面这种形式了,或者第三种方法(上面的方法有几点需要注意,中断的3个相关方法是Thread种的方法,其中interrupted() 是静态方法,区别下边再讲)。
第三种
class THrq extends Thread{
     //如果考虑多线程,想要保证isboolean的可见性,可以考虑加上volatile()
     public boolean isboolean = false;
     public void run() {
                while(true) {
                      if(isboolean) {
                           break;
                      }    
                }
     }
}
 
 
3.notify()和notifyAll()的区别 (需要注意的是wait,notify,notifyAll 这三个方法大都搭配synchronized使用,ReentrantLock 有别的方式)
    notify() 和 notifyAll() 都是搭配wait使用的,意在唤醒沉睡的线程。它们的调用对象必须是synchronized对象锁( 注意不是类锁)否则会报错。
    notify()唤醒随机一个线程
    notifyAll()唤醒所有线程
    总结了一下网上靠谱的说法:  锁池和等待池(属于对象锁)
    锁池:等待获取锁的线程(调用了notify,notifyAll被唤醒的线程)
     等待池:调用了wait方法,处于暂停等待状态的线程
这篇文章讲的比较好可以参考一下: https://blog.csdn.net/wangmx1993328/article/details/80703971
这个讲的也特别好: https://blog.csdn.net/djzhao/article/details/79410229
 
4 sleep() wait() 和join() 和 yieId()  有什么区别?
    对于 sleep(),join(),yieId() 方法,我们首先要知道该方法是属于 Thread 类中的。而 wait() 方法,则是属于 Object 类中 的。
    sleep():暂停当前线程,不释放已获得的锁,使当前线程进入阻塞状态,最小阻塞时间1毫秒
    wait():在其他线程调用同一对象锁的notify或notifyAll方法前,阻塞当前线程,并释放当前锁占有的锁(是指定的锁哦),调用 notify或 notifyAll的线程也必须持有相同的对象锁。
    yieId():暂停当前正在执行的线程,使其回到可执行状态,与其他可执行状态的线程共同争夺资源,也就是说调用yieId暂停后可能马上又开始执行。
            yieId只能使同优先级或更高优先级的线程有执行机会。
    join(): 等待调用线程终止。例如:main主线程中 a线程调用join()方法,则主线程需要等待a线程执行完毕才能继续执行。
 
5 volatile 是什么 ? 可以保证有序性吗 ?
    volatile是一个修饰符,只能用来修饰变量。
    volatile修饰的变量能保证其可见性(一个线程修改它,立马别的线程就能看到。)
    volatile不是原子性操作。
    volatile 禁止进行指令重排,可保证部分有序(例如abc,b是volatile变量,则在编译运行时abc的这个组合的位置不能变动,但是a和c之外的部分它就管不着了。)
      使用 Volatile 一般用于 状态标记量 和 单例模式的双检锁
 
6 Thread 类中的 start() run() 方法有什么区别?
     不论是run() 还是 call()(这个Callable需要实现的方法) 都只是普通方法,跟线程没毛关系,开启一个线程必须调用start()方法。
 
7 、为什么 wait, notify notifyAll 这些方法不在 thread 类里面?
    我们都知道 wait 相关的3个方法都是用在多线程交互上的,那么他们为什么不是Thread中的方法,而是Object中的方法那,真实原因是因为wait相关方法主要是针对对象锁(对象归Object),暂停和唤醒都是针对锁来的,如果在thread中那它如何识别应该唤醒那些需要指定锁的线程那。
 
8 、为什么 wait notify 方法要在同步块中调用?
     因外wait和notify必须基于锁才能协同运行。(wait可以单独使用不需要放在同步块)
    如果不这么做notify编译时不会报错,但运行时会报 IllegalMonitorStateException
    避免wait和notify产生竞态条件(多线程同一时间需要同一资源,synchronized独占锁避免这一情况)
 
9 Java interrupted isInterruptedd 方法的区别?
    interrupted():获取当前线程的中断状态,并重置为false, 此方法是静态方法,特别注意是当前线程
    isInterrupted();获取调用线程的中断状态,不重置,此方法是实例方法。
 
10 Java synchronized ReentrantLock 有什么不同?
    synchronized和 reentrantlock都是阻塞型同步。
    synchronized:是java得关键字,需要jvm得实现。synchronized 对于代码块和方法得具体做法不同,详细咨询百度。
    
    reentrantlock:是jdk提供得一个api层面得互斥锁,用lock()和unlock()配合使用。可以定时中断线程( 持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于 Synchronized 来说可以避免出现死锁的情况。
     可以实现公平(Reentrantlock lock = new Reentrantlock();)
     可以绑定多个对象  https://blog.csdn.net/gaopan1999/article/details/105015730
 
 11 、有三个线程 T1,T2,T3, 如何保证顺序执行?
        这里我们可以运用join()(等待指定线程执行完毕)方法实现,join()得底层实现是使用isAlive()判断线程活动状态,用wait来暂停线程。
 
12 SynchronizedMap ConcurrentHashMap 有什么区别?
    synchronizedMap 是Colletions为我们提供得一种同步Map,其与HashTable相似,都是用的synchronized全数据加锁,
      ConcurrentHashMap 虽然也用到了synchronized,但它不是锁定了全部数据,而是指锁定指定得桶(hashmap是数组加链表得,链表那里也可称为桶),相较于前边两种显然concurrentHashMap效率更高。
 
13 、什么是线程安全
    线程安全就是说多线程访问同一代码,不会产生不确定得结果。
 
15 Java 线程池中 submit() execute() 方法有什么区别?
    submit() 可以接收一个Futuer对象得返回值
    execute() 不能接收返回值。
 
16 synchronized关键字得三种使用方式?
    修饰实例方法:synchronized持有得锁是当前实例对象。
     修饰静态方法:synchronized持有得锁是当前类
     修饰代码块: 指定加锁对象
    synchronized 得锁对象不要使用字符串(字符串常量池有缓存功能,顺便说一下 Integer 127~-128 也有缓存功能,参考Integer.valueof())
 
17 Vector 是一个线程安全类吗?
    vector是一个线程安全得类,它的底层数据结构与arraylist相似,但arraylist是线程不安全的
18 、volatile关键字的作用?
        volatile关键字只能修饰变量
        volatile修饰的变量对线程可见(可见性,a线程修改了,b和c线程能立马看到),但不能保证原子性
        volatile不会造成线程阻塞
        volatile标记的变量不会被编译器优化,禁止指令重排
20 、常用的线程池有哪些?
            newSingleThreadExecutor :创建一个单线程的线程池,此线程池保证所有任务的执行顺序按照 任务的提交顺序执行。
            newFixedThreadPool :创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达 到线程池的最大大小。
            newCachedThreadPool :创建一个可缓存的线程池,此线程池不会对线程池大小做限制,线程池 大小完全依赖于操作系统(或者说 JVM )能够创建的最大线程大小。
            newScheduledThreadPool :创建一个大小无限的线程池,此线程池支持定时以及周期性执行任 务的需求。
            newSingleThreadExecutor :创建一个单线程的线程池。此线程池支持定时以及周期性执行任务 的需求。
不建议直接使用 Executors创建线程池,应该自己使用ThreadPoolExecutor创建(阿里巴巴开发手册上也是这么要求的哦)
 
 
21 、简述一下你对线程池的理解
线程池能合理控制线程的数量,避免创建大量线程
线程池能合理复用线程,避免大量线程创建和销毁浪费资源
线程池能提高响应速度,当任务到达时,任务可以不需要的等到线程创建就能立即执行
22, 单例模式了解吗?来给我手写一下!
public class Singleton {
    private volatile static Singleton uniqueInstance;
    private Singleton() {
    }
    public static Singleton getUniqueInstance() {
        //先判断对象是否已经实例过,没有实例化过才进入加锁代码
        if (uniqueInstance == null) {
            //类对象加锁
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
    return uniqueInstance;
    }
}
 
另外,需要注意 uniqueInstance 采用 volatile 关键字修饰也是很有必要。
uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton();
这段 代码其实是分为三步执行:
    1. uniqueInstance 分配内存空间
    2. 初始化 uniqueInstance
    3. uniqueInstance 指向分配的内存地址
    但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2 。指令重排在单线程环境下不会出先 问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 3 此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance ,但此 uniqueInstance 还未被初始化。 使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行
 
 
25 、 讲一下 synchronized 关键字的底层原理
synchronized 关键字底层原理属于 JVM 层面。
通过 JDK 自带的 javap 命令查看 SynchronizedDemo 类的相关字节码信息:首先切换到类的对应目录 执行 javac SynchronizedDemo.java 命令生成编译后的 .class 文件,然后执行 javap - c - s - v - SynchronizedDemo.class
synchronized 同步语句块的情况
synchronized 同步语句块的实现使用的是 monitorenter monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置, monitorexit 指令则指明同步代码块的结束位置。 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor 对象存在于每个 Java 对象 的对象头中, synchronized 锁便是通过这种方式获取锁的,也是为什么 Java 中任意对象可以作为锁的 原因 ) 的持有权 . 当计数器为 0 则可以成功获取,获取后将锁计数器设为 1 也就是加 1 。相应的在执行 monitorexit 指令后,将锁计数器设为 0 ,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等 待,直到锁被另外一个线程释放为止。
 
synchronized 修饰方法的的情况
synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法, JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用
 
 
26 、 实现 Runnable 接口和 Callable 接口的区别
        如果想让线程池执行任务的话需要实现的 Runnable 接口或 Callable 接口。 Runnable 接口或 Callable 口实现类都可以被 ThreadPoolExecutor ScheduledThreadPoolExecutor 执行。两者的区别在于 Runnable 接口不会返回结果但是 Callable 接口可以返回结果。
    备注: 工具类 Executors 可以实现 Runnable 对象和 Callable 对象之间的相互转换。
    ( Executors.callable Runnable task Executors.callable Runnable task Object resule )。
 
 

你可能感兴趣的:(java,thread)