文章目录
- 1、多线程
- 1.1 什么是进程?什么是线程?
- 1.2 Java 程序中来讲,当在 dos 命令窗口中输入:
- 1.3 一个进程就是一个独立的软件,一个线程就是进程里面的执行单元
- 1.4 使用了多线程的机制之后,main() 方法的结束,程序也是有可能不会结束?
- 1.5 多线程的并发,分析:对于单核的CPU来说,可以做到真正的多线程并发吗?
- 1.6 java 中的实现线程的方式,存在两种,哪儿两种方式?
- 1.7 关于两个线程之间的运行有先有后,有多有少
- 1.8 线程生命周期图(重要!!!)
- 1.9 线程的调度模型
- 1.10 多线程并发环境下面的数据安全问题
- 1.10.4 同步
- 1.11 怎么解决线程安全的问题呢?
- 1.12 线程这一块的其他内容
1、多线程
1.1 什么是进程?什么是线程?
进程是一个应用程序,一个进程是一个应用软件;
线程是一个进程中的执行单元,执行场景;
一个进程可以启动多个线程;
1.2 Java 程序中来讲,当在 dos 命令窗口中输入:
java HelloWorld 回车之后,会先启动 JVM ,JVM 是一个进程;
JVM 在启动一个主线程调用 main() 方法,同时启动一个线程进行负责看护,回收垃圾;
最起码,现在的 java 程序中有两个线程是并发的,一个是垃圾回收线程,一个是执行 main() 主方法的线程;
1.3 一个进程就是一个独立的软件,一个线程就是进程里面的执行单元
进程和进程之间的内容是独立不共享的(比如两个公司之间的相关东西是不会共享的);
同一个进程下面的线程之间有的内存是共享的;
在 Java 语言中:
线程A 和线程B 之间的堆内存和方法区之间是内存共享的,但是栈内存之间是独立的,一个线程一个栈;
假设同时启动 10 个线程,是同时会产生 10 个栈的,每个栈之间是不会干扰的,各自执行各自的东西,这就是多线程并发;
火车站可以看作是一个进程,每个售票窗可以看作是一个线程,可以同时在多个窗口中购票,是不需要相互等待的,所以多线程并发(同时执行,是可以提高效率的);
Java 中之所以存在多线程机制,目的就是为了提高程序的处理效率;
1.4 使用了多线程的机制之后,main() 方法的结束,程序也是有可能不会结束?
对的,因为 main() 方法结束之时主线程的结束,主栈空了,其他的线程(或者栈)还在继续的执行;
main() 方法的结束只是代表了主线程的结束,其他的方法仍然在执行;
栈空间是独立的,一个空间中有一个栈;
两个线程之间的栈内存之间独立的,不进行共享,但是堆内存和方法去两个线程之间是可以共享的;
1.5 多线程的并发,分析:对于单核的CPU来说,可以做到真正的多线程并发吗?
多核 CPU 可以做到真正的线程并发;
4核 CPU 在一个时间点上,可以真正的存在 4 个进程并发;
什么是多线程并发?
t1 线程执行 t1 的;
t2 线程执行 t2 的;
t1 线程是不会影响到 t2 的;
t2 线程是不会影响到 t1 的;
上面的描述为真正的多线程的并发;
单核的CPU 表示只有一个大脑,不能做到真正的多线程并发,可以做到多线程并发的感觉;
单核的 CPU 在某个单独的时间点上面,只能处理一个事情,但是由于 CPU 的处理速度比较快,在线程之间的切换速度是比较快的, 感觉就是在多线程的运作(同时并发的);
1.6 java 中的实现线程的方式,存在两种,哪儿两种方式?
Java 支持多线程的机制,Java的多线层呢已经实现,需要实现即可;
1、编写一个类,直接继承 java.lang.Thread,重写 run 方法
/**
* 实现线程的第一个方式:
* 编写一个类直接继承 java.lang.Thread 方法,进行 run 方法的重写即可
*
* 如何创建线程对象?怎么启动线程?
* 创建:main 方法,这里的代码是属于主线程的,在主栈中运行
* new 即可
* 启动:新建一个分支线程对象
* 调用线程中的 start 即可
*
* 方法体里面的代码是从上到下面按照顺序执行的,是不会变的;
*/
public class ThreadTest02 {
public static void main(String[] args) {
// 在这里是main 方法,代码属于主线程,在主栈中运行
MyThread myThread = new MyThread();
// 线程的启动
// start 方法中的作用:
// 动一个分支线程,在 JVM 中开辟一个新的栈空间,启这段代码任务完成之后瞬间就结束了,
// 这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开辟出来 start() 方法就结束了,线程就启动成功了
// 启动成功的线程,会自动调用 run 方法,并且在 run 方法在分支栈的底部(压栈)最先进去的,所以在最底部;
// run 方法在分支栈的底部,main 方法在主栈的底部,run 和 main 是平级别的;
// myThread.run(); // 就是普通的单线程 不会启动分支栈,不会启动分支线程
myThread.start(); // 瞬间结束这一句,开辟了栈空间就结束
// myThread.run() 如果只是调用了这一个方法,那么就是只是调用了方法,没有使用新的线程,不会分配新的分支栈
// 分支线程启动开始,主线程了分支线程都在执行了
// 这里的代码运行在主线程中
for (int i = 0; i < 1000; i++) {
System.out.println("主线程-->" + i);
}
}
}
class MyThread extends Thread {
// 对于继承方法的重写,实现多线程
@Override
public void run() {
// 编写程序,这段程序运行在分支栈中
for (int i = 0; i < 1000; i++) {
System.out.println("分支线程 -->" + i);
}
}
}
2、编写一个类,实现 java.lang.Runnable 接口,实现 run 方法(主要是实现了接口,这个是重要的)
/**
* 实现线程的第二种方式:编写一个类实现下面的接口;使用了接口的方式实现了线程的并发;
* java.lang.Runnable
*/
public class ThreadTest03 {
public static void main(String[] args) {
// // 创建一个可以运行的对象
// MyRunnable r = new MyRunnable();
//
// // 将可以运行的对象进行封装
// Thread t = new Thread(r);
// 上面的代码进行合并
Thread t = new Thread(new MyThread());
// 启动线程
t.start();
for (int i = 0; i < 1000; i++) {
System.out.println("主线程" + i);
}
}
}
// 下面不是一个线程,是一个可以运行的类
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("分支线程" + i);
}
}
}
1.7 关于两个线程之间的运行有先有后,有多有少
/**
* 下面的程序的输出特点:
* 有先有后,有多有少?如何解释?
* 1、控制台只有一个
* 2、线程抢到了执行权
* 1、new 出来的线程对象(处于新建状态)
* 2、调用 start 方法,进去就绪状态
* 就绪状态也叫做可运行状态,表示当前线程具有抢夺CPU 时间片的权利(CPU 时间片就是执行权)。当一个线程抢到了CPU
* 时间片之后,开始执行 run 方法,run 方法开始执行的时候,标志着线程进入到了运行状态
* 3、运行状态
* run 方法开始执行,标志者程序进去运行状态,但是在之前的CPU 时间片被使用结束之后,重新回去就绪状态准备抢CPU 时间片
* 4、在运行状态和就绪状态时间之间,是需要使用 JVM 进行资源的分配的
* 5、最后程序中的线程周期将会结束
* 6、当一个线程遇到了阻塞状态的时候,比如接收用户的键盘输入,或者使用sleep() 方法等;
* 此时的线程会进入阻塞状态,阻塞状态的线程会放弃之前占有的CPU 时间片;
* 当阻塞状态结束后,当前的程序会进去就绪状态,在这里分配到到了CPU 时间片之后,才会进行运行状态;
*/
* 关于线程对象的生命周期?
* 新建状态
* 就绪状态
* 运行状态
* 阻塞状态
* 死忙状态
1.8 线程生命周期图(重要!!!)
1.9 线程的调度模型
1.9.1 常用的线程调度模型:
1.9.1.1抢占调度模型
线程的优先级比较高,抢到的CPU 时间片的概率高一些(Java)
java 提供什么与线程的调度有关系?
实例方法
设置优先级 void setPriority(int newPriority) 设置线程的优先级别;
获取优先级 int getPriority() 获取线程的优先级别;
最低优先级 1
最高是10
默认是 5
void join()
合并线程
当前线程进入阻塞状态,当加进来的线程执行完毕之后,原来的线程进入执行状态;
静态方法
static void yield() 让位方法;
暂停了当前正在执行的线程对象,并切执行其他的线程;
yield() 方法不是阻塞方法,让当前的线程让位,让给其他线程进行使用;
yield() 方法,让当前的程序,从运行状态,回到就绪状态,回来之后,还有可能继续抢到CPU 时间片;
1.9.1.2均分式调度模型
平局分配的CPU 时间片,每个线程占有的CPU 时间片是一样的,平均分配,一切平等
1.10 多线程并发环境下面的数据安全问题
1.10.1 为什么是重点?
因为在开发过程当中,项目运行在服务器中,服务器已经把,线程的定义,线程对象的创建,线程的启动都已经实现结束了,都不需要编写;
重要的是:编写的程序需要放置在多线程的运行环境中,更加需要关注的是:数据在多线程的并发环境下面是否是安全的(*****重点)
1.10.2 什么时候数据在多线程并发的环境中会存在安全问题?
三个条件:
1、多线程并发
2、有共享数据
3、共享数据有修改的行为
满足上面的条件之后,就会存在线程的安全问题;
1.10.3 如何解决线程安全的问题?
当多线程并发的环境下面,有共享数据,并且这个数据会被修改,此时就会存在线程安全问题;
解决的办法是排队执行线程,(不能并发),使用排队执行解决线程的安全问题,这种机制叫做线程同步机制;
线程同步,就是线程需要进行同步,不能进行线程的并发;
线程排队了,会牺牲效率,只有数据是安全的,才能谈一谈线程的效率,数据不安全,效率无从谈起;
1.10.4 同步
专业术语:
同步编程模型:线程t1,t2在线程 t1 执行的时候,t2 线程想要执行,必须等待线程 t1 执行结束之后,才能继续执行;或者说在 t2 线程执行的时候,必须等待 t1 线程的执行结束;两个线程之间发生了等待关系;(排队)
异步编程模型:线程 t1,t2 各自执行各自的,谁也不需要等谁,就是多线程并发,效率是比较高的;(并发)
1.11 怎么解决线程安全的问题呢?
一上来就是选择线程同步吗?
不是,synchronized 会导致程序使用起来比较缓慢,不建议一上来就使用这种方法,用户网站的吞吐量会变低,不得已的时候,使用线程同步机制;
第一种方案:
尽量使用局部变量,从而代替掉实例变量以及静态变量;
第二种方案:
如果必须是实例变量,可以考虑创建多个对象,这样子的实例变量就不会共享了(一个线程使用一个对象,一百个线程对应使用的是一百个对象),对象不共享了,就不会出现数据安全的问题了;
第三种方案:
不能使用局部变量,不能创建多个对象的时候:
只能选择 synchronized 线程同步的机制;
1.12 线程这一块的其他内容
(1)守护线程
后台线程,垃圾回收线程类似;
Java 线程的分类:
1、用户线程
2、守护线程(后台线程)(垃圾回收线程)
守护线程的特点:
一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束;
注意:主线程 main 方法是一个用户线程
守护线程用在什么地方?
作用就是守护,用户线程结束了之后,就没有守护的必要性了;
每天 0点的时候,进行数据的备份,可以把定时器设置为守护线程;
每次到了0点的时候备份一次;
(2)定时器
定时器的作用:
间隔特定的时间,执行特定的程序;
每周进行银行账户的总账操作,每天进行数据的备份操作,此时就需要使用定时来实现相关的代码;
实际的开发中,隔多久时间一个特定的需求,是一种常见的需求方式;
Java中可以使用多种多样的方式来实现;
1、使用 sleep 睡醒了之后,即使的起来即可,是一种比较原始的计数器,需要自己写;
2、JDK 内置了一种定时器,叫做 java.lang.Timer ,拿过来直接使用即可,在开发中使用的比较少,目前使用的额比较多的定时器是:Spring 框架里面的 SpringTask 进行定时,Spring 进行简单的配置就能实现;
(3)实现线程的第三种方式:
FutureTask 方式,实现 Callanle 接口,(JDK8 新特性)