Java多线程
1、线程和进程
一个进程包括由操作系统分配的内存空间(如任务管理器中的进程),包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。
2、线程状态
(1)新建状态
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程
(2)就绪状态
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度
(3)运行状态
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态
(4)阻塞状态
1.等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态
2.同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)
3.其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态
(5)死亡状态
一个运行状态的线程完成任务或者其他终止条件发生时、或发生异常,该线程就切换到终止状态
3、创建线程的方法
(1)通过实现 Runnable 接口,必须重写run()方法,run()没有返回值;这种方式必须将Runnable作为Thread类的参数,然后通过Thread的start方法来创建一个新线程来执行该子任务。如果调用Runnable的run方法的话,是不会创建新线程的。
public class RunnableDemo implements Runnable{ public void run() { System.out.println("执行线程:"+Thread.currentThread().getName()); } public static void main(String[] args) { Thread thread=new Thread(new RunnableDemo()); thread.start(); } }
(2)通过继承 Thread 类本身,run()没有返回值
public class ThreadTest extends Thread{ public void run(){ System.out.println("执行线程:"+Thread.currentThread().getName()); } public static void main(String[] args) { ThreadTest t1=new ThreadTest(); ThreadTest t2=new ThreadTest(); t1.start(); t2.start(); } }
(3)实现Callable()接口, call() (==run() )有返回值
1.创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
2.创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
3.使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
4.调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值
(4)线程池的实现(java.util.concurrent.Executor接口),降低了创建线程和销毁线程时间开销和资源浪费
4、线程的优先级
Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)
5、Thread的方法
public void start() ——使该线程开始执行;Java 虚拟机调用该线程的 run 方法
public void run() ——如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回
public final void setName(String name) ——改变线程名称,使之与参数 name 相同
public final void setPriority(int priority) ——更改线程的优先级
public final void setDaemon(boolean on) ——将该线程标记为守护线程或用户线程
用户线程和守护线程:用户线程是那些完成有用工作的线程。 守护线程 是那些仅提供辅助功能的线程。Thread 类提供了 setDaemon() 函数。Java 程序将运行到所有用户线程终止,然后它将破坏所有的守护线
程。在 Java 虚拟机 (JVM)中,即使在 main 结束以后,如果另一个用户线程仍在运行,则程序仍然可以继续运行
public final void join(long millisec) ——等待该线程终止的时间最长为 millis 毫秒
public void interrupt() ——中断线程,单独调用interrupt()方法不能中断正在运行过程中的线程,只能中断阻塞过程中的线程;当一个线程处于被阻塞状态或者试图执行一个阻塞操作时,使用Thread.interrupt()方式中断
该线程,注意此时将会抛出一个InterruptedException的异常,同时中断状态将会被复位(由中断状态改为非中断状态),将无法中断非阻塞状态下的线程:
public boolean isInterrupted() ——判断线程是否被中断
public static boolean interrupted() ——判断是否被中断并清除当前中断状态
public static void yield() ——暂停当前正在执行的线程对象,并执行其他线程
public static void sleep(long millisec) ——在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),变为阻塞状态,阻塞线程的同时仍然会持有锁,也就是说它休眠期间其他线程仍然无法获得锁,同时sleep()休眠是自动醒的
public static Thread currentThread() ——返回对当前正在执行的线程对象的引用
wait() ——等待全部线程完成任务,调用后为阻塞状态,会自动释放锁,也就是其他线程可以获得锁,而且wait()是无法自动醒的,只有通过notify()或 notifyAll()才行
notify()/notifyAll() ——通知一个/全部线程解除等待,调用后为就绪状态
wait(),notify()/notifyAll()使用时的注意事项:在使用这3个方法时,必须处于synchronized代码块或者synchronized方法中,否则就会抛出IllegalMonitorStateException异常,这是因为调用这几个方法前必须拿到当
前对象的监视器monitor对象,也就是说notify/notifyAll和wait方法依赖于monitor对象