1.多线程基础
线程共包括以下5种状态。
①.新建状态(New):线程对象被创建后,就进入了新建状态。此时它和其他Java对象一样,仅仅由Java虚拟机分配了内存,并初始化其成员变量值。
②.就绪状态(Runnable):也被称为“可执行状态”。线程对象被调用了该对象的start()方法,该线程处于就绪状态。Java虚拟机会为其创建方法调用栈和程序计数器。处于就绪状态的线程,随时可能被CPU调度执行,取决于JVM中线程调度器的调度。
③.运行状态(Running):线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
④.阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(01) 等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。
(02) 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
(03) 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
⑤. 死亡状态(Dead):线程执行完了、因异常退出了run()方法或者直接调用该线程的stop()方法(容易导致死锁,现在已经不推荐使用),该线程结束生命周期。
2.ava.lang.Object类里的方法
①.wait()方法
作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。
让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。
②.notify()方法
唤醒在此对象监视器上等待的单个线程,使其进入“就绪状态”。
③.notifyAll()方法
唤醒在此对象监视器上等待的所有线程,使其进入“就绪状态”。
④.wait(long timeout)方法
让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
⑤.wait(long timeout, int nanos)方法
让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个 线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)。
3.Thread类的方法
①.yield()方法
yield()是Thread类的静态方法。它能让当前线程暂停,但不会阻塞该线程,而是由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是 当前线程又进入到“运行状态”继续运行!yield()方法不会释放锁。
②.sleep()方法
sleep()是Thread类的静态方法。该方法声明抛出了InterrupedException异常。所以使用时,要么捕捉,要么声明抛出。
有2种重载方式:
static void sleep(long millis) 让当前正在执行的线程暂停millis毫秒,并进入阻塞状态,该方法受到系统计时器和线程调度器的精度和准度的影响。
sleep(long millis , int nanos) 让当前正在执行的线程暂停millis毫秒加nanos微秒,并进入阻塞状态,该方法受到系统计时器和线程调度器的精度和准度的影响。
sleep()的作用是让当前线程休眠,即当前线程会从“运行状态”进入到“休眠(阻塞)状态”。sleep()会指定休眠时间,线程休眠的时间会大于/等于该休眠时间;在线程重新被唤醒时,它会由“阻塞状态”变成“就绪状态”,从而等待cpu的调度执行。常用来暂停程序的运行。sleep()方法不会释放锁。
③.join()方法
join() 是Thread的一个实例方法。表示当某个程序执行流中调用其他线程的join方法时,调用线程将被阻塞,直到被join的线程执行完毕。
有3种重载的形式:
join() 等待被join的线程执行完成
join(long millis) 等待被join的线程的时间最长为millis毫秒,若在millis毫秒内,被join的线程还未执行结束,则不等待。
join(long millis , int nanos) 等待被join的线程的时间最长为millis毫秒加nanos微秒,若在此时间内,被join的线程还未执行结束,则不等待。
④.interrupt()方法
interrupt()是Thread类的一个实例方法,用于中断本线程。这个方法被调用时,会立即将线程的中断标志设置为“true”。所以当中断处于“阻塞状态”的线程时,由于处于阻塞状态,中断标记会被设置为“false”,抛出一个 InterruptedException。所以我们在线程的循环外捕获这个异常,就可以退出线程了。
interrupt()并不会中断处于“运行状态”的线程,它会把线程的“中断标记”设置为true,所以我们可以不断通过isInterrupted()来检测中断标记,从而在调用了interrupt()后终止线程,这也是通常我们对interrupt()的用法。 Interrupted()是Thread类的一个静态方法,它返回一个布尔类型指明当前线程是否已经被中断,isInterrupted()是Thread类的实例方法,返回一个布尔类型来判断线程是否已经被中断。它们都能够用于检测对象的“中断标记”。区别是,interrupted()除了返回中断标记之外,它还会清除中断标记(即将中断标记设为false);而isInterrupted()仅仅返回中断标记。
4.Synchronized关键字
①.原理
在java中,每一个对象有且仅有一个同步锁。这也意味着,同步锁是依赖于对象而存在。
当前线程调用某对象的synchronized方法时,就获取了该对象的同步锁。例如,synchronized(obj),当前线程就获取了“obj这个对象”的同步锁。 不同线程对同步锁的访问是互斥的。也就是说,某时间点,对象的同步锁只能被一个线程获取到!通过同步锁,我们就能在多线程中,实现对“对象/方法”的互斥访问。例如,现在有个线程A和线程B,它们都会访问“对象obj的同步锁”。假设,在某一时刻,线程A获取到“obj的同步锁”并在执行一些操作;而此时,线程B也企图获取“obj的同步锁” —— 线程B会获取失败,它必须等待,直到线程A释放了“该对象的同步锁”之后线程B才能获取到“obj的同步锁”从而才可以运行。
②.基本原则
第一条 : 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的该“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
第二条 : 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程仍然可以访问“该对象”的非同步代码块。
第三条 : 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的其他的“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
3、实例锁和全局锁
实例锁 -- 锁在某一个实例对象上。如果该类是单例,那么该锁也具有全局锁的概念。实例锁对应的就是synchronized关键字。
全局锁 -- 该锁针对的是类,无论实例多少个对象,那么线程都共享该锁。全局锁对应的就是static synchronized(或者是锁在该类的class或者classloader对象上)。
就是说,一个非静态方法上的synchronized关键字,代表该方法依赖其所属对象。一个静态方法上synchronized关键字,代表该方法依赖这个类本身。
5.线程的优先级和守护线程
①.线程的优先级
java中的线程优先级的范围是1~10,默认的优先级是5。每个线程默认的优先级都与创建它的父线程具有相同的优先级。默认情况下,mian线程具有普通优先级。“高优先级线程”会优先于“低优先级线程”执行。Thread提供了setPriority(int newPriority)和getPriority()方法来设置和返回线程优先级。
Thread类有3个静态常量:
——MAX_PRIORITY = 10
——MIN_PRIORITY = 1
——NORM_PRIORITY = 5
②.守护线程
java 中有两种线程:用户线程和守护线程。可以通过isDaemon()方法来区别它们:如果返回false,则说明该线程是“用户线程”;否则就是“守护线程”。
用户线程一般用户执行用户级任务,而守护线程也就是“后台线程”,一般用来执行后台任务。需要注意的是:Java虚拟机在“用户线程”都结束后会后退出。
守护线程又称“后台线程”、“精灵线程”,它有一个特征——如果所有前台线程都死亡,后台线程自动死亡。
通过setDaemon(true)来设置一个线程。
6.释放锁和不释放锁的操作
不释放锁
线程执行同步代码块或同步方法时,程序调用Thread.sleep(Long l)、Thread.yield()方法暂停当前线程的执行
线程执行同步代码块时,其它线程调用该线程suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)
尽量避免使用suspend()和resume()来控制线程
释放锁
当前线程的同步方法、同步代码块执行结束
当前线程的同步方法、同步代码块遇到break、return终止该代码块、该方法的继续执行
当前线程的同步方法、同步代码块中出现了未处理Error和Exception,导致异常结束
当前线程在同步方法、同步代码块中执行了线程对象的wait()方法,当前线程暂停,并释放锁
/**
参考出处:http://www.cnblogs.com/zrtqsk/
https://blog.youkuaiyun.com/sinat_32869075/article/details/54632425
*/
线程共包括以下5种状态。
①.新建状态(New):线程对象被创建后,就进入了新建状态。此时它和其他Java对象一样,仅仅由Java虚拟机分配了内存,并初始化其成员变量值。
②.就绪状态(Runnable):也被称为“可执行状态”。线程对象被调用了该对象的start()方法,该线程处于就绪状态。Java虚拟机会为其创建方法调用栈和程序计数器。处于就绪状态的线程,随时可能被CPU调度执行,取决于JVM中线程调度器的调度。
③.运行状态(Running):线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
④.阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(01) 等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。
(02) 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
(03) 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
⑤. 死亡状态(Dead):线程执行完了、因异常退出了run()方法或者直接调用该线程的stop()方法(容易导致死锁,现在已经不推荐使用),该线程结束生命周期。
2.ava.lang.Object类里的方法
①.wait()方法
作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。
让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。
②.notify()方法
唤醒在此对象监视器上等待的单个线程,使其进入“就绪状态”。
③.notifyAll()方法
唤醒在此对象监视器上等待的所有线程,使其进入“就绪状态”。
④.wait(long timeout)方法
让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
⑤.wait(long timeout, int nanos)方法
让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个 线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)。
3.Thread类的方法
①.yield()方法
yield()是Thread类的静态方法。它能让当前线程暂停,但不会阻塞该线程,而是由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是 当前线程又进入到“运行状态”继续运行!yield()方法不会释放锁。
②.sleep()方法
sleep()是Thread类的静态方法。该方法声明抛出了InterrupedException异常。所以使用时,要么捕捉,要么声明抛出。
有2种重载方式:
static void sleep(long millis) 让当前正在执行的线程暂停millis毫秒,并进入阻塞状态,该方法受到系统计时器和线程调度器的精度和准度的影响。
sleep(long millis , int nanos) 让当前正在执行的线程暂停millis毫秒加nanos微秒,并进入阻塞状态,该方法受到系统计时器和线程调度器的精度和准度的影响。
sleep()的作用是让当前线程休眠,即当前线程会从“运行状态”进入到“休眠(阻塞)状态”。sleep()会指定休眠时间,线程休眠的时间会大于/等于该休眠时间;在线程重新被唤醒时,它会由“阻塞状态”变成“就绪状态”,从而等待cpu的调度执行。常用来暂停程序的运行。sleep()方法不会释放锁。
③.join()方法
join() 是Thread的一个实例方法。表示当某个程序执行流中调用其他线程的join方法时,调用线程将被阻塞,直到被join的线程执行完毕。
有3种重载的形式:
join() 等待被join的线程执行完成
join(long millis) 等待被join的线程的时间最长为millis毫秒,若在millis毫秒内,被join的线程还未执行结束,则不等待。
join(long millis , int nanos) 等待被join的线程的时间最长为millis毫秒加nanos微秒,若在此时间内,被join的线程还未执行结束,则不等待。
④.interrupt()方法
interrupt()是Thread类的一个实例方法,用于中断本线程。这个方法被调用时,会立即将线程的中断标志设置为“true”。所以当中断处于“阻塞状态”的线程时,由于处于阻塞状态,中断标记会被设置为“false”,抛出一个 InterruptedException。所以我们在线程的循环外捕获这个异常,就可以退出线程了。
interrupt()并不会中断处于“运行状态”的线程,它会把线程的“中断标记”设置为true,所以我们可以不断通过isInterrupted()来检测中断标记,从而在调用了interrupt()后终止线程,这也是通常我们对interrupt()的用法。 Interrupted()是Thread类的一个静态方法,它返回一个布尔类型指明当前线程是否已经被中断,isInterrupted()是Thread类的实例方法,返回一个布尔类型来判断线程是否已经被中断。它们都能够用于检测对象的“中断标记”。区别是,interrupted()除了返回中断标记之外,它还会清除中断标记(即将中断标记设为false);而isInterrupted()仅仅返回中断标记。
4.Synchronized关键字
①.原理
在java中,每一个对象有且仅有一个同步锁。这也意味着,同步锁是依赖于对象而存在。
当前线程调用某对象的synchronized方法时,就获取了该对象的同步锁。例如,synchronized(obj),当前线程就获取了“obj这个对象”的同步锁。 不同线程对同步锁的访问是互斥的。也就是说,某时间点,对象的同步锁只能被一个线程获取到!通过同步锁,我们就能在多线程中,实现对“对象/方法”的互斥访问。例如,现在有个线程A和线程B,它们都会访问“对象obj的同步锁”。假设,在某一时刻,线程A获取到“obj的同步锁”并在执行一些操作;而此时,线程B也企图获取“obj的同步锁” —— 线程B会获取失败,它必须等待,直到线程A释放了“该对象的同步锁”之后线程B才能获取到“obj的同步锁”从而才可以运行。
②.基本原则
第一条 : 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的该“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
第二条 : 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程仍然可以访问“该对象”的非同步代码块。
第三条 : 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的其他的“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
3、实例锁和全局锁
实例锁 -- 锁在某一个实例对象上。如果该类是单例,那么该锁也具有全局锁的概念。实例锁对应的就是synchronized关键字。
全局锁 -- 该锁针对的是类,无论实例多少个对象,那么线程都共享该锁。全局锁对应的就是static synchronized(或者是锁在该类的class或者classloader对象上)。
就是说,一个非静态方法上的synchronized关键字,代表该方法依赖其所属对象。一个静态方法上synchronized关键字,代表该方法依赖这个类本身。
5.线程的优先级和守护线程
①.线程的优先级
java中的线程优先级的范围是1~10,默认的优先级是5。每个线程默认的优先级都与创建它的父线程具有相同的优先级。默认情况下,mian线程具有普通优先级。“高优先级线程”会优先于“低优先级线程”执行。Thread提供了setPriority(int newPriority)和getPriority()方法来设置和返回线程优先级。
Thread类有3个静态常量:
——MAX_PRIORITY = 10
——MIN_PRIORITY = 1
——NORM_PRIORITY = 5
②.守护线程
java 中有两种线程:用户线程和守护线程。可以通过isDaemon()方法来区别它们:如果返回false,则说明该线程是“用户线程”;否则就是“守护线程”。
用户线程一般用户执行用户级任务,而守护线程也就是“后台线程”,一般用来执行后台任务。需要注意的是:Java虚拟机在“用户线程”都结束后会后退出。
守护线程又称“后台线程”、“精灵线程”,它有一个特征——如果所有前台线程都死亡,后台线程自动死亡。
通过setDaemon(true)来设置一个线程。
6.释放锁和不释放锁的操作
不释放锁
线程执行同步代码块或同步方法时,程序调用Thread.sleep(Long l)、Thread.yield()方法暂停当前线程的执行
线程执行同步代码块时,其它线程调用该线程suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)
尽量避免使用suspend()和resume()来控制线程
释放锁
当前线程的同步方法、同步代码块执行结束
当前线程的同步方法、同步代码块遇到break、return终止该代码块、该方法的继续执行
当前线程的同步方法、同步代码块中出现了未处理Error和Exception,导致异常结束
当前线程在同步方法、同步代码块中执行了线程对象的wait()方法,当前线程暂停,并释放锁
/**
参考出处:http://www.cnblogs.com/zrtqsk/
https://blog.youkuaiyun.com/sinat_32869075/article/details/54632425
*/