线程和进程的概念
进程:一个程序的运行实例,正在进行的程序。通常来说一个程序对应着一个进程,它有独立的内存空间和系统资源。
线程:线程是CPU中运算和分派的基本单位,也是进程中运算/的基本单位。一个进程中可以有多个线程,一个线程可以独立完成一个顺序控制流程。
多线程和单线程的概念
单线程:如果程序中只有一个线程,那么称为单线程
多线程:如果程序中有多个线程,那么称为多线程
- 多线程在执行时不一定有单线程执行快。
- 多线程产生的根本原因也是它最根本的优势,就是我们能够在一个程序中,同时同时干多件事,可以充分利用CPU的资源。
- 其实多线程有两个概念,一种是指
单核CPU的多线程
,一种是指多核CPU的多线程
单核CPU:同时只能执行一个线程,但同样可以实现多线程,这个多线程是“抢占式/交替”执行的。
多核CPU:可进行交替执行,也可以实现真正的物理上的并行执行。
主线程
- main()方法即为主线程入口
- 产生其他子线程的线程
- 必须最后完成执行,因为它执行各种关闭动作
Java给我们准备了一个类,来支持我们进行多线程编程Thread类。
在Java中每一个程序有且只有一个主线程,就是我们程序入口main方法。
public static void main(String[] args){
// 通过线程类的静态方法 获取当前线程对象
Thread currentThread = Thread.currentThread();
// 通过Thread的getName()方法能获得线程的名称
System.out.println("当前线程的名字是:"+currentThread.getName());
// 有get必然有set方法
currentThread.setName("MyThread");
System.out.println(" 当前线程名字是:"+currentThread.getName());
}
线程的基本创建和启动
在Java中创建线程的两种方式
- 继承java.lang.Thread类
- 实现java.lang.Runnable接口
使用线程的步骤
①.定义线程
—>②.创建线程对象
—>③.启动线程
—>④.终止线程
继承Thread类
- 定义MyThread类继承
Thread
类 - 重写
run()
方法,编写线程执行体 - 创建线程对象,调用
start()
方法启动线程
public class MyThread extends Thread{
// 重写run()方法
public void run(){
XXXXX
}
}
// main方法调用定义的MyThread类
public static void main(String[] args){
MyThread thread = new MyThread();
thread.start();//启动线程
}
如果再创建第二个线程对象,这时候我们同样给其启动,会发现两个线程是交互执行的,并且没有什么规律这就叫做“CPU的抢占式调度
”;
- 多个线程交替执行,不是真正的“并行”
- 线程每次执行时长由分配的CPU时间片长度决定
启动线程能否直接调用run()方法?
如果线程不调用start()那么不会开启线程,直接使用线程调用run()方法会是主线程调用run()方法,那么自然不会出现交替执行,只有一条main线程执行路径,属于单线程执行模式。
如果调用start()方法 那么就开启了子线程,这样主线程和子线程就开始交替执行,因为CUP再一个时间点只能执行一个线程,因此多个线程是交替执行的,分配的时间片长度不完全一致,可多可少,所以每次运行结果不同,CPU越快,那么分配效果越不好展现。
实现Runnable接口
- 定义MyRunnable类实现
Runnable
接口 实现run()
方法,边写线程执行体- 创建线程对象,调用start() 方法启动线程
Thread 和 Runnable 两种方式的区别
实际上Thread类也实现了Runnable接口。实际开发中多是使用实现Runnable接口的方式。
继承Thread类
- 编写简单,可直接操作线程
- 适用于单线程
实现Runnable接口
- 避免单继承局限性
- 便于共享资源
线程的生命周期
每个Java程序至少由一个线程即主线程 main
。当一个Java程序启动时,JVM会创建主线程,并在该线程种调用main()放法。
在执行new MyThread()之后此时线程处于创建状态
,执行了start()方法之后
,开启了第一个线程(默认线程名称为Thread-x,序号从0开始)此时该线程处于就绪状态
。
同样创建并开启第二个线程后,此时它也处于就绪状态。(故事:所有的人员必须全部准备好才能开始活动,这时候不会因为谁先准备号就谁先开始!)
当某个线程进入了run()方法这时候它就获得了CPU资源
,此时进入运行状态。
(谁先强到,谁加一分)
休眠或者等待输入(Scanner)时线程处于阻塞状态
,释放了CPU资源然后再队列中等待阻塞解除,解除之后就又进入了就绪状态。
线程自然执行完毕,外部干涉终止线程,这时候属于线程死亡状态。
线程的调度
线程的优先级
线程的优先级的高低反映县城的重要或紧急程度,一般情况下,优先级越高线程获得CPU资源概率越大,线程的优先级由1~10表示,1最低,默认为5
线程休眠
让线程暂时睡眠指定时长,线程进入阻塞状态
睡眠时间过后线程再次进入运行状态
public static void sleep(long millis)
millis为休眠时长,以毫秒为单位
调用sleep()方法需处理InterruptedException异常
public class Wait{
public static void bySec(long s){
for(int i = 0;i < s;i ++){
System.out.println(i + 1 + "秒");
try{
Thread,sleep(1000);
}catch(InterruptedException){
}
}
}
}
线程强制运行
使当前线程暂停,等待其他线程结束后在进行
void join(long millis)
等待该线程运行结束的时间为millis毫秒。(该线程强制要运行的时间)
线程礼让
yueld()方法可以让当前正在执行的线程暂停,但它不会阻塞线程,它知识将该线程从运行状态转入就绪状态。
线程同步
同时有多个线程在执行,虽然它们是交替执行,但是速度切换很快,如果有一些资源被共享,可能导致资源出错。
线程并发
同一个资源被多个线程共享,可以通过“加锁”synchronized解决安全问题。
同步方法
public synchronized void saleTicket(){
}
同步代码块
synchronized(this){
}
常见的线程安全类型
StringBuffer和StringBuilder
它们再使用种没有区别,但是StringBuffer线程安全,而StringBuilder线程不安全,但是同理,线程安全效率就低,效率高线程就不安全。
HashMap和Hashtable
HashTable是线程安全的
HashMap是线程不安全的
ArrayList和Vector
Vertor是线程安全的
ArrayList是线程不安全的