社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
多线程,线程池,线程安全
多线程
程序、进程、线程的区别是什么? 举个现实的例子说明。
程序(Program):是一个指令的集合。程序不能独立执行,只有被加载到内存中,系统为它分配资源后才能执行
进程(Process):如上所述,一个执行中的程序称为进程。
进程是系统分配资源的独立单位,每个进程占有特定的地址空间。
程序是进程的静态文本描述,进程是程序在系统内顺序执行的动态活动。
线程(Thread):是进程的“单一的连续控制流程“。
线程是CPU调度和分配的基本单位,是比进程更小的能独立运行的基本单位,也被称为轻量级的进程。
线程不能独立存在,必须依附于某个进程。一个进程可以包括多个并行的线程,一个线程肯定属于一个进程。Java虚拟机允许应用程序并发地执行多个线程。
举例:如一个车间是一个程序,一个正在进行生产任务的车间是一个进程,车间内每个从事不同工作的工人是一个线程。
线程通常有五种状态,创建,就绪,运行、阻塞和死亡状态。
新建状态(New):新创建了一个线程对象。
就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况又分为三种:
等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒,wait是object类的方法
同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。
其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。sleep是Thread类的方法
import java.util.concurrent.Callable;
public class CallTask implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int num=0;
for(int i=0;i<100;i++) {
num+=i;
}
return num;
}
}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CallTask callTask = new CallTask();
//使用FutureTask实现类接收返回值 泛型是返回值类型
FutureTask <Integer> futureTask = new FutureTask <>(callTask);
for (int i = 0; i < 100; i++) {
new Thread(futureTask).start();
Integer sum = futureTask.get();
System.out.println(i+"---"+sum);
}
}
}
join(): 当前线程等待,调用此方法的线程执行结束再继续执行。如:在main方法中调用t.join(),那main方法在此时进入阻塞状态,一直等t线程执行完,main方法再恢复到就绪状态,准备继续执行。
join方法必须在线程start方法调用之后调用才有意义。这个也很容易理解:如果一个线程都没有start,那它也就无法同步了。作用于线程
yield(): 可以暂停当前正在执行的线程对象,从而让其相同优先级的线程有机会运行。它是一个静态方法而且只保证当前线程放弃CPU占用而不能保证使其它线程一定能占用CPU,执行yield()的线程有可能在进入到暂停状态后马上又被执行。作用于线程
wait():
wait只能在同步(synchronize)环境中被调用,而sleep不需要。
进入wait状态的线程能够被notify和notifyAll线程唤醒,但是进入sleeping状态的线程不能被notify方法唤醒。
wait方法在进入wait状态的时候会释放对象的锁,但是sleep方法不会。
wait方法是针对一个被同步代码块加锁的对象
wait:导致当前线程等待,进入阻塞状态,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。当前线程必须拥有此对象监视器(对象锁)。该线程释放对此监视器的所有权并等待,直到其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。然后该线程将等到重新获得对监视器的所有权后才能继续执行.
notify:唤醒在此对象监视器(对象锁)上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程(随机选取一个线程)。直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程。此方法只应由作为此对象监视器的所有者的线程来调用.
"当前线程必须拥有此对象监视器"与"此方法只应由作为此对象监视器的所有者的线程来调用"说明wait方法与notify方法必须在同步块内执行,即synchronized(obj之内).
notifyAll: 唤醒在此对象监视器(对象锁)上等待的所有线程。
举例来说:
当在一个线程对象上调用stop()方法时,这个线程对象所运行的线程就会立即停止,并抛出特殊的ThreadDeath()异常。这里的“立即”因为太“立即”了,
假如一个线程正在执行:
synchronized void {
x = 3;
y = 4;
}
由于方法是同步的,多个线程访问时总能保证x,y被同时赋值,而如果一个线程正在执行到x = 3;时,被调用了 stop()方法,即使在同步块中,它也干脆地stop了,这样就产生了不完整的残废数据。而多线程编程中最最基础的条件要保证数据的完整性,所以请忘记 线程的stop方法,以后我们再也不要说“停止线程”了。
Java中如何停止一个线程?
Java提供了很丰富的API但没有为停止线程提供API。JDK 1.0本来有一些像stop(), suspend() 和 resume()的控制方法但是由于潜在的死锁威胁因此在后续的JDK版本中他们被弃用了,之后Java API的设计者就没有提供一个兼容且线程安全的方法来停止一个线程。当run() 或者 call() 方法执行完的时候线程会自动结束,如果要手动结束一个线程,你可以用volatile 布尔变量来退出run()方法的循环或者是取消任务来中断线程。
当run()方法执行完后,线程就自动终止了。
但有些时候run()方法不会结束(如服务器端监听程序),或者其它需要用循环来处理的任务。在这种情况下,一般是将这些任务放在一个循环中,如while循环。如果想让循环永远运行下去,可以使用while(true){……}来处理。但要想使while循环在某一特定条件下退出,最直接的方法就是设一个boolean类型的标志,并通过设置这个标志为true或false来控制while循环是否退出。
下面的代码,实际上有几个线程在运行:
public static void main(String[] argc) throws Exception {
Runnable r = new Thread6();
Thread t = new Thread(r, "Name test");
t.start();
}
两个:线程t和main()方法(主线程)。
线程池
通过重用线程池中的线程,来减少每个线程创建和销毁的性能开销。
对线程进行一些维护和管理,比如定时开始,周期执行,并发数控制等等。
Executor接口很简单,只有一个execute方法。
ExecutorService是Executor的子接口,增加了一些常用的对线程的控制方法,之后使用线程池主要也是使用这些方法。
AbstractExecutorService是一个抽象类。ThreadPoolExecutor就是实现了这个类。
线程池类ThreadPoolExecutor在包java.util.concurrent下。
ThreadPoolExecutor的参数:
ThreadPoolExecutor mExecutor = new ThreadPoolExecutor(corePoolSize,// 核心线程数
maximumPoolSize, // 最大线程数
keepAliveTime, // 闲置线程存活时间
TimeUnit.MILLISECONDS,// 时间单位
new LinkedBlockingDeque<Runnable>(),// 线程队列
Executors.defaultThreadFactory(),// 线程工厂
new AbortPolicy()// 队列已满,而且当前线程数已经超过最大线程数时的异常处理策略
);
corePoolSize
核心线程数,默认情况下核心线程会一直存活,即使处于闲置状态也不会受存keepAliveTime限制。除非将allowCoreThreadTimeOut设置为true。
maximumPoolSize
线程池所能容纳的最大线程数。超过这个数的线程将被阻塞。当任务队列为没有设置大小的LinkedBlockingDeque时,这个值无效。
keepAliveTime
非核心线程的闲置超时时间,超过这个时间就会被回收。
unit
指定keepAliveTime的单位,如TimeUnit.SECONDS。当将allowCoreThreadTimeOut设置为true时对corePoolSize生效。
workQueue
线程池中的任务队列.
常用的有三种队列,SynchronousQueue,LinkedBlockingDeque,ArrayBlockingQueue。
threadFactory
线程工厂,提供创建新线程的功能。ThreadFactory是一个接口,只有一个方法
public interface ThreadFactory {
Thread newThread(Runnable r);
}
通过线程工厂可以对线程的一些属性进行定制。
RejectedExecutionHandler
RejectedExecutionHandler也是一个接口,只有一个方法
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable var1, ThreadPoolExecutor var2);
}
当线程池中的资源已经全部使用,添加新线程被拒绝时,会调用RejectedExecutionHandler的rejectedExecution方法。
说一下线程池内部使用规则
下面都假设任务队列没有大小限制:
如果线程数量<=核心线程数量,那么直接启动一个核心线程来执行任务,不会放入队列中。
如果线程数量>核心线程数,但<=最大线程数,并且任务队列是LinkedBlockingDeque的时候,超过核心线程数量的任务会放在任务队列中排队。
如果线程数量>核心线程数,但<=最大线程数,并且任务队列是SynchronousQueue的时候,线程池会创建新线程执行任务,这些任务也不会被放在任务队列中。这些线程属于非核心线程,在任务完成后,闲置时间达到了超时时间就会被清除。
如果线程数量>核心线程数,并且>最大线程数,当任务队列是LinkedBlockingDeque,会将超过核心线程的任务放在任务队列中排队。也就是当任务队列是LinkedBlockingDeque并且没有大小限制时,线程池的最大线程数设置是无效的,他的线程数最多不会超过核心线程数。
如果线程数量>核心线程数,并且>最大线程数,当任务队列是SynchronousQueue的时候,会因为线程池拒绝添加任务而抛出异常。
任务队列大小有限时
当LinkedBlockingDeque塞满时,新增的任务会直接创建新线程来执行,当创建的线程数量超过最大线程数量时会抛异常。
SynchronousQueue没有数量限制。因为他根本不保持这些任务,而是直接交给线程池去执行。当任务数量超过最大线程数时会直接抛异常。
线程安全
同步有两种方式,第一种是使用关键字synchronized,第二种是使用接口lock
对于线程交互,synchnronized有object对象的wait(),notify()和notifyall()等方法;lock也有相对应的Condition.await(),Condition.signal()和Condition.signalall()等方法
synchronized会死锁,lock也会死锁,但是lock可以使用lock.trylock()尝试获得锁,如果超出设定时间就放弃,而不是一直请求
synchronized会自动解锁,lock必须手动解锁lock.unlock()
这些面试题参考多位博主整理而来,由于参考的博主没有标明出处,所以就不标明原地址了。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!