关于Java并发编程那点事

关于JAVA并发编程的那些事。

记录一下自己在学习并发编程的时候遇到的一些问题。方便自己查阅。

1.实现Runnable接口好在哪里?

  1. 从代码架构角度:具体的任务(run方法)应该和“创建和运行线程的机制”(Thread类)相解耦。
  2. 使用继承Thread类的方式的话,那么每次想新建一个任务,只能新建一个独立的线程们这样做的话损耗会比较大(比如重新创建一个线程,执行完毕
    以后再销毁等。如果实际工作内容,也就是run()函数里只是简单的打印一行文字的话,那么可能线程的实际工作内容还不如损耗来的大)。如果使用Runnable和线程池,就可以大大减少这样的损耗。
  3. 继承Thread类以后,由于Java语音不支持多继承,这样就无法再继承其他的类,限制了可扩展性

2.“实现Runnable接口并传入Thread类”和继承Thread类,然后重写run()方法的本质对比?

在实现多线程的本质上,并没有区别,都是最终调用了start()方法来新建线程。这两个方法最主要的区别是在于run()的了内容来源。

//以下代码在Thread类中,可以找到
@override
public void run(){
	if(target!=null){
		target.run();
	}
}

实现Runnable接口最终调用target.run()。继承Thread类,是整个run()方法都被重写。

3.有几种创建线程的方法?

  1. 从不同的角度看,会有不同的答案。
  2. 典型答案是两种,分别是实现Runnable接口和继承Thread类
  3. 但是我们看原理,其实Thread类实现了Runnable接口,并且Thread类的run()方法,会发现其实哪两种本质是一样的。两种方式在实现多线程的本质上,并没有区别。
  4. 还有其他实现线程的方法,例如线程池等,他们也能新建线程,但是细看源码,并没有逃过本质,也是实现Runnable接口和继承Thread类。
  5. 结论:我们只能那个通过新建Thread类这一种方式来出创建线程,但是类里面的run方法有两种方式实现,的一种是重写run()方法,第二种是实现Runnable接口的run方法,然后再把该Runnable实例传给Thread类。除此之外,从表面上看线程池、定时器等工具类也能创建线程,但是他们的本质也是逃不过刚说的范围。

4.start方法的执行流程是什么?

  1. 检查线程状态,只有New状态的线程才能继续,否则会抛出IllegalThreadStateException。运行或已经结束的线程都不能再启动。
    (这里可引申面试题:一个线程调用两次start()方法会出现什么情况?why?解题思路就是1异常2线程状态)
  2. 被加入线程组
  3. 调用start0()方法启动线程。

Tips:start方法是被synchronized修饰的方法,可以保证线程安全。并且由JVM产检的main方法和system组线程,并不会通过start来启动。

5.线程之间的状态是如何转化的?

线程状态转化图

6.Java中如何正确停止线程?

  1. 用interrupt来请求停止线程。(仅仅是通知到被终止的线程应该停止运行了,被停止的线程自身拥有决定权。这是一个协作机制。)
  2. 想要停止线程,需要请求方,被停止方,子方法被调用方相互配合才行:
    – a.作为被停止方:每次循环或者适时检测中断信号,并且在可能抛出interruptedException的地方处理该中断信号;
    – b.请求方:发出中断信号。
    – c.子方法调用方:要注意优先在方法抛出InterruptedException,或者在检查到中断信号时,再次设置中断状态。(Catch里会重置这个状态,需要再次设置中断状态,否则就被吞了)
  3. 最后,错误的方法: stop、suspend方法已经被废弃(stop容易造成脏数据)
    volatile的boolean标记,无法处理长时间阻塞的情况(例如,生产者消费者模式中,就存在这样的情况,生产者生产速度快,消费者消费速度慢,生产者队列阻塞)

7.无法响应中断时如何停止线程?

如果线程阻塞是由于调用了wait()、sleep()或者join()方法,你可以中断线程,通过抛出interruptedException异常来唤醒该线程,但是对于不能响应InterruptedException的阻塞,并没有一个通用的解决方案。但是我们可以利用特定的其他可以响应中断的方法,比如Reentrantlock.lockInterruptibly(),比如关闭套接字使线程立即返回等方法来达到目的。答案有很多种,因为有很多原因会造成线程阻塞,所以针对不同的情况,唤起的方法不同。总结来说,如果不支持响应中断,就要用特定的方法来唤起。根据不同的类,调用不同的方法。

8.可以响应中断而抛出InterruptedException的常见方法?

Object.wait()/wait(long)/wait(long,int)
Thread.sleep()/sleep(long,int)
Thread.join()/join(long)/join(long,int)
java.util.concurrent.BlockingQueue.take()/put(E)
java.util.concurrent.locks.Lock.lockInterruptibly()
java.util.concurrent.countDownLatch.await()
java.util.concurrent.cyclicBarrier.await()
java.uti.concurrent.Exchanger.exchange(V)
java.nio.channels.InterruptibleChannel相关方法
java.nio.channels.Selector相关方法

9.判断线程是否被中断的方法有哪些?

static boolean interrupted() //返回之后,清除标记
boolean isInterrupted()  //不清除
//Tip:注意Thread.intterupted()的目的对象时“当前线程”,而不管本方法来自于哪个对象

10.线程都有哪几个状态?

6种状态

  • New:已经创建,但是还没有执行start方法
  • Runnable:一旦调用了start方法后,就一定会到Runnable,java中的Runnable对应操作系统里的ready和running状态
  • Blocked:当一个线程进入同步代码块(被Synchronized修饰),该锁被其他线程拿走了。线程变成Blocked。只有Synchronized才能rag线程进入这个状态。
  • Wating:等待
  • Time_waiting:计时等待
  • Terminated:死亡

11.线程相关方法

Thread类:

  • sleep相关、
  • join() :等待其他线程执行完毕
  • yield相关 :放弃已经获得的CPU资源
  • currentThread:获取当前线程的引用
  • start,run方法:启动线程相关
  • interrupt相关::中断线程
  • stop() suspend() resuem()相关 :已经废弃

Object类:

  • wait():让线程短暂休息
  • notify/notifyAll 相关 :唤醒线程

12.wait/notify/notifyAll的作用和用法?

阶段方法和作用
阻塞阶段调用wait()方法
唤醒阶段1、另一个线程调用这个对象的notify方法且刚好被唤醒的是本线程。2、另外一个线程调用这个对象的notifyAll方法。3、过了wait(long timeOut)的规定的超时时间,如果传入0就是永久等待。4、线程自身调用了intterrupt()
遇到中断wait阶段遇到中断会抛出异常,并且释放掉锁

13.wait、notify、notifyAll特点?性质?

  1. 用必须先拥有monitor锁。(Synchronized)
  2. notify只能唤醒一个线程。
  3. 属于Object类,是所有对象的父类,所以任何对象都能调用,并且都是native final的。
  4. 类似Condition的功能
  5. 同时持有多个锁的情况。释放锁,只能释放现在wait所对应的对象的那把锁。

14.用wait/notify方法实现消费者生产者模式?

package com.yue.consumer;

import java.util.Date;
import java.util.LinkedList;

//使用wait notify实现一个生产者消费者模式
public class ProducerConsumerModel {
    public static void main(String[] args) {
        EventStorage storage = new EventStorage();
        Producer producer = new Producer(storage);
        Consumer consumer =new Consumer(storage);
        new Thread(producer).start();
        new Thread(consumer).start();
    }
}
class Producer implements Runnable{
    private EventStorage storage;

    public Producer(EventStorage storage) {
        this.storage = storage;
    }

    public void run() {
        for (int  i= 0;i<100;i++){
            storage.put();
        }
    }
}


class Consumer implements  Runnable{
    private EventStorage storage;

    public Consumer(EventStorage storage) {
        this.storage = storage;
    }


    public void run() {
        for (int i=0;i<100;i++){
            storage.take();
        }
    }
}

class EventStorage{
    private int maxSize;
    private LinkedList<Date> storage;

    public EventStorage() {
        this.maxSize = 10;
        this.storage = new LinkedList<Date>();
    }

    public synchronized void put(){
        while(storage.size() == maxSize){
            try {
                wait();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        storage.add(new Date());
        System.out.println("仓库里有了"+storage.size()+"个产品");
        notify();
    }

    public synchronized void take(){
        while(storage.size()==0){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("拿到了"+storage.poll()+",现在仓库还剩下"+storage.size());
        notify();
    }
}

15.为什么wait需要在同步代码块中使用,而sleep不需要?

为了让线程之间的通信更加可靠,防止死锁或者永久等待。如果不放在synchronized中的话,那么久有可能在线程执行到一个wait之前,切换到另外一个线程。而另外一个线程执行完notify后,切换回来。这样就没有线程去唤醒它了。而sleep是针对自己线程的,和其他线程的关系不大。

16.为什么wait、notify和notifyAll定义在object类中,sleep定义在Thread类中?

因为在Java中,这三个操作都是所级别的操作,而锁是针对对象的。锁是绑定到对象中,而不是绑定到线程。

17.wait是属于Object对象的,那调用Thread.wait()会出现什么情况?

会导致流程问题。因为在线程退出的时候,会自动执行一个notify

18.sleep方法的作用?

作用:让线程在预期执行,其他时候不占用CPU资源
特点:Sleep方法可以让线程进入waiting状态,并且不占用CPU资源,但是不释放锁(包含synchronized和lock),直到规定时间之后在执行。休眠期内如果被中断,会抛出异常并清除中断状态。
Tips:

//这两种方式其实都是一样的,但是第一种比较优雅
TimeUnit.SECONDS.sleep() 
Thread.sleep()

19.wait和sleep方法的异同?

相同:

  1. Wait和sleep方法都可以使线程阻塞,对应线程状态是Waiting或Time_Waiting。
  2. wait和sleep方法都可以响应中断Thread.interrupt()。

不同点:

  1. wait方法的执行必须在同步方法中进行,而sleep则不需要。
  2. 在同步方法里执行sleep方法时,不会释放monitor锁,但是wait方法会释放monitor锁。
  3. sleep方法短暂休眠之后会主动退出阻塞,而没有指定时间的 wait方法则需要被其他线程中断后才能退出阻塞。
  4. wait()和notify(),notifyAll()是Object类的方法,sleep()和yield()是Thread类的方法

TIps:Java设计的时候把对象都当成一把锁,对象头中都有锁的状态

20. join()方法的作用?

作用:因为新线程加入我们,所以得等他执行完再出发;通常是,主线程等待子线程,而不是子线程等待主线程。例如一般是main等thread1执行完。join遇到中断时候是主线程被中断,是主线程抛出异常;在join期间状态是waiting

Tips:CountDownLatch或CyclicBarrier类封装了join。建议使用封装好的工具

源码:
调用了thread.wait方法,而这方法会在thread执行结束后悔自动调用notify。这也是为什么不要使用这个的原因。

21.yield()方法的作用?

作用:释放cpu时间片,线程状态是runnable,而不是bolcked,也不是waiting。常用于并发包中。
yield 和 sleep:
sleep期间属于被阻塞,yield不是阻塞,随时是runable状态。而且JVM是不保证遵循的。

22.线程都有哪些属性?

  1. 编号(ID):每个编程都有自己的ID,用于标识不同的线程。

  2. 名称(Name):作用是让用户或者程序员在开发、调试或运行过程中,更容易区分每个不同的线程,定位问题等。

  3. 是否是守护线程(isDeamon) :true代表该线程是守护线程,false代表线程是非守护线程,也就是用户线程。
    – 作用:给用户线程提供服务。例如垃圾处理器。
    – 特性:线程类型默认继承自父线程。被谁启动,一般都是JVM启动的,(main)。不影响JVM退出。
    – 区别:整体无区别。唯一区别在于是否影响JVM退出。

  4. 优先级(Priority):优先级这个属性的目的是告诉线程调度器,用户希望哪些线程相对多运行,哪些少运行。
    – 10个优先级,默认5.
    – 程序的设计不应该优先级(不同的操作系统不一样、优先级会被操作系统改变)

23.实际工作中,如何全局处理异常?为什么要全局处理?不处理行不行?

  1. 主线程可以启动发现异常,子线程却不行。比如主线程操作非常多,子线程虽然报异常,但是日志太多,不好发觉。并且在子线程发现问题后,并没有停止执行。
  2. 子线程异常无法用传统方法捕获。
  3. 不能直接捕获的后果,可能线程挂掉打印堆栈。用了全局处理之后提高健壮性,可以在发生未知异常后,重启线程或者通知程序员等。

24.关于线程异常的两种处理方法?

  1. 方案一:手动在每个run()方法里进行try catch (不推荐)
  2. 方案二:利用UncaughtExceptionHanler接口
    – void uncaughtException (Thread t,Throwable e)
    – 异常处理器的调用策略:首先会检查父线程,一直往上找,查找是否有人能够处理。
    在这里插入图片描述
    – 实现:
    首先,自定义一个类实现Thread.UncaughtExceptionHandler。重写内置方法uncaughtException(Thread t,Throwable e)方法,里面写自己的逻辑。(Tips:可以通过构造方法来传模块名字)
    然后,在需要配置的类中setDefaultUncaughtExceptionHandler(new HandlerInstance);

未完待续…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值