关于Java的并发编程

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

成都创新互联公司专注于溪湖网站建设服务及定制,我们拥有丰富的企业做网站经验。 热诚为您提供溪湖营销型网站建设,溪湖网站制作、溪湖网页设计、溪湖网站官网定制、小程序制作服务,打造溪湖网络公司原创品牌,更为您提供溪湖网站排名全网营销落地服务。

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

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

从代码架构角度:具体的任务(run方法)应该和“创建和运行线程的机制”(Thread类)相解耦。

使用继承Thread类的方式的话,那么每次想新建一个任务,只能新建一个独立的线程们这样做的话损耗会比较大(比如重新创建一个线程,执行完毕

以后再销毁等。如果实际工作内容,也就是run()函数里只是简单的打印一行文字的话,那么可能线程的实际工作内容还不如损耗来的大)。如果使用Runnable和线程池,就可以大大减少这样的损耗。

继承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.有几种创建线程的方法?

从不同的角度看,会有不同的答案。

典型答案是两种,分别是实现Runnable接口和继承Thread类

但是我们看原理,其实Thread类实现了Runnable接口,并且Thread类的run()方法,会发现其实哪两种本质是一样的。两种方式在实现多线程的本质上,并没有区别。

还有其他实现线程的方法,例如线程池等,他们也能新建线程,但是细看源码,并没有逃过本质,也是实现Runnable接口和继承Thread类。

结论:我们只能那个通过新建Thread类这一种方式来出创建线程,但是类里面的run方法有两种方式实现,的一种是重写run()方法,第二种是实现Runnable接口的run方法,然后再把该Runnable实例传给Thread类。除此之外,从表面上看线程池、定时器等工具类也能创建线程,但是他们的本质也是逃不过刚说的范围。

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

检查线程状态,只有New状态的线程才能继续,否则会抛出IllegalThreadStateException。运行或已经结束的线程都不能再启动。

(这里可引申面试题:一个线程调用两次start()方法会出现什么情况?why?解题思路就是1异常2线程状态)

被加入线程组

调用start0()方法启动线程。

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

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

用interrupt来请求停止线程。(仅仅是通知到被终止的线程应该停止运行了,被停止的线程自身拥有决定权。这是一个协作机制。)

想要停止线程,需要请求方,被停止方,子方法被调用方相互配合才行:

– a.作为被停止方:每次循环或者适时检测中断信号,并且在可能抛出interruptedException的地方处理该中断信号;

– b.请求方:发出中断信号。

– c.子方法调用方:要注意优先在方法抛出InterruptedException,或者在检查到中断信号时,再次设置中断状态。(Catch里会重置这个状态,需要再次设置中断状态,否则就被吞了)

最后,错误的方法: stop、suspend方法已经被废弃(stop容易造成脏数据)

volatile的boolean标记,无法处理长时间阻塞的情况(例如,生产者消费者模式中,就存在这样的情况,生产者生产速度快,消费者消费速度慢,生产者队列阻塞)

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

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

.可以响应中断而抛出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相关方法

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

static boolean interrupted() //返回之后,清除标记

boolean isInterrupted() //不清除

//Tip:注意Thread.intterupted()的目的对象时“当前线程”,而不管本方法来自于哪个对象

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

6种状态

New:已经创建,但是还没有执行start方法

Runnable:一旦调用了start方法后,就一定会到Runnable,java中的Runnable对应操作系统里的ready和running状态

Blocked:当一个线程进入同步代码块(被Synchronized修饰),该锁被其他线程拿走了。线程变成Blocked。只有Synchronized才能rag线程进入这个状态。

Wating:等待

Time_waiting:计时等待

Terminated:死亡

9.线程相关方法

Thread类:

sleep相关、

join() :等待其他线程执行完毕

yield相关 :放弃已经获得的CPU资源

currentThread:获取当前线程的引用

start,run方法:启动线程相关

interrupt相关::中断线程

stop() suspend() resuem()相关 :已经废弃

Object类:

wait():让线程短暂休息

notify/notifyAll 相关 :唤醒线程

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

阶段  方法和作用

阻塞阶段  调用wait()方法

唤醒阶段  1、另一个线程调用这个对象的notify方法且刚好被唤醒的是本线程。2、另外一个线程调用这个对象的notifyAll方法。3、过了wait(long timeOut)的规定的超时时间,如果传入0就是永久等待。4、线程自身调用了intterrupt()

遇到中断  wait阶段遇到中断会抛出异常,并且释放掉锁

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

用必须先拥有monitor锁。(Synchronized)

notify只能唤醒一个线程。

属于Object类,是所有对象的父类,所以任何对象都能调用,并且都是native final的。

类似Condition的功能

同时持有多个锁的情况。释放锁,只能释放现在wait所对应的对象的那把锁。

12.用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 storage;

public EventStorage() {

this.maxSize = 10;

this.storage = new LinkedList();

}

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();

}

}郑州妇科在线医生 http://www.zzkdfk120.com/

System.out.println("拿到了"+storage.poll()+",现在仓库还剩下"+storage.size());

notify();

}

}

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

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

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

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

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

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

16.sleep方法的作用?

作用:让线程在预期执行,其他时候不占用CPU资源

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

Tips:

//这两种方式其实都是一样的,但是第一种比较优雅

TimeUnit.SECONDS.sleep()

Thread.sleep()

17.wait和sleep方法的异同?

相同:

Wait和sleep方法都可以使线程阻塞,对应线程状态是Waiting或Time_Waiting。

wait和sleep方法都可以响应中断Thread.interrupt()。

不同点:

wait方法的执行必须在同步方法中进行,而sleep则不需要。

在同步方法里执行sleep方法时,不会释放monitor锁,但是wait方法会释放monitor锁。

sleep方法短暂休眠之后会主动退出阻塞,而没有指定时间的 wait方法则需要被其他线程中断后才能退出阻塞。

wait()和notify(),notifyAll()是Object类的方法,sleep()和yield()是Thread类的方法

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

18. join()方法的作用?

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

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

源码:

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

19.yield()方法的作用?

作用:释放cpu时间片,线程状态是runnable,而不是bolcked,也不是waiting。常用于并发包中。

yield 和 sleep:

sleep期间属于被阻塞,yield不是阻塞,随时是runable状态。而且JVM是不保证遵循的。

20.线程都有哪些属性?

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

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

是否是守护线程(isDeamon) :true代表该线程是守护线程,false代表线程是非守护线程,也就是用户线程。

– 作用:给用户线程提供服务。例如垃圾处理器。

– 特性:线程类型默认继承自父线程。被谁启动,一般都是JVM启动的,(main)。不影响JVM退出。

– 区别:整体无区别。唯一区别在于是否影响JVM退出。

优先级(Priority):优先级这个属性的目的是告诉线程调度器,用户希望哪些线程相对多运行,哪些少运行。

– 10个优先级,默认5.

– 程序的设计不应该优先级

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

主线程可以启动发现异常,子线程却不行。比如主线程操作非常多,子线程虽然报异常,但是日志太多,不好发觉。并且在子线程发现问题后,并没有停止执行。

子线程异常无法用传统方法捕获。

不能直接捕获的后果,可能线程挂掉打印堆栈。用了全局处理之后提高健壮性,可以在发生未知异常后,重启线程或者通知程序员等。

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

方案一:手动在每个run()方法里进行try catch (不推荐)

方案二:利用UncaughtExceptionHanler接口

– void uncaughtException (Thread t,Throwable e)

– 异常处理器的调用策略:首先会检查父线程,一直往上找,查找是否有人能够处理。

– 实现:

首先,自定义一个类实现Thread.UncaughtExceptionHandler。重写内置方法uncaughtException(Thread t,Throwable e)方法,里面写自己的逻辑。(Tips:可以通过构造方法来传模块名字)

然后,在需要配置的类中setDefaultUncaughtExceptionHandler(new HandlerInstance);


本文标题:关于Java的并发编程
文章路径:http://myzitong.com/article/pepgdj.html