Java多线程(三)

如何停止一个正在运行的线程

有三种方法
1.使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
2.使用stop方法强行终止,不推荐,因为stop和suspend及resume一样都是过期作废的方法。
3.使用interrupt方法中断线程。

interrupt和isInterrupted方法的区别
interrupt:用于中断线程。调用该方法的线程的状态为:将被设置为"中断"状态
注意:线程中断仅仅是线程的中断状态,不会停止线程。需要用户自己去监视线程的状态并做处理。

支持线程中断的方法(也就是线程中断后会抛出interruptedException的方法)就是在监视线程的中断状态,一旦线程的中断状 态被设置为"中断状态",就会抛出异常

interrupted:静态方法,查看当前中断信号是true还是false并且清除中断信号。如果一个线程被中断了,第一次调用interrupted则返回true,第二次和后面的就返回false。
isInterrupted:查看当前中断信号是true还是false。

什么是阻塞式方法
阻塞式方法是指程序会一直等待该方法完成,期间不做其他事情,ServerSocket的accept()方法就是一直等待客户端连接。这里的阻塞是指调用结果返回之前,当前的线程会被挂起,直到得到结果之后才会返回。此外,还有异步和非阻塞式方法在任务完成前就返回。

如何唤醒一个阻塞的线程
wait()、notify()方法是针对对象的,调用任意对象的wait()方法都会导致线程阻塞,阻塞的同时也释放该对象的锁,相应地,调用任意对象的notify()方法则将随机解除该对象阻塞的线程,但它需要重新获取该对象的锁,直到获取成功才能往下执行。

wait()、notify()方法必须在synchronized块或方法中被调用,并且要保证同步块或方法的锁对象与调用wait、notify方法的对象时同一个,如此一来,在调用wait之前,当前线程就已经成功获取某对象的锁,执行wait阻塞后当前线程就将之前获取的对象锁释放,

notify()和notifyAll()的区别
如果线程调用了对象的wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不回去竞争该对象的锁。
notify()只会唤醒一个线程,而notifyAll()会唤醒所有线程

notifyAll()调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,竞争不成功则留在锁池等待锁被释放后再次参与竞争。notify()只会唤醒一个线程,具体唤醒哪个线程由JVM控制。

如何在两个线程间共享数据
在两个线程间共享变量即可共享数据。
一般来说,共享变量要求变量本身是线程安全的,然后在线程内使用的时候,如果有对共享变量的复合操作,那么也得保证复合操作的线程安全性。

如何实现多线程之间的通讯和协作
可以通过中断和共享变量的方式实现线程间的通讯和协作。

比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待时间内,生产者必须释放对临界资源(队列)的占用权。因为生产者如果不释放对临界资源的占用权,那么,消费者就无法消费队列中的商品,就不会让队列有空间,那么生产者就会一直等待下去。
因此,一般情况下,当队列满时,会让生产者交出对临界资源的占用权,进入挂起状态。然后等待消费者消费了商品,然后消费者通知生产者队列有了空间。同样的,当队列空时,消费者也必须等待,等待生产者通知它队列中,这种相互通信的过程就是线程间的协作。

线程通信协作的最常见的两种方式
1.synchronized加锁的线程的Object类的wait()/notify()/nitifyAll()
2.ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll
线程间直接的数据交换:
3.通过管道进行线程间通信:1.字节流2.字符流

同步方法和同步块,哪个是更好的选择
同步块是更好地选择,因为它不会锁住整个对象(也可以让它锁住整个对象)。同步方法会锁住整个对象,哪怕这个类中有多个不相关联的同步块,这通常会导致他们停止执行并需要等待获得这个对象上的锁。

同步块更符合开放调用的原则,只在需要锁住的代码块锁住相应的对象,这样从侧面来说也可以避免死锁
原则:同步的范围越小越好

什么是线程同步和线程互斥,有哪几种实现方式
线程同步:当一个线程对共享的数据进行操作时,应使之成为一个"原子操作",即在没有完成相关操作之前,不允许其他线程打断它,否则,就会破坏数据的完整性,必然会得到错误的处理结果,这就是线程的同步。

在多线程应用中,考虑不同线程之间的数据同步和防止死锁。当两个或多个线程之间同时等待对方释放资源时,就会形成线程之间的死锁。为了防止死锁的发生,需要通过同步来实现线程安全。

线程互斥:对于共享的资源,各个线程访问时具有排他性,任何时刻最多只允许一个线程使用此资源,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步。

线程中的同步方法大体可以分为两类:用户模式和内核模式。
内核模式:利用系统内核对象的单一性来进行同步,使用时需要切换内核态和用户态,而用户模式就不需要切换到内核态,只在用户态完成操作

用户模式下方法有:原子操作(例如一个单一的全局变量),临界区。内核模式下的方法有:时间,信号量,互斥量。

实现线程同步的方法
1.同步代码方法:synchronized关键字修饰的方法
2.同步代码块:synchronized关键字修饰的代码块
3.使用特殊变量域volatile实现线程同步:volatile关键字为域变量的访问提供了一种免锁机制
4.使用重入锁实现线程同步:reentrantlock类可冲入、互斥、实现了lock接口的锁与synchronized方法具有相同的基本行为和语义。

在监视器(Monitor)内部,是如何做线程同步的?程序应该做哪种级别的同步
在JVM中,每个对象(Object和class)通过某种逻辑关联监视器,每个监视器和一个对象引用相关联,为了实现监视器的互斥功能,每个对象都关联着一把锁。

一旦方法或者代码块被synchronized修饰,那么这个部分就放入了监视器的监视区域,确保一次只能有一个线程执行该部分代码,线程在获取锁之前不允许执行该部分的代码。
另外Java还提供了显式监视器(Lock)和隐式监视器(synchronized)两种锁方案

如果提交任务时,线程池队列已满,这时会发生什么

1.如果使用的是无界队列LinkedBlockingQueue:
继续添加任务到阻塞队列中等待执行,因为LinkedBlockingQueue可以近乎认为是一个无穷大的队列,可以无限存放任务

2.如果使用的是有界队列ArrayBlockingQueue:
任务首先会被添加到ArrayBlockingQueue中,ArrayBlockingQueue满了,会根据maximumPoolSize的值增加线程数量,如果增加了线程数量还是处理不过来,ArrayBlockingQueue继续满,那么则会使用拒绝策略RejectedExecutionHandler处理满了的任务,默认是AbortPolicy。

什么叫线程安全,servlet是线程安全的吗
线程安全是指,某个方法在多线程环境中被调用时,能够正确处理多个线程之间的共享变量,使程序功能正确完成。

Servlet不是线程安全的,servlet是单实例多线程的,当多个线程同时访问同一个方法,是不能保证共享变量的线程安全性的。

Struts2的action是多实例多线程的,是线程安全的,每个请求过来都会new一个新的action分配给这个请求,请求完成后销毁

SpringMVC的Controller是线程安全的吗?
不是线程安全的,和Servlet类似的处理流程。

Struts2好处是不用考虑线程安全问题;Servlet和SpringMVC需要考虑线程安全问题,但是性能可以提升不用处理太多gc,可以使用ThreadLocal来处理多线程问题
程序中如何保证多线程的运行安全
方法一:使用安全类如 java.util.concurrent下的类,使用子类AtomicInteger
方法二:使用自动锁synchronized
方法三:使用手动锁Lock

手动锁ex:

Lock lock=new ReentrantLock();
lock.lock();
try{
   S.o.p("获得锁");
}catch(Exception e){
	//handle exception
}finally{
	S.o.p("释放锁");
	lock.unlock();
}

线程优先级的理解
每一个线程都是有优先级的,一般情况,高优先级的线程在运行时具有优先权,但依赖于线程调度实现,这个实现和操作系统相关。
可以定义线程的优先级,但并不保证高优先级的线程会在低优先级的线程前执行。线程的优先级是一个int变量,从1到10,1代表最低优先级,10代表最高优先级

线程类的构造方法、静态块是被哪个线程调用的
线程类的构造方法、静态块是被new这个线程类所在的线程调用的,而run方法里面的代码才是被线程自身所调用的

ex:Thread2中new了Thread1,main函数中new 了Thread2,那么:
1.Thread2的构造方法、静态块是main线程调用的,Thread2的run()方法是Thread2自己调用的
2.Thread1的构造方法、静态块是Thread2调用的,Thread1的run()方法是Thread1自己调用的

如何获取一份线程dump文件?如何获取线程堆栈
Dump文件是进程的内存镜像,可以把程序的执行状态通过调试器保存到dump文件中

在Linux下,可以通过命令kill -3 PID (进程ID)来获取应用的dump文件
在Windows下,可以按下Ctrl+Break来获取。这样JVM就会将线程的dump文件打印到标准输出或错误文件中,它可能打印在控制台或者日志文件中,具体位置依赖应用的配置

一个线程运行时发生异常会怎么样
如果异常没有被捕获,该线程将会停止执行。Thread.UncaughtExceptionHandler是用于处理未捕获异常而造成线程突然中断情况的一个内嵌接口。当一个未捕获异常将造成线程中断的时候,JVM会使用Thread.getUncaughtExceptionHandler()来查询线程的UncaughtExceptionHandler并将线程和异常作为参数传递给handler的uncaughtException()方法进行处理。

线程数过多会造成什么异常
1.线程的生命周期开销非常高
2.消耗过多的CPU
资源如果可运行的线程数量多于可用处理器的数量,那么有线程将会被闲置,大量空闲的线程会占用许多内存,给垃圾回收器带 来压力,而且大量的线程在竞争CPU资源时还将产生其他性能的开销。
3.降低JVM的稳定性
在可创建线程的数量上存在一个限制,这个限制值将随着平台的不同而不同,并且承受着多个因素制约,包括JVM的启动参数、 Thread构造函数中请求栈的大小,以及底层操作系统对线程的限制等。如果破坏了这些限制,那么可能抛出OutOfMemoryError异常。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值