目录
一、线程相关概念
1、程序
2、进程
3、线程
2、多线程应用
1、创建线程的两种方式
2、代码示例
三、Runnable 接口
1、为什么要实现接口
2、代码示例:如何使用
3、静态代理模式:模拟极简的Tread类
4、继承Tread类 和 实现Runnable接口的区别
四、线程常用方法
1、补充-线程终止
2、常用方法
3、守护线程
五、线程的生命周期
1、线程的几种状态
2、线程状态转换图
3、代码示例:查看线程状态
六、线程同步机制
1、基本介绍
2、synchronized 关键字
3、互斥锁
4、线程死锁
5、释放锁
1、程序
程序是一个指令序列。是为完成特定任务、 用某种语言编写的一组指令的集合。简单的说:就是我们写的代码
它以某些程序设计语言编写,运行于某种目标结构体系上。打个比方,程序就如同以英语(程序设计语言)写作的文章,要让一个懂得英语的人(编译器)同时也会阅读这篇文章的人(结构体系)来阅读、理解、标记这篇文章。一般的,以英语文本为基础的计算机程序要经过编译、链接而成为人难以解读,但可轻易被计算机所解读的数字格式,然后放入运行。
2、进程
进程是指运行中的程序, 比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。 当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。
进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程 : 有它自身的产生 存在和消亡的过程3、线程
(1)基本介绍
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
线程是独立调度和分派的基本单位。线程可以为操作系统内核调度的内核线程,如Win32线程;由用户进程自行调度的用户线程,如Linux平台的POSIX Thread;或者由内核与用户进程,如Windows 7的线程,进行混合调度。
一个进程可以有很多线程,每条线程并行执行不同。
同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。
(2)单线程
同一个时刻, 只允许执行一个线程。(单线程在程序执行时,所走的程序路径按照连续顺序排下来,前面的必须处理好,后面的才会执行。)
(3)多线程
同一个时刻, 可以执行多个线程,比如 : 一个qq进程, 可以同时打开多个天窗口,一个迅需进程, 可以同时下载多个文件
(4)并发
同一个时刻,多个任务交替执行,造成一种“貌似同时” 的错觉, 简单的说, 单核cpu实现的多任务就是井发。
(5)并行
同一个时刻,多个任务同时执行。多核cpu可以实现并行。
1、创建线程的两种方式
(1)继承 Thread 类,重写 run 方法
(2)实现 Runnable 接口,重写 run 方法
(3)Thread 类示意图:
2、代码示例
▶ 当一个类继承了 Thread 类, 该类就可以当做线程使用▶ 我们会重写 run 方法,写上自己的业务代码public class Thread01 { public static void main(String[] args) throws InterruptedException{//抛出异常 //创建一个Cat对象,当线程使用 Cat cat = new Cat(); //启动线程 cat.start(); //当主线程(main)启动一个子线程(Thread-0) ,主线程不会阻塞(即停止),会继续执行 //这时主线程和子线程是交替执行的(交替速度极快) //Thread.currentThread().getName() 获取线程的名称 System.out.println("主线程继续执行" + Thread.currentThread().getName()); for (int i = 0; i < 60; i++){ System.out.println("主线程 i=" + i); //让主线程休眠 Thread.sleep(1000); } } } //当一个类继承了 Thread类,该类就可以当做线程使用 //我们会重写run方法,写上自己的业务代码 class Cat extends Thread{ //记录次数,为了控制退出线程 int times = 0; @Override public void run() { while (true) { //该线程每隔一秒,在控制台输出子线程 System.out.println("子线程 :" + (++times) + "线程名=" + Thread.currentThread().getName()); //让该线程休眠一秒 //此处有异常,需要try-catch try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //退出线程 if (times == 80){ break;//当times到80时,线程就退出了 } } } }
3、底层
1、为什么要实现接口
- java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时再用继承Thread类方法来创建线程显然不可能了。
- java设计者们提供了另外一个方式创建线程,就是通过实现 Runnable 接口来创建线程
2、代码示例:如何使用
public class Runnable01 { public static void main(String[] args) { Text text = new Text(); //一定要注意:这里不能直接调用start() //需要创建Thread对象,把text对象(实现Runnable)放入到Thread Thread thread = new Thread(text); //然后再调用 start()方法 thread.start(); } } // Text类 实现 Runnable 接口 class Text implements Runnable { //计数 int count = 0; @Override public void run() {//普通方法 while (true) { System.out.println("hi" + (++count) + Thread.currentThread().getName()); try {//休眠1秒,有异常进行try-catch Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //退出线程 if (count == 10) { break; } } } }
3、静态代理模式:模拟极简的Tread类
public class Runnable01 { public static void main(String[] args) { //创建T1对象 T1 t1 = new T1(); //创建ThreadProxy 对象,参数是t1对象 ThreadProxy threadProxy = new ThreadProxy(t1); //启动线程,调用start()方法 threadProxy.start(); } } class T { } class T1 extends T implements Runnable { @Override public void run() { System.out.println("T1类"); } } //静态代理模式 //模拟了一个极简的Thread类 class ThreadProxy implements Runnable { //属性,类型是 Runnable private Runnable target = null; //构造器 public ThreadProxy(Runnable target) { this.target = target; } //start()方法 public void start() { //调用start0()方法 start0(); } //start0()方法 public void start0() { //调用run方法 run(); } @Override public void run() { if (target != null) { target.run(); //动态绑定(运行类型T1) } } }
运行顺序:
ThreadProxy对象的start()方法 --> start0()方法 --> ThreadProxy对象的run() 方法 --> 创建ThreadProxy对象时,传入了t1对象,所以target 不为空 --> 运行类型是传入是t1对象,所以会调用 T1类 的 run()方法
4、继承Tread类 和 实现Runnable接口的区别
- 从java的设计来看, 通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,从jdk文档中我们可以知道Thread类本身就实现了Runnable接口
- 实现Runnable接口方式更加适合多个线程共享一个资源的情况 并且避免了单继承的限制,建议使用Runnable
1、补充-线程终止
- 线程终止就是当线程完成任务后,会自动退出。
- 还可以通过使用变量来控制 run方法 退出的方式停止线程,即通知方式
- 代码示例
public class Exit_ { public static void main(String[] args) throws InterruptedException{ T t = new T(); t.start(); //通知方式 //让主线程休眠10秒 Thread.sleep(10 * 1000); //如果希望main线程去控制t 线程的终止,只需要修改loop //修改loop的值为false 让t退出run方法,而终止t线程 t.setLoop(false); } } class T extends Thread{ //计数 int count = 0; //设置一个控制变量,控制线程的退出 private boolean loop = true; @Override public void run() { while (loop){ try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程正在运行...." + (++count)); } } //用来修改 loop 属性 public void setLoop(boolean loop){ this.loop = loop; } }
2、常用方法
方法名 功能 setName 设置线程称 getName 返回该线程的名称,使之与参数name相同 start 使该线程开始执行,即启动线程,Java 虚拟机底层调用该线程的start0方法。注意:底层会创建新的线程,调用run方法,run方法就是一个简单的方法调用,不会启动新线程 run 调用线程对象 run方法 setPriority 更改线程的优先级 getPriority 获取线程的优先级 sleep 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),(线程的静态方法) interrupt 中断线程。注意中断线程, 是没有真正的结束线程的。所以一般用于中断正在休眠线程 yield 线程的礼让。 让出cpu, 让其他线程执行,但礼让的时间不确定 所以也不一定礼让成功 join 线程的插队。 插队的线程一旦插队成功,则肯定先执行完插入的线程的所有任务,然后再依次执行其他任务 join :线程插队代码示例
public class Method02 { public static void main(String[] args) throws InterruptedException{ T2 t2 = new T2(); t2.start(); for (int i = 1; i <= 20; i++) { Thread.sleep(1000); System.out.println("主线程执行...." + i); if (i == 5){ System.out.println("主线程让子线程先执行"); //join --> 线程插队 t2.join(); System.out.println("子线程执行完,主线程继续执行"); } } } } class T2 extends Thread{ @Override public void run() { for(int i = 1;i <= 20; i++){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("子线程在执行...." + i); } } }
解释:主线程和子线程会先交替执行,当 i = 5 时子线程会线程插队,然后只执行子线程,当子线程执行完毕,再继续执行主线程。
3、守护线程
- 用户线程 : 也叫工作线程,当线程的任务执行完毕或通知的方式结束
- 守护线程 : 一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
- 常见的守护线程 : 垃圾回收机制
- 代码示例:
public class Method03 { public static void main(String[] args) throws InterruptedException{ MyDaemonThread myDaemonThread = new MyDaemonThread(); //如果我们希望主线程结束后,子线程自动结束,只需将子线程设置成守护线程即可 myDaemonThread.setDaemon(true); myDaemonThread.start(); for (int i = 1; i <= 10; i++) { System.out.println("主线程在执行...."); Thread.sleep(1000); } } } class MyDaemonThread extends Thread{ @Override public void run() { //无限循环 for (; ;) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("子线程在执行...."); } } }
关键点:在启动线程之前,把 setDaemon()方法设置成 true
1、线程的几种状态
2、线程状态转换图
3、代码示例:查看线程状态
public class State { public static void main(String[] args) throws InterruptedException{ T t = new T(); //查看初始状态,调用方法getState() System.out.println(t.getName() + " 状态" + t.getState()); //启动线程 t.start(); //循环查看线程的状态 while (Thread.State.TERMINATED != t.getState()){ System.out.println(t.getName() + " 状态" + t.getState()); Thread.sleep(500); } System.out.println(t.getName() + " 状态" + t.getState()); } } class T extends Thread{ @Override public void run() { while (true){ for (int i = 0; i < 10; i++) { System.out.println("hi " + i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } break; } } }
1、基本介绍
- 在多线程编程时,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。
- 也可以这样理解 : 线程同步, 即当有一个线程在对内存进行操作时, 其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作
2、synchronized 关键字
(1)同步代码块
synchronized (对象){//得到对象的锁,才能操作同步代码//需要被同步代码
}
(2)同步方法public synchronized void m1(String name){
//需要被同步的代码
}
3、互斥锁
(1)基本介绍
- Java语言中,引入了对象互斤锁的概念, 来保证共享数据操作的完整性。
- 每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
- 关键字synchronized 来与对象的互斥锁联系。 当某个对象用 synchronized 修饰时,表明该对象在任一时刻只能由一个线程访问。
- 同步的局限性 : 导致程序的执行效率要降低
- 同步方法(非静态的)的锁可以是this, 也可以是其他对象(要求是同一个对象)
- 同步方法 (静态的)的锁为当前类本身。
(2)注意事项
- 同步方法如果没有使用static修饰 : 默认锁对象为this
- 如果方法使用static修饰, 默认锁对象 :当前类.class
- 实现的落地步骤 : ①需要先分析上锁的代码 ②选择同步代码块或同步方法 ③要求多个线程的锁对象为同一个即可!
(3)代码示例
public class SellTicket { public static void main(String[] args) { SellTicket03 sellTicket03 = new SellTicket03(); new Thread(sellTicket03).start(); new Thread(sellTicket03).start(); new Thread(sellTicket03).start(); } } //使用Runnable方式 class SellTicket03 implements Runnable{ //控制线程退出 boolean loop = true; private static int ticketNum = 100; //让多个线程共享ticketnum //在同一时刻只能有一个线程来执行sell方法,加synchronized关键字 public synchronized void sell(){ if (ticketNum <= 0){ System.out.println("售票结束。。。"); loop=false; return; } System.out.println("剩余票数" + (--ticketNum)); } @Override public void run() { while (loop){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } //调用方法,实现售票 sell(); } } }
4、线程死锁
- 多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生。
5、释放锁
- 当前线程的同步方法、 同步代码块执行结束。就会释放锁
- 当前线程在同步代码块、 同步方法中遇到break,return。就会释放锁
- 当前线程在同步代码块、同步方法中出现了未处理的Error 或 Exception,导致异常结束。就会释放锁
- 当前线程在同步代码块、 同步方法中执行了线程对象的 wait()方法, 当前线程暂停,并释放锁
- 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方
法暂停当前线程的执行,不会释放锁- 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,
该线程不会释放锁- 注意:应尽量避免使用suspend()和resume()来控制线程, 方法不再推荐使用