进程和线程的概念
进程是应用程序的执行实例有独立的内存空间和系统资源。
线程是进程中执行运算的最小单位,可完成一个独立的顺序控制流程
一。一个进程可以包含多个线程,每个线程都独立执行特定的任务,
是CPU调度和分派的基本单位。
多线程的概念:
- 如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为“多线程”
- 多个线程交替占用CPU资源,而非真正的并行执行
多线程的好处:
- 充分利用CPU的资源
- 简化编程模型
- 带来良好的用户体验
主线程:
Thread类
Java提供了java.lang.Thread类支持多线程编程
主线程
- main()方法即为主线程入口
- 产生其他子线程的线程
- 必须最后完成执行,因为它执行各种关闭动作
public static void main(String args[]) {Thread t= Thread.currentThread(); System.out.println("当前线程是: "+t.getName()); t.setName("MyJavaThread"); System.out.println("当前线程名是: "+t.getName()); }
线程的创建和启动
在Java中,可以通过继承Thread类或实现Runnable接口来创建线程。
- 继承Thread类:
I. 定义MyThread类继承Thread类
||. 重写run()方法,编写线程执行体
|||. 创建线程对象,调用start()方法启动线程
public class MyThread extends Thread {public void run() {// 线程执行的代码}
}// 创建并启动线程
MyThread thread = new MyThread();
thread.start();
补充:
多线程里的多个线程交替执行,而不是真正的“并行”。
线程每次执行时长由分配的CPU时间片长度决定。
- 实现Runnable接口:
|.定义MyRunnable类实现Runnable接口
||.实现run()方法,编写线程执行体
|||.创建线程对象,调用start()方法启动线程
public class MyRunnable implements Runnable {public void run() {// 线程执行的代码}
}
// 创建并启动线程
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
调用线程的run()和start()方法的区别如下:
run()方法:直接调用线程对象的run()方法会在当前线程中执行该方法的代码,就像普通的方法调用一样。并不会创建新的线程,而是在当前线程中按顺序执行。所以只有主线程main一条执行路径。
start()方法:调用线程对象的start()方法会创建一个新的线程,并在新的线程中执行run()方法的代码。start()方法会启动线程的执行,然后立即返回,不会阻塞当前线程。所以具体执行过程有多条执行路径,主线程和子线程并行交替执行
总结来说,直接调用run()方法只会在当前线程中执行方法的代码,不会创建新的线程。而调用start()方法会创建新的线程,并在新线程中执行方法的代码。通常情况下,我们应该使用start()方法来启动线程,以实现多线程并发执行的效果。
比较两种创建线程的方式
继承Thread类:
- 编写简单,可直接操作线程
- 适用于单继承
实现Runnable接口
- 避免单继承局限性
- 便于共享资源
线程的状态
线程在生命周期中有多个状态:
线程共包括以下 5 种状态:
下面是使用Markdown语法写的嵌套列表:
线程共包括以下 5 种状态:
-
新建状态(New): 线程对象被创建后,就进入了新建状态。例如,
Thread thread = new Thread()
。 -
就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的
start()
方法,从而来启动该线程。例如,thread.start()
。处于就绪状态的线程,随时可能被CPU调度执行。 -
运行状态(Running): 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
-
阻塞状态(Blocked): 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
- 等待阻塞 – 通过调用线程的
wait()
方法,让线程等待某工作的完成。 - 同步阻塞 – 线程在获取
synchronized
同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。 - 其他阻塞 – 通过调用线程的
sleep()
或join()
或发出了I/O请求时,线程会进入到阻塞状态。当sleep()
状态超时、join()
等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
- 等待阻塞 – 通过调用线程的
-
死亡状态(Dead): 线程执行完了或者因异常退出了
run()
方法,该线程结束生命周期。
线程调度的常用方法
线程调度指操作系统或Java虚拟机按照特定机制为多个线程分配CPU的使用权。Java提供了一些常用的线程调度方法:
方法 | |
---|---|
void setPriority(int newPriority) | 更改线程的优先级 |
static void sleep(long millis) | 在指定的毫秒数内让当前正在执行的线程休眠 |
void join() | 等待该线程终止 |
static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
void interrupt() | 中断线程 |
boolean isAlive() | 测试线程是否处于活动状态 |
- 线程优先级
- 线程优先级由1~10表示,1最低,默认优先级为5
- 优先级高的线程获得CPU资源的概率较大
public static void main(String[] args) {Thread t1 = new Thread(new MyThread(),"线程A");Thread t2 = new Thread(new MyThread(),"线程B");t1.setPriority(Thread.MAX_PRIORITY);t2.setPriority(Thread.MIN_PRIORITY);//省略代码……}}
- static void sleep(long millis):使当前线程暂停执行一段时间。
- millis为休眠时长,以毫秒为单位
- 调用sleep()方法需处理InterruptedException异常
try {Thread.sleep(1000); // 暂停1秒
} catch (InterruptedException e) {e.printStackTrace();
}
- yield():暂停当前正在执行的线程,让出CPU资源给其他线程。
Thread.yield();
- join():等待其他线程执行完毕再继续执行。
-
public final void join()
-
public final void join(long mills)
-
public final void join(long mills,int nanos)
- millis:以毫秒为单位的等待时长
- nanos:要等待的附加纳秒时长
- 需处理InterruptedException异常
Thread thread1 = new Thread();
Thread thread2 = new Thread();thread1.start();
thread2.start();try {thread1.join(); // 等待thread1执行完毕thread2.join(); // 等待thread2执行完毕
} catch (InterruptedException e) {e.printStackTrace();
}
5.public static void yield()线程的礼让
- 暂停当前线程,允许其他具有相同优先级的线程获得运行机会
- 该线程处于就绪状态,不转为阻塞状态
只是提供一种可能,但是不能保证一定会实现礼让
public class MyThread implements Runnable{public void run(){for(int i=0;i<5;i++){System.out.println(Thread.currentThread().getName()+"正在运行:"+i);if(i==3){System.out.print("线程礼让:");Thread.yield(); } } }}
线程的同步
多个线程访问共享资源时可能会出现数据不一致的问题,需要使用同步机制来保证线程安全。
- synchronized关键字:可以修饰方法或代码块,确保同一时间只有一个线程执行。
方法:
代码块:
public synchronized void synchronizedMethod() {// 同步的方法
}public void someMethod() {synchronized (this) {// 同步的代码块}
}
- Lock对象:使用Lock接口的实现类来实现同步。
Lock lock = new ReentrantLock();lock.lock(); // 获取锁try {// 同步的代码块
} finally {lock.unlock(); // 释放锁
}
多个并发线程访问同一资源的同步代码块时
- 同一时刻只能有一个线程进入synchronized(this)同步代码块
- 当一个线程访问一个synchronized(this)同步代码块时,其他synchronized(this)同步代码块同样被锁定
- 当一个线程访问一个synchronized(this)同步代码块时,其他线程可以访问该资源的非synchronized(this)同步代码
Java中常见的线程安全类型及其比较:
-
ArrayList vs. Vector:
- ArrayList是非线程安全的类型,适用于单线程环境下的操作。
- Vector是线程安全的类型,适用于多线程环境下的操作。Vector的方法使用了
synchronized
关键字进行同步,保证了线程安全。
-
Hashtable vs. HashMap:
- Hashtable是线程安全的类型,适用于多线程环境下的操作。Hashtable的方法使用了
synchronized
关键字进行同步,保证了线程安全。 - HashMap是非线程安全的类型,适用于单线程环境下的操作。HashMap的方法没有进行同步,不保证线程安全。
- Hashtable是线程安全的类型,适用于多线程环境下的操作。Hashtable的方法使用了
- StringBuffer vs. StringBuilder:
- StringBuffer是线程安全的类型,适用于多线程环境下的操作。StringBuffer的方法使用了
synchronized
关键字进行同步,保证了线程安全。 - StringBuilder是非线程安全的类型,适用于单线程环境下的操作。StringBuilder的方法没有进行同步,不保证线程安全。
- StringBuffer是线程安全的类型,适用于多线程环境下的操作。StringBuffer的方法使用了
需要注意的是,虽然线程安全的类型在多线程环境下可以保证数据的一致性和正确性,但在性能上可能会有一定的开销。非线程安全的类型在多线程环境下需要额外的同步措施来保证数据的安全性。因此,在选择使用哪种类型时,需要根据具体的需求和场景进行权衡和选择。