学习目的
知识点
- 进程和多线程简介
1.1 何为进程?
1.2 何为线程?
1.3 何为多线程?
1.4 为什么多线程是必要的?
1.5 为什么提倡多线程而不是多进程? - 线程回路
- 常见问题
- 如何创建一个子线程
4.1 定义一个类继承于Thread 实现run方法
4.2 定义一个类接口于Runnable 实现run方法 - 线程安全
5.1 synchronized
5.2 ReentrantLock
解析
- 进程和多线程简介
1.1 何为进程?
进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。如下图所示,在 windows 中通过查看任务管理器的方式,我们就可以清楚看到 window 当前运行的进程(.exe文件的运行)。
1.2 何为线程?
线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
特点:一个进程最少拥有一个线程(主线程 运行起来就执行线程)
线程之间是共享内存资源(进程申请)
线程之间可以通信(数据传递:多数为主线程和子线程)
1.3 何为多线程?
多线程就是多个线程同时运行或交替运行。单核CPU的话是顺序执行,也就是交替运行。多核CPU的话,因为每个CPU有自己的运算器,所以在多个CPU中可以同时运行。
1.4 为什么多线程是必要的?
个人觉得可以用一句话概括:开发高并发系统的基础,利用好多线程机制可以大大提高系统整体的并发能力以及性能。
1.5 为什么提倡多线程而不是多进程?
线程就是轻量级进程,是程序执行的最小单位。使用多线程而不是用多进程去进行并发程序的设计,是因为线程间的切换和调度的成本远远小于进程。 - 线程回路
每一个线程都有自己的运行回路(生命周期:线程的状态)
- NEW:新建状态 准备好了一个多线程的对象,即执行了new Thread(); 创建完成后就需要为线程分配内存
- RUNNABLE:就绪状态 只要抢到时间片就可以运行这个线程 调用了start()方法, 等待CPU进行调度
- RUNNING:运行状态 执行run()方法
- BLOCKED:阻塞状态 暂时停止执行线程,将线程挂起(sleep()、wait()、join()、没有获取到锁都会使线程阻塞), 可能将资源交给其它线程使用
- WAITING:等待状态 wait
TERMINATED:终止状态 线程销毁(正常执行完毕、发生异常或者被打断interrupt()都会导致线程终止)
- 常见问题
- 为什么要创建子线程?
如果在主线程中存在有比较耗时的操作:下载视频 上次文件
这些操作会阻塞主线程,后面的任务必须要等这些任务执行完成之后才能执行。 - 为了不阻塞主线程?
需要将消耗时的任务放在主线程
- 如何创建一个子线程
4.1 定义一个类继承于Thread 实现run方法
join:让当前这个线程阻塞 等join的线程执行完毕再执行
SetName:设置线程名称
getName:获取线程名称
currentThread:获取当前的任务对象
- 继承类
class TestThread extends Thread{
//实现run方法
//方法里面就是具体需要执行的代码
@Override
public void run() {
String name=Thread.currentThread().getName();
//System.out.println(name);
for (int i = 0; i < 100; i++) {
System.out.println(name+" "+(i+1));
if (this !=MyClass.tt2){
try{
MyClass.tt2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
super.run();
}
}
- 具体使用
public static void main(String[] args) {
//main方法里面执行的代码是在主线程里面执行的
//主线程名称main
String name=Thread.currentThread().getName();
System.out.println(name);
//创建对象
TestThread tt = new TestThread();
//设置子线程的名称
tt.setName("子线程1");
//开启任务
tt.start();//tt.start() 错误
// for (int i = 0; i < 5; i++) {
// System.out.println("main"+(i+1));
// }
//TestThread tt2 = new TestThread();
tt2 = new TestThread();
//设置子线程的名称
tt2.setName("子线程2");
//开启任务
tt2.start();
4.2 定义一个类接口于Runnable 实现run方法
a.创建任务 创建类实现Runnable接口
b.使用Thread 违这个任务分配线程
c.开始任务 start
- 实现接口
class XJTThread implements Runnable{
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
- 具体使用及4种使用方式
//接口 抽象方法
//创建一个任务:创建一个类实现Runable接口
XJTThread xt=new XJTThread();
//1. 使用Thread操作这个任务
Thread t=new Thread(xt);
t.setName("子线程1");
t.start();
Thread t2=new Thread(xt);
t2.setName("子线程2");
t2.start();
//2. 这个任务只需要使用一次
Thread t=new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
});
t.setName("子线程1");
t.start();
//3. 创建线程份同时 直接开启线程任务
//不需要操作线程对象本身
Thread t=new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}).start();
//4. 使用Lambda表达式
//不建议:阅读性太差
new Thread(()->{
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}).start();
-
线程安全(synchronized lock :加锁解锁)
多个线程操作同一个资源,线程无法确定自己什么时候被阻塞,容易导致数据错误
5.1 synchronized
- 同步代码块
synchronized(监听器/对象/锁){
需要同步的代码
}
实现
接口:
class Ticket implements Runnable{
//定义所有车票的数量
public static int num=100;
String name;
public Ticket(String name){
this.name=name;
}
static final Object obj=new Object();
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
//先判断有没有票
synchronized (obj) {
//需要同步的代码
if (num > 0) {
System.out.println(name + "出票:" + num);
num--;
} else {
break;
}
main:
//火车站买票
//全国的买票系统就一个 地点有多个 重庆 上海 北京
Ticket ticketCQ=new Ticket("重庆");
Thread t1=new Thread(ticketCQ);
t1.start();
Ticket ticketSH=new Ticket("上海");
Thread t2=new Thread(ticketSH);
t2.start();
- 同步方法
同步监听器是当前对象本身,必须确保多个对象的同步方法是操作的统一一个对象
public synchronized void test()
其实本质就是同步代码块
等价于
synchronized (this){
test()
}
实现
@Override
public void run() {
synchronized (this) {
test();
}
}
public synchronized void test(){
for (int i = 1; i <= 100; i++) {
//需要同步的代码
if (num > 0) {
System.out.println(name + "出票:" + num);
num--;
} else {
break;
}
5.2 ReentrantLock
//创建可重入的锁
static ReentrantLock lock=new ReentrantLock();
Condition condition=lock.newCondition();
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
//先判断有没有票
//需要同步的代码
lock.lock();
if (num > 0) {
System.out.println(name + "出票:" + num);
num--;
} else {
break;
}
lock.unlock();
}