一、什么是线程?什么是进程?
简单来说,
进程:⼀个内存中运⾏的应⽤程序,每个进程都有⼀个独⽴的内存空间;
线程:线程是进程中的⼀个执⾏单元,负责当前进程中程序的执⾏,⼀个进程中可以有一个或多个线程
什么是多线程?
如果说我有一个单核的CPU,它某一时刻只能处理一件事,但是它处理事情的速度极快,我可以拿他一边打cs,一边打黑🐒它可以在两个线程之间来回切换,让人感觉这俩是在同时进行的,这能叫多线程吗?肯定不能;真正的多线程并发应该是:A做A的事情,B做B的事情,A与B之间不会相互影响;
线程对象的有生命周期:
①新建状态
②就绪状态
③运行状态
④阻塞状态
⑤死亡状态
作用:多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。
线程的构造方法
线程中最基础的方法
怎么使用多线程? 实现线程有两种方式;
第一种方式:继承
编写一个类,直接继承Thread,然后再重写其中的run()方法;然后可以调用该线程对象的start()方法启动该线程;
eg
public class ThreadTest02 {
public static void main(String[] args) {
MyThread t = new MyThread();
// 启动线程
//t.run(); // 不会启动线程,不会分配新的分支栈。(这种方式就是单线程。)
t.start();
// 这里的代码还是运行在主线程中。
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);
}
}
}
如果直接调用t.run()不会启动线程,也不会分配新的分支栈;
而t.start()会启动一个分支线程,再JVM中开辟一个新的栈空间;有了空间之后,线程就自动启动了,然后会调用重写的run()方法
第二种方式:实现Runna接口来实现run()方法;
eg
public class ThreadTest03 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
// 启动线程
t.start();
for(int i = 0; i < 100; i++){
System.out.println("主线程--->" + i);
}
}
}
// 这并不是一个线程类,是一个可运行的类。它还不是一个线程。
class MyRunnable implements Runnable {
@Override
public void run() {
for(int i = 0; i < 100; i++){
System.out.println("分支线程--->" + i);
}
}
}
第二种方法相比于第一种方法,可以再去继承另一个类更加灵活;
线程中常用的方法
方法名 | 作用 |
static Thread currentThread() | 获取当前线程对象 |
String getName() | 获取线程对象名字 |
void setName(String name) | 修改线程对象名字 |
void sleep(long millis) | 让当前线程休眠millis秒 |
void interrupt() | 终止线程睡大觉 |
void stop() | 强行结束一个线程(不推荐使用,会丢失数据) |
当线程没有名字时,默认为Thread-0,Thread-1.......
public class demo {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
System.out.println(Thread.currentThread().getName());//main中的进程默认名称为main
}
}
class MyThread extends Thread {
@Override
public void run() {
Thread t = Thread.currentThread();
System.out.println(t.getName());//Thread-0
t.setName("PatrickStar's Thread");
System.out.println(t.getName());//PatrickStar's Thread
}
}
sleep方法可以让线程直接去睡大觉,可惜单位是毫秒,只能休息一会,这么做的作用是让线程进入阻塞状态,放弃占有CPU时间片,把它让给其它的线程用;
run()方法内的异常只能使用try-catch而不能使用throws,因为run()方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常;
一个线程只能启动一次,启动多次会报错
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
System.out.println(Thread.currentThread());
System.out.println(Thread.currentThread().getName());
t.start();
}//编译的时候并不会有异常,但是运行的时候会抛出异常
既然stop方法不推荐那么我们应该怎么终止线程?可以用一个Boolean类型的标记点,
eg.
package threadTest;
public class ThreadTest10 {
public static void main(String[] args) {
MyRunable4 r = new MyRunable4();
Thread t = new Thread(r);
t.setName("t");
t.start();
// 模拟6秒
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 终止线程
// 你想要什么时候终止t的执行,那么你把标记修改为false,就结束了。
r.flag = false;
}
}
class MyRunable4 implements Runnable {
// 打一个布尔标记
boolean flag = true;
@Override
public void run() {
for (int i = 0; i < 10; i++){
if(flag){
System.out.println(Thread.currentThread().getName() + "--->" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
// return就结束了,你在结束之前还有什么没保存的。
// 在这里可以保存呀。
//save....
//终止当前线程
return;
}
}
}
}//前后经过五秒,输出从0到4
线程调度的优先级;
-
抢占式调度模型:
那个线程的优先级比较高,抢到的CPU时间片的概率就高一些/多一些。
java采用的就是抢占式调度模型。 -
均分式调度模型:
平均分配CPU时间片。每个线程占有的CPU时间片时间长度一样。
平均分配,一切平等。
有一些编程语言,线程调度模型采用的是这种方式。
实例方法最低优先级为1,默认为5,最高为10;优先级高的会比优先级低的获取CPU时间片的可能高一点;在优先级设置的时候有几个常量可以使用
Thread t = new Thread(new MyRunnable7());
t.setPriority(Thread.MAX_PRIORITY);
System.out.println(t.getPriority());//打印10
静态方法;让当前线程从”运行状态“变成”就绪状态“,所有的同等优先级的线程都可以抢到执行权,包括它自己也可以;
实例方法;join
public static void main(String[] args) {
System.out.println("main begin");
Thread t = new Thread(new MyRunnable7());
t.setName("t");
t.start();
//合并线程
try {
t.join(); // t合并到当前线程中,当前线程受阻塞,t线程执行直到结束。
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main over");
}
}
class MyRunnable7 implements Runnable {
@Override
public void run() {
for(int i = 0; i < 10000; i++){
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
正常来讲多线程应该是并行的, 那么如果没有join()方法,上述代码中的"main over"应该是在线程t打印1到10000的过程中打印出来,但是join()方法可以把t线程合并到当前线程,然后执行完t线程之后再打印"main over";所有join()方法可以让当前线程进入"阻塞状态",然后执行完合并进来的线程后再继续刚才的线程;
多线程并发下的安全问题;
什么情况下会存在安全问题:
①多线程并发
②有共享数据
③共享数据有修改行为
当我的线程A对数据temp有修改,线程B对temp也要有修改,这时数据就是不安全的;那如果我们有共享的数据,又对这个数据要修改,这个时候就要线程排队执行,不能并发处理用排队执行来解决安全问题,这种机制叫做线程同步机制;排队执行自然线程的效率就会降低,但是在数据安全面前,放弃一点效率也不是大问题;
其实这里有两个专业术语:
异步编程模型:也就是并发,线程t1只用管线程t1的线程t2只用管线程t2的;
同步编程模型:也就是排队,线程t2只能在线程t1完成之后再执行
synchronized 线程同步
synchronized(共享的数据){
// 线程同步代码块。
}
这里我们创建一个类ThreadSyn;
public class ThreadSyn implements Runnable{
private volatile static int count = 0;
@Override
public void run() {
synchronized(this) {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
然后执行下面的语句
ThreadSyn threadSynOne = new ThreadSyn();
Thread thread1 = new Thread(threadSynOne,"ThreadSynOne");
Thread thread2 = new Thread(threadSynOne,"ThreadSynTwo");
thread1.start();
thread2.start();
想想如果没有synchronized语句会输出什么,有了synchronized又会输出什么;
没有synchronized的话线程并发处理并且对这同一个数据操作输出的结果ThreadSynOne与Two交叉出现而且每次执行之后count的值结果都不相同
有了同步之后的输出结果如图
详细的Java多线程:Java多线程(超详细!)-CSDN博客
有关线程安全的:
synchronized可以看这个:Java多线程——synchronized使用详解_synchronized用法-CSDN博客
还有一个volatile和并发的三个基本概念:Java volatile关键字最全总结:原理剖析与实例讲解(简单易懂)-CSDN博客
二、stream流
什么是stream流
Stream
将要处理的元素集合看作一种流,在流的过程中,借助Stream API
对流中的元素进行操作;
它可以让我们以更加简洁的方式对集合进行操作;
对stream流的操作分为两种:
1.中间操作 :每次会返回一个新的流,可以有多个
2.终端操作:每个流只能进行一次终端操作,会产生一个新的集合或值
stream的特性:
- stream不存储数据,而是按照特定的规则对数据进行计算,一般会输出结果。
- stream不会改变数据源,通常情况下会产生一个新的集合或一个值。
- stream具有延迟执行特性,只有调用终端操作时,中间操作才会执行。
stream流的使用;
使用stream流之前可以先了解一个与它同版本出现的Lambda,Lambda可以让我们将函数作为参数传递给其它方法
基本语法: (parameters) -> expression 或 (parameters) ->{ statements; }
Lambda表达式由三部分组成:
paramaters:类似方法中的形参列表,这里的参数是函数式接口里的参数。这里的参数类型可以明确的声明也可不声明而由JVM隐含的推断。另外当只有一个推断类型时可以省略掉圆括号。
->:可理解为“被用于”的意思
方法体:可以是表达式也可以代码块,是函数式接口里方法的实现。代码块可返回一个值或者什么都不反回,这里的代码块块等同于方法的方法体。如果是表达式,也可以返回一个值或者什么都不反回;
stream的参数中可能会用到这个参数;
1.stream流的创建:
a.从集合类中(如LIst,Map,Set)创建,使用Stream()方法或者parallelStream()方法,前者创建的是顺序流后者创建的是并行流;parallel()方法可以把顺序流变成并并行流;
List<String> list = Arrays.asList("a", "b", "c");
// 创建一个顺序流
Stream<String> stream = list.stream();
// 创建一个并行流
Stream<String> parallelStream = list.parallelStream();
b.用数组创建
int[] array={1,3,5,6,8};
IntStream stream = Arrays.stream(array);
c. 调用静态方法,of(),iterate(),generate()
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
stream.forEach(System.out::println);//1~6
Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 3).limit(4);
stream2.forEach(System.out::println);//0~9
Stream<Double> stream3 = Stream.generate(Math::random).limit(3);
stream3.forEach(System.out::println);//三个0~1的随机数
2. 中间操作
代码实例:Stream流中间操作方法_stream的中间操作方法-CSDN博客
3.终端操作:
Java 8 Stream终端操作使用详解_java8 stream api的终端操作-CSDN博客
更多的stream实例可以在这篇后面翻看二十个实例;【java基础】吐血总结Stream流操作_java stream流操作-CSDN博客