基本概念:程序-进程-线程
电脑,cup,cpu 的核数
几核的 cpu,就代表同一个顺时时间能处理的任务数
主频
10核 cpu,主频 100mhz
1核 cpu ,主频 3Ghz
核数差别不大,主频高好些
主频差别不大,核数多好些
程序(program):是为完成特定任务,用某种语言编写的一组指令的集合,即指一段静态的代码,静态对象
进程(process):是程序的一次执行过程,或是正在运行的一个程序
- 动态过程:有它自身的产生,存在和消亡的过程
- 如:运行的qq,运行的mp3播放器
- 程序是静态的,教程是动态的
线程(thread):进程可进一步细化为线程,是一个程序内部的一条执行路径。
- 若一个程序可同一时间执行多个线程,就是支持多线程的
- 多线程,一个进程(一个程序运行时),可以分化为并行执行的多个线程(多个子程序)
进程相当于一条河,线程就相当于河流的分支
何时需要多线程
- 程序需要同时执行两个或多个任务
- 程序需要实现一些需要等待的任务时,如用户输入,文件读写操作,网络操作,搜索等
-例如:有一个进程时浏览器,看网页,比如用百度搜索,需要等待百度那边的服务器通过网络给你展示搜索的内容,这个过程需要时间,如果网速越慢时间越长,在等待的过程中,这个浏览器进程是一直占用cpu的资源,考虑说在这个浏览器等待百度服务器响应的这段时间先让这个进程干点别的事,等响应回来的数据再继续使用 - 需要一些后台运行的程序时
- 因为多线程事进程的支流,当分支之后就各走各的,假设在进程上跑的代码时主程序,当其中第三行代码时开启线程的,那么,开启线程之后线程运行的代码和主程序并行(他们之间就不相干了)
java中多线程的创建和使用
Java语言的 JVM 允许程序运行多个线程,它通过 java.lang.Thread 类来实现
Thread类的特性:
- 每个线程都是通过某个特定 Thread 对象的 run() 方法来完成操作的,经常把 run() 方法的主体称为线程体(想要在开启的多线程中运行的代码逻辑写到 run() 方法中)
- 通过该 Thread 对象的start() 方法来调用(启动)这个线程(本质上就是运行 run() 方法)
Thread类的构造方法:
- Thread():创建新的 Thread 对象
- Thread(String threadname):创建线程并指定线程实例名
- Thread(Runnable target):指定创建线程的目标对象,它实现了 Runnable 接口中的 run 方法
- Thread(Runnable target,String name):创建新的 Thread 对象
创建线程的两种方式
1.继承 Thread 类
- 定义子类继承 Thread 类
- 子类中重写 Thread 类中的 run 方法
- 创建 Thread 子类对象,即创建了线程对象
- 调用线程对象 start 方法:启动线程,调用 run 方法
2.实现 Runnable 接口
- 定义子类,实现 Runnable 接口
- 子类中重写 Runnable 接口中的 run 方法
- 通过 Thread 类含参构造创建线程对象
- 将 Runnable 接口的子类对象作为实际参数传递给 Thread 类的构造方法中
- 调用 Thread 类的 start 方法:开启线程,调用 Runnable 子类接口的 run 方法
不带线程名称
带线程名称的
继承方式和实现方式的联系与区别
public class Thread extends Object implements Runnable
- 区别:
- 继承 Thread:线程代码存放在 Thread 子类 run 方法中,(重写 run方法)
- 实现 Runnable:线程代码存放在接口的子类 run 方法,(实现 run 方法)
- 实现接口方式的好处
- 避免了单继承的局限性
- 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源
- 一般使用实现接口的方式来实现多线程
t4 和 t5共享了同一个 run 对象,他们相加的counts是同一个
实现多线程的优点
- 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
- 提高计算机系统 CPU 的利用率
- 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
如果在一个方法里面有1000行代码,前300行,中间300行 和最后的400行这三段代码没有因果关系,这时候可以采用多线程,把前中后三段代码分别放在不同的线程中去运行,这三段代码就是并行运行的
Thread 类的有关方法(1)
- void start():启动线程,并执行对象的run()方法
- run():线程在被调度时执行的操作
- String getName(): 返回线程的名称
- void setName(String name):设置该线程的名称
- static currentThread():返回当前线程
线程的优先级
- 线程的优先级(线程的优先级就是哪个线程有较大的概率被执行)控制
- 优先级时用数字 1-10表示,数字越大,优先级越高,如果没有设置,默认优先级为5
- MAX_PRIORITY(10);
- MIN_PRIORITY(1);
- NORM_PRIORITY(5);
- 涉及的方法:
- getPriority():返回线程的优先值
- setPriority(int newPriority):改变线程的优先级
- 线程创建时继承父类的优先级
- 优先级时用数字 1-10表示,数字越大,优先级越高,如果没有设置,默认优先级为5
Thread 类的有关方法(2)
- static void yield():线程让步
- 暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
- 若队列中没有同优先级的线程,忽略此方法
- join():当某个程序执行流中调用其他线程的 join()方法时,调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完成为止
- 低优先级的线程也可以获得执行
- 低优先级的线程也可以获得执行
- static void sleep(long millis):(指定时间:毫秒)
- 令当前活动线程在指定时间段内放弃对 CPU 控制,使其他线程有机会被执行,时间到后重新排队
- 抛出 InterruptedException 异常
- stop():强制线程生命周期结束
- boolean isAlive():返回 boolean,判断线程是否活着
线程的生命周期
(线程从生到死的整个经历)
JDK中用 Thread.State 枚举表示了线程的几种状态
要想实现多线程,必须在主线程中创建新的线程对象,Java语言使用 Thread 类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:
- 新建(线程实例的创建):当一个 Thread 类或其子类的对象被声明并创建时,新生的对象处于新建状态
- 就绪(执行.start方法之后):处于新建状态的线程被 start() 后,将进入线程队列等待 CPU 时间片,此时它已具备了运行的条件
- 运行(run方法的代码开始执行):当就绪的线程被调度并获得处理器资源时,使进入运行状态,run()方法定义了线程的操作和功能
- 阻塞(run方法的代码暂停执行,卡住run方法):在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时终止自己的执行,进入阻塞状体
- 死亡:线程完成了它的全部工作或线程被提前强制性地终止
- 自然死亡:程序执行完毕或者程序发生了异常到程序结束
- 强制死亡:执行.stop()方法,断电,杀掉进程
线程的同步
问题的提出:
- 多个线程执行的不确定性引起执行结果的不稳定
- 多个线程对账本的共享,会造成操作的不完整性,会破会数据
问题的原因:
- 当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进行执行。导致共享数据的错误。
解决办法:
- 对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行
Synchronized 的使用方法
java 对于多线程的安全问题提供了专业的解决方式:
- 同步机制
- synchronized 还可以放在方法声明中,表示整个方法位同步方法(如果针对对象要加同步锁,那就加在方法上)
例如:
public synchronized void show (String name){ … }
- 如果针对某一个段代码需要加同步锁,那就直接在代码块上加同步锁
synchronized(对象){ //需要被同步的代码: }
- synchronized 还可以放在方法声明中,表示整个方法位同步方法(如果针对对象要加同步锁,那就加在方法上)
线程的死锁问题
- 死锁
- 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
- 解决方法:
- 专门的算法,原则,比如加锁顺序一致
- 尽量减少同步资源的定义,尽量避免锁未释放的场景
线程的通信
wait() 与 notify() 和 notifyAll()(这三个方法只能用在有同步锁的方法或者代码块中)
- wait(): 令当前线程挂起并放弃CPU,同步资源,使别的线程可访问并修改共享资源,而当前线程排队等候再次对资源的访问
- notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等候
- notifyAll():唤醒正在排队等候资源的所有线程结束等待
java.lang.Object 提供的这三个方法只有在 synchronized 方法或 synchronized 代码块中才能使用,否则会报 java.lang.IllegalMonitorStateException异常
wait() 方法
- 在当前线程中调用方法:对象名.wait()
- 使当前线程进入等待(某对象)状态,直到另一个线程对该对象发出 notify(或 notifyAll)为之。
- 调用此方法的必要条件:
- 当前线程必须具有对改对象的监控权(加锁)
- 调用此方法后,当前线程释放对象监控权,然后进入 notify 后,要重新获得监控权,然后从断点处继续代码的执行
notify()/notifyAll()
- 在当前线程中调用方法: 对象名.notify()
- 功能:唤醒等待对象监控全的一个线程
- 调用方法的必要条件:当前线程必须具有改对象的监控权(加锁)
经典例题:生产者/消费者问题
生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如 20),如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有了产品再通知消费者来取走产品。
这里可能出现两个问题:
- 生产者比消费者快时,消费者会漏掉一些数据没有取到
- 消费者比生产者快时,消费者会取到相同的数据。