Java 线程相关
并发
- 多个不同的软件同时运行,windows/linux等操作系统,同时管理多个软件并发执行
- 一个软件可以被多个用户同时请求,例如,多个浏览器用户同时请求淘宝,同时做支付操作和结算操作,等价于在服务端多次运行
- 并发的目的就是要CPU不停的工作,提高CPU的工作效率
计算机是如何做到并发的
ps:只有一台计算机,一块CPU,一个核
分析硬件 – 时间片
在某一个CPU的时间点上,只能有一个程序在执行,为了能做到并发,把CPU的时间分成若干时间片段。
时间片段的划分原则由操作系统定义,每个小的时间片段只能执行一个程序。
时间片到了就把程序强制离开CPU,在从内存中选出下一个程序,继续运行。
看似在一段时间内是多个程序在同时运行。
分析软件 – 进程
一个软件动辄几百Mb,或上Gb,一个软件是不能整个存储在内存中的
进程:把大的软件分割成多个程序段,程序段可以称之为进程
多个大的程序要运行,先把程序分割成多个程序段,然后把每个程序的前一部分的若干程序段加载到内存,最终内存中放置了多个程序的多个程序段。
这些程序段要根据向优先级排队,从队头获取下一个程序段来运行。
大量的程序段也可以说是进程,在频繁切换CPU的是会占用CPU的时间,做压栈和弹栈的工作,和需要部分内存存储栈中的数据。
进程还拥有一个私有的虚拟的内存地址,该空间仅能被他包含的线程访问,线程只能归属某一个进程,并且他只能访问该进程的所有的资源。
当操作系统创建了一个进程后,该进程就会自动申请一个名为主线程或者叫首要线程。
同类的多个线程,共享一块内存空间和一组资源,线程本身有一个供程序执行的堆栈。
线程在CPU切换时负荷小,因此线程被称之为轻负荷进程,一个进程可以包含多个线程。
进程和线程的区别
- 一个进程至少有一个线程。
- 线程的划分尺度小于进程,使得多个线程并发性高。
- 另外,进程在指定的过程中拥有独立的内存单元,而多个线程共享进程的内存空间,从而提高了程序的运行效率。
- 线程在执行的过程中,与进程区别在于每个独立的线程有一个程序的运行入口,顺序执行序列和程序出口。
- 线程不能独立运行,必须依存于应用程序中,由应用程序提供对线程的控制。
线程的应用场景:
- 线程通常用于一个应用程序中需要同时完成多个任务的情况
- 我们可以将每个任务定义一个线程,使得他们可以一同工作,也可以用于单一线程中完成
- 存在多线程可以更快的完成所需要的功能
线程的使用:
在java中能通过api做线程编程
方案一:继承Thread类
- 此类是线程类,其每一个实例对象表示一个可以并发运行的线程。
- 通过继承此类并重写run方法,来定义一个具体的线程,在run方法中体现的是线程的功能。
- 启动线程时,调用线程对象的start方法,而不是直接调用run方法。
- start方法将线程纳入线程调度,使当前线程可以并发运行。
- 当线程获取到CPU时间片后,开始自动运行run方法中的逻辑代码 。
public class MyThread extends Thread{
public void run(){
//线程的入口
//线程的任务逻辑
//线程的出口
}
}
//使用:
MyThread mt = new MyThread();
mt.start();//调用重写的run。实际是多态
方案二:实现Runnable接口
- 实现此接口,并重写run方法来定义线程类,然后创建线程的时候。
- 将Runnable接口的实例传入并启动线程,这做的好处可以将线程和线程要执行的任务逻辑分离,减少耦合。
- 同时java是单继承,定义一个实现Runnable接口,这样做可以更好的实现其他的接口和继承父类。
- Thread的run调用重写的run。
public class MyThread implements Runnable{
public void run(){
//线程的入口
//线程的任务逻辑
//线程的出口
}
}
//使用:
Thread t=new Thread(new MyThread());
t.start();//执行的是thread中的run方法
//thread中的run又调了MyThread里的run
线程的状态
创建线程的对象 | 创建态 | Thread t=new Thread(); |
创建完的线程对象 | 就绪态 | t.start();//排到线程队列中,不一定马上获取CPU |
获取到CPU资源 | 执行态 | 执行run()方法;//线程出队列 |
执行到某一个点时 | 阻塞态 | 从CPU上下来;//IO,sleep,wait阻塞 |
如果正常执行完run | 结束态/消亡态 | 等待GC回收 |
线程的状态之间的转化
- 创建态–>就绪态
- 就绪态–>执行态
- 执行态–>阻塞态(挂起)
- 执行态–>就绪态
- 阻塞态–>就绪态
- 执行态–>消亡态
做线程的目的
实际上是为了做多线程
多线程的目的就是为了能并发
能并发的目的就是为了提高CPU的利用率
守护线程
当进程中只剩下守护线程时,所有的守护线程强制终止
联合线程:void join()
此方法用于等待当前线程结束,比如:
t1.join();//t1线程没有结束,当前线程不会结束
线程的重要的几个关键点
- 线程何时启动,即线程对象.start();是有先后顺序的,有可能某个线程的启动要靠他的主线程是否启动,
- run方法的执行顺序,run是并发执行的,靠的是何时start()后,何时能获取到CPU
- run并发执行着,需要注意线程之间的依存关系,当前线程是否执行完毕,可能是依赖于另一个线程是否执行完毕
做多线程的两种方式
- 多个线程共享一个run()
- 多个线程可以每个线程一个run(),
- 以上两种组合,也是多线程
写多线程的步骤
- 不需要关心线程在那个进程里
- 每new一个Thread类或其子类的对象都是线程的对象,且要start()
- 要根据需求考虑要有几个run的任务逻辑
- 要根据需求考虑每个run要执行多少次
- 要根据需求考虑run方法是否使用数据,并考虑数据安全性
用内部类创建线程对象
通常我们可以通过匿名内部类的方法创建线程,使用该方式可以简化编写代码的复杂度
当一个线程仅需要一个实例时,我们通常可以使用如下方式:
//方式一:实现类是匿名的,对象有名
Thread t=new Thread(){
public void run(){
System.out.println("run方法");
}
};
t.start();
//方式二:实现类是匿名的,对象也是匿名的
new Thread(){
public void run(){
System.out.println("run方法");
}
}.start();
//方式三:实现类是匿名的,对象不是匿名的
Runnable r=new Runnable(){
public void run(){
System.out.println("run方法");
}
};
Thread t=new Thread(r);
t.start();
//方式四:对象是匿名的,实现类也是匿名的
new Thread(new Runnable(){
public void run(){
System.out.println("run方法");
}
}).start();
//方式五:实现类是匿名的,对象是有名的,对象名t
Thread t=new Thread(new Runnable(){
public void run(){
System.out.println("run方法");
}
});
t.start();
创建线程对象
- 继承自Thread类,重写run方法
优点:在当前的线程类中,可以获取run方法,并重写,同时在当前线程类中也可以访问Thread类中的方法。
缺点:当前线程类不能继承其他的类,java是单继承。 - 实现Runnable接口,并重写run方法
优点:当前的类可以多实现,还可以继承,把线程对象和线程的任务逻辑分离出来。
缺点:Runnable接口的实现类只有一个run方法,要想使用其他的线程方法需要。
Thread t=new Thread(Runnable接口的实现类对象); - 特殊用法,匿名内部类
优点:写法简单,编码量少
缺点:run的实现只有一次,且对象只有一个
线程常用的API
static Thread currentThread(); | 获取当前的线程对象 |
long getId(); | 获取标识符 |
String getName(); | 获取线程的名字 |
int getPriority(); | 获取线程的优先级 优先级有十级,1-10,10最高 |
boolean isAlive(); | 获取当前线程是否为活动状态 |
boolean isDaemon(); | 获取当前线程是否为守护线程 |
boolean isInterrupted(); | 获取线程是否中断 |
static void sleep(long 毫秒数); | 指定的毫秒数内,让当前正在执行的线程休眠(暂停执行),此操作受操作系统计时器和调度程序精度和准确度的影响 |
synchronized关键字,同步。
同步资源,同步锁
- 此关键字可以修饰在方法上
public synchronized void method(){
//代码块
}
当线程1调用method方法时,就会给method方法和调用该方法的对象添加一个锁,被锁的对象不能调用其他被同步锁标识的方法。但是可以调用无锁的方法。
如果方法不执行完毕,其他线程就不会执行此方法,线程排队等待
- 此关键字修饰在对象上
synchronized(某个对象){
//同步代码块
}
当线程1调用同步代码块时,就会给对象添加一个锁
如果代码块不执行完毕,其他线程就不会执行此代码块,线程排队等待
- 此关键字修饰在类上
synchronized(类名.class){
//同步代码块
}
当线程1调用同步代码块时,就会给类添加一个锁
如果代码块不执行完毕,其他线程就不会执行此代码块,线程排队等待
线程池
有一个Executors的线程工具类,此类提供了若干静态方法,这些静态方法用于生成线程池对象。
Executors.new SingleThreadExecutor(); | 创建一个单线程的线程池,这个线程池只有一个线程在工作即单线程的执行任务,如果这个唯一的线程因为异常结束,那么就会有一个新的线程来替代他,因此线程池保证所有的任务是按照任务的提交的顺序来执行 |
Executors.newFilxedThreadPool(); | 常见固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大的大小,线程池的大小一旦达到最大,就会保持不变,如果某个线程因为执行异常而结束,那么线程就会补充一个新的线程 |
Executors.newCacheedThreadPool(); | 创建一个可以缓冲的线程池,如果线程池大小超过了处理的任务所需要的线程,就回收部分线程,当任务数增加的时候,此线程池又可以智能的添加新线程来处理任务,此线程池不会对线程池大小做限制,线程池的大小完全依赖操作系统能够创建的最大的大小 |
Executors.newScheduledThreadPool(); | 创建一个大小无限制的线程池,此线程池支持定时以及周期性的执行任务的需求 |