线程是一条执行路径,是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位,一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样多线程也可以实现并发操作,每个请求分配一个线程来处理。
一个正在运行的软件(如迅雷)就是一个进程,一个进程可以同时运行多个任务( 迅雷软件可以同时下载多个文件,每个下载任务就是一个线程), 可以简单的认为进程是线程的集合。
线程是一条可以执行的路径。多线程就是同时有多条执行路径在同时(并行)执行。
1. 线程的实现方式
2. 正确启动线程的方式
线程对象初始化完成后,调用线程的start()方法就可以启动线程了,start()方法是告诉Java虚拟机,如果线程规划期空闲,应该立即启动调用了start()方法的线程。
同一个线程不能多次 调用 start() 方法, 否则会出现异常 Exception in thread" main" java. lang. IllegalThreadStateException。
线程的start()方法,会新启动一个线程,而线程run()方法则是同步等待当前线程调用。
3.停止和中断线程
1.正常停止
就是说线程在运行完run方法后直接正常退出。这种方式很明显,就不做案例演示了。
2.stop强制停止
stop()方法是一个过时的方法,也就是说已经不建议使用。这里也不做详细讨论
3.异常导致的线程停止
就是说在run方法运行期间产生了异常,导致了线程停止执行。看一下下面的案例,在run方法中是个无线循环。我们抛出了一个运行时异常就导致了线程退出。
public class TestExceptionOut implementsRunnable {
public static void main(String[] args) {
Thread thread = new Thread(new TestExceptionOut());
thread.start();
}
@Override
public void run() {
while (true) {
throw new RuntimeException("线程异常退出");
}
}
}
4.中断
新启动的线程有一个中断的状态,默认为false。当我们调用isInterrupted或者interrupted方法可以获取到这个状态值。我们在run里面可以通过获取这个状态来优雅的结束线程。(这里只做interrupted的案例演示)
public void run() {
for(int i=0;i<500000;i++) {//只有循环足够多,才能看到线程停止
if(this.interrupted()) {
System.out.println("已经是停止状态了!我要退出");
//break; for循环后面如果有语句会继续执行
try {
throw new InterruptedException();
} catch (InterruptedExceptione) {
e.printStackTrace();
}
}
System.out.println("i="+(i+1));
}
}
直接调用interrupt()方法,不能正确停止线程
因为调用interrupt()方法不能立即停止线程,仅仅是在当前线程中打了一个停止的 * 标记,并不是真的停止线程。
4.线程的生命周期
创建:新建一个线程对象,如Thread the = new Thread()
就绪:创建了线程对象后,调用了线程的start()方法(注意:此时线程只是进入了线程队列,等待获取CPU服务,具备了运行的条件,但并不一定已经开始运行了)
运行:处于就绪状态的线程,一旦获取了CPU资源,便进入到运行状态,开始执行run()方法里面的逻辑
终止:线程的run()方法执行完毕,或者线程调用了stop()方法,线程便进入终止状态。
阻塞:一个正在执行的线程在某些情况下,由于某种原因而暂时让出了CPU资源,暂停了自己的执行,便进入了阻塞状态,如调用了sleep()方法
5.thread、notify、join、yield的方法说明
Thread类
notify方法
唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。
直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。
此方法只应由作为此对象监视器的所有者的线程来调用。通过以下三种方法之一,线程可以成为此对象监视器的所有者:
. 通过执行此对象的同步实例方法。
. 通过执行在此对象上进行同步的 synchronized 语句的正文。
. 对于 Class 类型的对象,可以通过执行该类的同步静态方法。
一次只能有一个线程拥有对象的监视器。
join方法
join方法有三个重载版本:
/**
等待该线程终止。
*/
join()
join(long millis) //参数为毫秒
join(long millis,int nanoseconds) //第一参数为毫秒,第二个参数为纳秒
yield方法
调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。
注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
6.线程的异常处理
首先,我们要知道,在Java中,线程中的异常是不能抛出到调用该线程的外部方法中捕获的。
为什么不能抛出到外部线程捕获?
因为线程是独立执行的代码片断,线程的问题应该由线程自己来解决,而不要委托到外部。”基于这样的设计理念,在Java中,线程方法的异常都应该在线程代码边界之内(run方法内)进行try catch并处理掉。换句话说,我们不能捕获从线程中逃逸的异常。
怎么进行的限制?
通过java.lang.Runnable.run()方法声明(因为此方法声明上没有throw exception部分)进行了约束。
如果在线程中抛出了线程会怎么样?
线程会立即终结。
现在我们可以怎样捕获线程中的异常?
Java中在处理异常的时候,通常的做法是使用try-catch-finally来包含代码块,但是Java自身还有一种方式可以处理——使用UncaughtExceptionHandler。它能检测出某个线程由于未捕获的异常而终结的情况。当一个线程由于未捕获异常而退出时,JVM会把这个事件报告给应用程序提供的UncaughtExceptionHandler异常处理器。
JDK5之后允许我们在每一个Thread对象上添加一个异常处理器UncaughtExceptionHandler 。Thread.UncaughtExceptionHandler.uncaughtException()方法会在线程因未捕获的异常而面临死亡时被调用。
(具体使用方法在下面链接)
————————————————
版权声明:本文为CSDN博主「潘建南」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/pange1991/article/details/82115437
7.死锁的解决方案
通俗点讲:死锁就是两个或两个以上的进程或线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
产生死锁的四个原因:
1、互斥条件:资源不能被共享,只能由一个进程使用
2、请求与保持条件:进程已获得了一些资源,但因请求其它资源被阻塞时,对已获得的资源保持不放。
3、不可抢占条件(No pre-emption) :有些系统资源是不可抢占的,当某个进程已获得这种资源后,系统不能强行收回,只能由进程使用完时自己释放。
4、循环等待条件(Circular wait) :若干个进程形成环形链,每个都占用对方申请的下一个资源。
解决方案:
1.打破互斥条件,我们需要允许进程同时访问某些资源,这种方法受制于实际场景,不太容易实现条件;
2.打破不可抢占条件,这样需要允许进程强行从占有者那里夺取某些资源,或者简单一点理解,占有资源的进程不能再申请占有其他资源,必须释放手上的资源之后才能发起申请,这个其实也很难找到适用场景;
3.进程在运行前申请得到所有的资源,否则该进程不能进入准备执行状态。这个方法看似有点用处,但是它的缺点是可能导致资源利用率和进程并发性降低
4.避免出现资源申请环路,即对资源事先分类编号,按号分配。这种方式可以有效提高资源的利用率和系统吞吐量,但是增加了系统开销,增大了进程对资源的占用时间。
对待死锁的策略主要有:
1.死锁预防:破坏导致死锁必要条件中的任意一个就可以预防死锁。例如,要求用户申请资源时一次性申请所需要的全部资源,这就破坏了保持和等待条件;将资源分层,得到上一层资源后,才能够申请下一层资源,它破坏了环路等待条件。预防通常会降低系统的效率。
2.死锁避免:避免是指进程在每次申请资源时判断这些操作是否安全,例如,使用银行家算法。死锁避免算法的执行会增加系统的开销。
3.死锁检测:死锁预防和避免都是事前措施,而死锁的检测则是判断系统是否处于死锁状态,如果是,则执行死锁解除策略。
4.死锁解除:这是与死锁检测结合使用的,它使用的方式就是剥夺。即将某进程所拥有的资源强行收回,分配给其他的进程。