参考链接:https://blog.youkuaiyun.com/kwame211/article/details/78963044
1 线程介绍
1、什么是线程?
线程是进程的一个执行流程,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。一个进程中可以有多个线程。
一个线程---->独占一套:一个虚拟机栈、PC寄存器、操作数栈,本地方法栈
进程和线程的区别:
多线程的意义在于一个进程中,有多个执行部分可以同时执行。但是操作系统并没有将多个线程看作多个独立应用,来实现进程的调度和管理以及资源分配。
java程序默认开启的线程开启:
(1)分发处理送给JVM信号的线程
(2)调用对象finalize方法的线程
(3)垃圾收集线程,清除reference的线程
(4)main线程,用户程序入口
2、为什么使用线程?
可以在一个进程中有多个线程执行部分同时进行,多进程程序的并发性高。
进程在执行过程中拥有独立的内存单元,而多个线程共享所属进程的内存,从而极大地提高了程序的运行效率。
2 java线程:启动和终止线程
1、构造线程
实现runnable接口
public class RunnableImpl implements Runnable{
public void run(){}
}
public class TestRunnable {
public static void main(String[] args) {
RunnableImpl ri1=new RunnableImpl("李白");
Thread t1=new Thread(ri1);
t1.start();
}
}
继承Thread类
public class TestThread extends Thread {
public void run() {}
}
public static void main(String[] args){
Thread t1=new TestThread("李白");
t1.start();
}
}
2、启动线程
Thread.run() 与 Thread.start()的本质区别:
1)不同的执行路径:start() 会启动新的除main()以外的执行线程(具有新的调用栈),run()跳到执行run(),执行完返回main。
2)不同的线程状态转换:start() 让线程从新状态转移到可运行状态,run()方法让该线程从可运行状态到运行状态执行线程。
当程序执行到t.start();时候,程序多出一个分支(增加了一个调用栈B),这样,栈A、栈B并行执行。
3、理解线程的中断
线程的中断一般是其他线程调用该线程的**interrupt()**方法。
interrupt():线程的一个标识位属性,它表示一个运行中的线程是否被其他线程进行中断操作,好比其他线程对该线程打了个招呼。
—检查:检查自身是否被中断来进行响应 isInterrupted()
—复位:对当前线程的中断标识进行复位 interrupted()
安全的中止线程 --------中断、线程内部变量控制
终止方式使得线程有机会去清理资源,而不是武断的将线程停止。
3 线程的状态转换
线程之间相互配合完成工作,提升线程价值
1、新–>可运行:start()方法启动新线程到准备状态;
2、可运行–>运行:run()方法让线程到运行状态,线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。
3、运行–>死:当线程的run()方法完成时就认为它死去。在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。
4、运行–>睡眠:Thread.sleep(); 当前线程睡眠一段时间
运行–>阻塞:需要一个对象的锁定而被阻塞。
运行–>等待:Thread.wait();
5、运行–>可运行:线程的让步—Thread.yield()。作用是:暂停当前正在执行的线程对象,并执行其他线程。注意:yield()从未导致线程转到等待/睡眠/阻塞状态。
4 线程间的同步
4.1 synchronized
1、目的:保证多个线程访问一个数据时候,不会同时修改造成数据损坏。
2、实现:synchronized关键字,在对象上锁
获取锁:当程序运行到synchronized同步方法或代码块时才该对象锁才起作用。
释放锁:持锁线程退出了synchronized同步方法或代码块。
//同步方法
public int fix(int y) {
synchronized (this) {
x = x - y;
}
return x;
}
//以上改成非同步方法
public synchronized int getX() {
return x++;
}
public int getX() {
synchronized (this) {
return x;
}
}
注意:
1)只能同步方法,而不能同步变量和类;
2)每个对象只有一个锁,关键在哪个对象上同步;
3)线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁。
4)如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制。
5)线程睡眠时,它所持的任何锁都不会释放。
6)同步损害并发性,因此我们可以缩减同步区域,变成同步方法中一部分代码块。
7)静态同步方法和非静态同步方法将永远不会彼此阻塞,因为静态方法锁定在Class对象上,非静态方法锁定在该类的对象上。
8)死锁是线程间相互等待锁锁造成的,概率非常的小,但死锁就死掉。
9)应该尽可能避免在synchronized方法或synchronized块中使用sleep或者yield方法。
4.2 volatile
1、作用:volatile变量可以被看作是一种“程度较轻的synchronized”;与synchronized块相比,volatile变量所需的编码较少,运行时开销少,但是它所能实现的功能也仅是synchronized的一部分。
2、使用:volatile可以用在任何变量前面,但不能用于final变量前面(final型的变量是禁止修改的。也不存在线程安全的问题。)
要使volatile变量提供理想的线程安全,必须同时满足下面两个条件:
对变量的写操作不依赖于当前值。
该变量没有包含在具有其他变量的不变式中。
3、synchronized和volatile区别:
这两种机制的提出都是为了实现代码线程的安全性。其中 Volatile变量的同步性较差(但有时它更简单并且开销更低),而且其使用也更容易出错。
详细可参考《Java理论与实践:正确使用 Volatile 变量》
https://blog.youkuaiyun.com/wind5shy/article/details/5387773
5 Java线程的交互
5.1 线程的等待和唤醒
1、线程唤醒和等待的参数
void notify()——唤醒在此对象监视器上等待的单个线程。
void notifyAll()——唤醒在此对象监视器上等待的所有线程。
void wait()——导致当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll()方法。
void wait(longtimeout)——导致当前的线程等待, notify()方法或 notifyAll()方法,或者超过指定的时间量。
void wait(longtimeout, int nanos)——导致当前的线程等待, notify()方法或 notifyAll()方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。
2、调用需要注意的细节:【重要】
1)使用wait(),notify(),notifyAll时需要先对调用对象加锁。
2)调用wait()方法后, 线程:RUNNING–>WAITING,并放入对象的等待队列。
3)notify(),notifyAll()调用后:等待线程不会立即从wait()返回,需要调用notify的线程释放锁以后,等待线程才有机会从wait返回。
5.2 线程调度
1、线程的优先级: t.setPriority(8); //1-10的优先级
2、线程离开运行状态的3种方法:
调用Thread.sleep():线程休眠xxms;
调用Thread.yield():线程让步,通常它会让当前运行线程回到可运行性状态,使得有相同优先级的线程有机会执行。
调用join()方法:线程合并,让一个线程B“加入”到另外一个线程A的尾部。B必须在A完成后执行。(若B没有存活,则A不需要停止)
3、除了以上三种方式外,还有下面几种特殊情况可能使线程离开运行状态:
1)线程的run()方法完成,线程死亡。
2)在对象上调用wait()方法(不是在线程上调用)。
3)线程不能在对象上获得锁定,它正试图运行该对象的方法代码。
4)线程调度程序可以决定将当前运行状态移动到可运行状态,以便让另一个线程获得运行机会,而不需要任何理由。
5.3 Daemon线程—守护线程
–支持型线程,用作程序中后台调度以及支持性工作,设置X.setDaemon(true)。
(1)当JVM中不存在Daemon线程时,JVM将退出。
(2)当JVM只有Daemon线程时,虚拟机需要退出,所有Daemon线程需要立即终止,但是DamonRunner里的finally块并没有执行。
所以构建Daemon线程时,不能依靠finally块中的内容确保执行关闭、清理逻辑。