随着处理器的生产工艺的提升,处理器越来越趋向多核化。为了给用户更好的体验,充分利用处理器多核的特点,使用多线程技术是必然的选择。但是,Java关于线程的知识非常多又不容易理解,使用不当又特别容易出事故等情况。可以说,多线程是程序员前进路上额绊脚石。下面,我们将分享一下,多线程的相关知识:
进程:进程是程序一次动态执行的过程,它是程序执行过程中分配和管理资源的基本单位。
线程:线程是进程创建的,它是被系统独立调度和分派的基本单位,线程不拥有系统资源,只拥有一点必不可少的资源,但它可以与同一个进程的其它线程共享进程的全部资源。
Tips:一个程序至少一个进程,一个进程至少一个线程。
进程中有多个线程在同时运行,而这些线程可能会同时运行一段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
继承Thread类(Thread implements Runnable)创建线程:重写run方法;
实现Runnable接口创建线程:实现run方法,接口实现类的实例对象作为Thread构造函数的形式参数;
实现Callable接口和Future创建线程:实现call方法接口,实现类的实例作为FutureTask构造函数的形参,FutureTask类的实例作为Thread构造函数的形参。
Runnable提供run(),不会抛出异常,只能在run方法内部处理异常,而Callable提供call(),直接抛出Exception异常;
Runnable的run()无返回值,而Callable的call()提供返回值,用来表示任务运行的结果;
Runnable可以作为Thread构造器的参数,通过开启新的线程来执行,也可以通过线程池来执行,而Callable只能通过线程池执行。
当线程对象被创建后,线程进入了新建状态,如:Thread t = new Thread();
当调用线程对象的start()方法,即t.start();,线程进入就绪状态。处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。
当线程获得CPU时间后,调用run()方法,线程进入运行状态。
Tips:就绪状态是进入到运行状态的唯一入口(线程要想进入运行状态就必须处于就绪状态)。
处于运行状态的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,线程进入阻塞状态。
等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
同步阻塞:运行的线程在获取对象的同步锁(synchronized)时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
其他阻塞:运行的线程执行sleep()或join()方法,发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时,join()等待线程终止或者超时,I/O处理完毕时,线程重新转入就绪状态。(注:sleep()是不会释放持有的锁)
线程执行完成、因异常退出、调用stop()方法,线程结束进入死亡状态。
i. 程序运行结束,线程自动结束;
ii. 使用interrupt()与interruptted()/isinterrupted()结合+抛异常法/Return法;[推荐异常法,异常可以上抛,异常信息可以传播]
iii. 使用volatile变量标识终止正常运行的线程 + 抛异常法/Return法
iv. stop()暴力停止线程;[不推荐使用,原因:i.可能使一些清理工作得不到完成;II.对锁定对象进行'解锁',数据得不到同步处理,出现数据不一致]。
作用:使当前执行代码的线程进行等待。
特点:
i. 该方法会释放锁,把线程放入“等待池”;
ii. 该方法处的代码会停止执行,直到接到通知或被中断;
iii. 调用该方法前,线程必须获得对象级别锁(synchronized方法或代码块中调用),否则抛出IllegalMonitorStateException异常。
作用:通知那些可能等待该对象对象锁的其他线程,如果有多个线程等待,随机挑选一个;
特点:
i. 执行notify()的线程,不会立马释放该对象锁,要等执行notify()方法的线程的程序执行完毕,也就是退出synchronized代码后,才会释放锁;
ii. 一个调用notify()的线程对应一个调用wait()的线程;
iii. 调用该方法前,线程必须获得对象级别锁,否则抛出IllegalMonitorStateException异常;
作用:使所有正在等待同一共享资源的线程从等待状态退出,进入可运行状态(有可能随机、有可能按照优先级)
Java强制wait()/notify()的调用必须要在一个“同步块”中,避免出现lost wake up问题。lost wake up问题可以见《为什么wait()方法要放在同步块中?》
作用:等待线程对象销毁,然后才让其它线程执行;主要作用是同步,使线程之间的并行执行变为串行执行(排队运行)。
join(long)内部使用wait(long)方法实现的,它具有释放锁的特点,但sleep(long)不会释放锁。
yield():放弃当前的CPU资源,将它让给其他的任务去占用CUP执行时间。表明该线程没有在做一些紧急的事情。
join():参考上题。
sleep是让线程休眠,到时间后会继续执行,wait是等待,需要唤醒再继续执行;
sleep是Thread线程类的方法,wait是Object顶级类的方法;
sleep可以在任何地方使用,wait只能在同步方法或者同步块中使用;
sleep,wait调用后都会暂停当前线程并让出CPU的执行时间,但不同的是sleep不会释放当前持有的对象的锁资源,到时间后会继续执行,wait会放弃所有锁并需要notify/notifyAll后重新获取到对象锁资源后才能继续执行。
Thread.Sleep(0) 并非是要线程挂起0毫秒,是让当前线程被冻结了一下(暂时放弃CPU),让其他线程有机会优先执行,就相当于一个让位动作。