第八章:多线程
一,线程和进程
- 进程 : 应用程序的执行实例,有独立的内存空间和系统资源,一个应用程序只有一个进程。
- 线程: CPU调度和分派的基本单位,进程中执行运算的最小单位,可完成一个独立的顺序控制流程,一个进程中可以有多个线程。
我们window系统可以按下Ctrl+Alt+.
选择任务管理器的进程选卡,来看当前执行的进程和线程。
二,多线程
什么是多线程
- 如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为“多线程”,多个线程交替占用CPU资源,而非真正的并行执行.
多线程优点
- 充分利用CPU的资源
- 简化编程模型
- 带来良好的用户体验
三,实现多线程的三种方式
主线程:主线程也就是我们一直都在用的main方法,我们要在主线程中通过java给的Thread类,去创建多线程,并启动关闭。(Thread类:Java提供了java.lang.Thread类支持多线程编程)
1.实现多线程的第一种方式:继承Thread类
1.继承Thread类。
2.重写它的run()
方法。
3.在主线程中创建该类对象。
4.调用start()
方法。
自定义线程类:
public class MyThread extends Thread {
@Override
public void run() {
for(int i=0;i<20;i++){
System.out.println((i+1)+".\t你好,来自线程:"+Thread.currentThread().getName());
}
}
}
主线程:
public class Client{
public static void main(String[] args) {
Thread t1 = new MyThread();
Thread t2 = new MyThread();
t1.start();
t2.start();
}
}
运行部分代码:
主线程执行到start方法的时候,创建的对应线程就会启动,要注意,多个线程执行是一个抢CPU的过程,谁抢到CPU,谁就执行run方法,有可能执行一行代码或者多行。
2.实现多线程的第二种方式:实现Runnable接口
1.实现Runnable接口。
2.重写它的run()
方法。
3.在主线程中创建该类对象。
4.把对象传入Thread的构造方法内。
5.调用start()
方法。
线程类:
public class MyThreadRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println((i + 1) + ".\t你好 来自线程:" + Thread.currentThread().getName());
}
}
}
主线程类:
public class Client {
public static void main(String[] args) {
MyThreadRunnable my = new MyThreadRunnable();//实例化线程类。
Thread t1 = new Thread(my);//把接口对象传入Thread构造方法中。
Thread t2 = new Thread(my);
t1.start();
t2.start();
}
}
部分运行结果:
1.实现多线程的第一种方式:实现Callable接口
1.实现Callable接口。
2.重写它的call()
方法。
3.在主线程中创建该类对象。
4.把对象传入FutureTask的构造方法内。
5.再把FutureTask对象传入Thread构造方法内。
6.调用start()
方法。
线程类:
public class CallableThread implements Callable<String> {
@Override
public String call() throws Exception {
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
return "success";
}
}
主线程类:
public class Client {
public static void main(String[] args) {
CallableThread ct = new CallableThread();
FutureTask<String> ft1 = new FutureTask<>(ct);
FutureTask<String> ft2 = new FutureTask<>(ct);
Thread t1 = new Thread(ft1);
Thread t2 = new Thread(ft2);
t1.start();
t2.start();
}
}
注意:新建一个线程要新建一个FutureTask和Thread。
部分运行结果:
四,线程的状态
- 创建状态: 在实例化一个Thread的时候,是该状态。
- 就绪状态: 在执行start方法的时候,是就绪状态。
- 运行状态: 在该线程抢到CPU的时候,是运行状态。
- 阻塞状态: 等待用户输入,线程休眠等等进入阻塞状态。
- 死亡状态:线程自然执行完毕或者外部干涉终止线程时,进入死亡状态。
五,线程调度
线程调度:线程调度是指按照特定机制为多个线程分配CPU的使用权。
我们接下来了解一下调度方法。
线程优先级
- 线程优先级由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);
//省略代码……
}}
线程休眠
- 让线程暂时睡眠指定时长,线程进入阻塞状态
- 睡眠时间过后线程会再进入可运行状态
语法: 线程对象名.sleep(long mills);
注意:millis为休眠时长,以毫秒为单位调用sleep()方法需处理InterruptedException异常
示例:
for (int i = 0; i < 10; i++) {
System.out.println(i + 1 + "秒");
try {
Thread.sleep(1000); //休眠一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
线程的强制运行
谁调用了join()方法就强制执行谁,执行完该线程后,会正常执行其他线程。
join():强制执行当前线程,join写在哪个线程,就阻塞谁。
public final void join()
public final void join(long mills)
public final void join(long mills,int nanos)
//millis:以毫秒为单位的等待时长
nanos:要等待的附加纳秒时长
需处理InterruptedException异常
线程的礼让
public static void yield(): 线程的礼让,只是提供一种可能,但是不能保证一定会实现礼让.
- 暂停当前线程,允许其他具有相同优先级的线程获得运行机会
- 该线程处于就绪状态,不转为阻塞状态
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
使用synchronized修饰的方法控制对类成员变量的访问。
语法:
- 访问修饰符 synchronized 返回类型 方法名(参数列表){……}
- synchronized 访问修饰符 返回类型 方法名(参数列表){……}
// synchronized就是为当前的线程声明一把锁
同步代码块
使用synchronized关键字修饰的代码块
语法:
synchronized(synObject){
//需要同步的代码
}
synObject为需同步的对象,通常为this,效果与同步方法相同。
补充:
多个并发线程访问同一资源的同步代码块时:
同一时刻只能有一个线程进入synchronized(this)同步代码块
当一个线程访问一个synchronized(this)同步代码块时,其他synchronized(this)同步代码块同样被锁定
当一个线程访问一个synchronized(this)同步代码块时,其他线程可以访问该资源的非
synchronized(this)同步代码
七,线程安全的类型
- StringBuffer
- Vector
- Hashtable
:
多个并发线程访问同一资源的同步代码块时:
同一时刻只能有一个线程进入synchronized(this)同步代码块
当一个线程访问一个synchronized(this)同步代码块时,其他synchronized(this)同步代码块同样被锁定
当一个线程访问一个synchronized(this)同步代码块时,其他线程可以访问该资源的非
synchronized(this)同步代码
七,线程安全的类型
- StringBuffer
- Vector
- Hashtable
注意:线程安全的类型,效率低;没有线程安全的类型,效率高。为达到安全性和效率的平衡,可以根据实际场景来选择合适的类型