javaEE初阶————多线程初阶(1)

多线程初阶------------

1,认识线程

1.1 概念

1)线程是什么

线程就是一个"执行流",可以理解为程序执行的最小单位;

可以看成轻量级的进程;

2)为啥要有线程

"并发编程" 的需要,但是我们不是已经有进程了吗,我们要知道,我们进行的是服务器开发,我们在访问网站的时候,一个用户进行访问就是一个进程,用户的数据量是非常庞大的,进程的创建和销毁需要的开销就会变得非常非常大,这样我们就引出了线程,让一个进程中包含一个或多个进程,提升效率,

3)线程和进程的区别

1,进程是操作系统进行资源分配的基本单位,线程是操作系统进行运算调度的基本单位;

2,线程的创建,销毁,调度需要的开销更小;

3,进程之间互不影响,同一进程下的线程会互相影响,创建进程后会自动创建一个线程,第一个线程会涉及到申请资源的操作,其余线程不会涉及,进程销毁才会释放资源,线程的销毁不会释放资源;

4,因为线程是调度相关,所以每一份线程都有调度相关的数据

5,一个进程死掉了不会影响其他进程,但是一个进程中一个线程死掉了就掀桌了,全部都运行不了了;

4)java中线程和操作系统的关系

java中包装好了操作系统中对线程操作的API,但是java是不推荐多进程编程的,我们只去学习多线程编程;

1.2 第一个多线程程序

java 复制代码
class MyThread extends Thread{
    public void run(){
        System.out.println("myThread");
    }
}
public class Demo1 {
    public static void main(String[] args) {
        Thread thread = new MyThread();
        thread.start();

        System.out.println("main");
    }
}

main就是进程刚创建我们自动生成的第一个线程,运行

只是个示范,看接下来的讲解就好;

1.3 创建线程

1)创建对象继承Thread类
java 复制代码
class MyThread2 extends Thread{
    public void run(){
        while(true){
            System.out.println("MyThread2 线程");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
public class Demo2 {
    public static void main(String[] args) {
        Thread thread = new MyThread2();
        
        System.out.println("主线程");
    }
}

我们创建一个类,让它继承Thread类,Thread类中给我们提供了一个run方法,让我们自己去写里面的内容,我们就在自己实现的类中重写run方法,我们循环打印,并且打印一次睡眠1秒,在主线程也就是main方法中,打印主线程,我们来看运行结果;

只有一个主线程,因为我们没有去调度线程,我们可以直接用.run或者是.start来开启线程

java 复制代码
thread.run();

程序一直在运行,这里不明显,我们来借助一个工具,

找到jdk中的bin ,

以管理员身份运行它

找到我们刚才创建的Demo2

点击线程

我们看到main线程一直在等待,因为我们使用的run方法,所以是在主线程上运行的,如果我们想看到我们自己创建的线程,就要用start

我们在试试;

这个Thread------0就是我们自己创建的线程,但是main呢,还有为啥先打印的主线程呢,因为调度随机的,我们不知道操作系统让拿个线程先执行,所以就会发生这样的状况,这也是我们后期要重点掌握的,要怎么保证线程之间协调配合,避免乌鸦哥掀桌,哈哈哈,main线程在这里已经结束了,没啥好说的了,下一个;

2)实现Runnable接口
java 复制代码
class MyRunnable implements Runnable{
    public void run(){
        while(true){
            System.out.println("MyRunnable 线程");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

public class Demo3 {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start();
        System.out.println("主线程");
    }
}

这是第二种,我们Runnable接口,我们还是要使用Thread类来创建,这不有病吗.........这么麻烦,还不如用第一种,其实这种想法是不对的,大家听,没听过,高内聚低耦合,这里就谈到了低耦合,我们使用接口,在想要修改的时候去修改接口的代码即可,是不影响Thread的,但是我们使用Thread的时候,想要修改的时候,就要修改Thread中的代码, 可能扯到线程相关的代码,而且用类继承一次局限性大,接口更灵活;

运行

我们这次让主线程也活着

java 复制代码
class MyRunnable implements Runnable{
    public void run(){
        while(true){
            System.out.println("MyRunnable 线程");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

public class Demo3 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new MyRunnable());
        thread.start();
        while(true){
            System.out.println("主线程");
            Thread.sleep(1000);
        }
    }
}

来运行

这次更能看到随机调度的现象

3)匿名内部类(Thread)

这几个其实用的都少,最多用到的还是lambda表达式

java 复制代码
public class Demo4 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(){
            public void run(){
                while(true){
                    System.out.println("Thread 线程");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };
        
        
        thread.start();
        while(true){
            System.out.println("主线程");
            Thread.sleep(1000);
        }
    }
}

跟之前都一样,就是使用·匿名内部类了;

直接看运行

4)匿名内部类(Runnable)
java 复制代码
public class Demo5 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread =  new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    System.out.println("Thread 线程");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        });
        thread.start();
        while(true){
            System.out.println("主线程");
            Thread.sleep(1000);
        }
    }
}

一样嗷,匿名内部类创建Ruunable对象,

5)lambda表达式

这个才是我们使用最多的方法,主要是很方便;

lambda表达式:

(参数)->{实现了啥}

java 复制代码
public class Demo6 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
           while (true){
               System.out.println("Thread 线程");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }
        });
        
        thread.start();
        
        while(true){
            System.out.println("主线程");
            Thread.sleep(1000);
        }
    }
}

方便吧,在new Thread的时候直接在括号中使用lambda表达式就行;

大家可能有疑问,不说重写run方法吗,这个{}里面的就是我们已经重写了,这个跟那个第三个匿名内部类的方法其实很像的;

我们来看运行结果;

完美嗷

2,Thread类及常见方法

Thread类是JVM用来管理线程的一个类,我们每创建一个Thread对象就有一个线程与他对应;

2.1 Thread的常见构造方法

|-------------------------------------|--------------------|
| 方法 | 说明 |
| Thread() | 创建线程对象 |
| Thread(Runnable target) | 使用Runnable对象创建线程对象 |
| Thread(String name) | 创建线程对象并命名 |
| Thread(Runnable target,String name) | 使用Runnable |

java 复制代码
 Thread thread1 = new Thread();
        Thread thread2 = new Thread(new Runnable() {public void run() {}});
        Thread thread3 = new Thread(()->{
            while (true){
                System.out.println("线程3");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"线程3");
        
        Thread thread4 = new Thread(new Runnable() {public void run() {}},"线程4");

我们用4种构造方法创建了线程,我们来观察一下线程3,我们是否把线程的名字修改了呢;

成功看到线程3了;

2.2 Thread的常见属性

|--------|-----------------|
| 属性 | 获取方法 |
| ID | getId() |
| 名称 | getName() |
| 状态 | getState() |
| 优先级 | getPriority() |
| 是否后台线程 | isDaemon() |
| 是否存活 | isAlive() |
| 是否被中断 | isInterrputed() |

  1. ID 类似进程的pid,线程的唯一标识,不同线程不会重复;

  2. 名称 各种调试工具用到;

  3. 线程当前所处的情况;

  4. 通常来说优先级高的线程会容易调用;

  5. 可以想象为饭局中的小程序员,对这次饭局不起决定性作用,JVM会在一个进程的所有非后台线程结束后结束;

我们可以使用SetDaemon()来把当前线程设置为后台线程;

  1. run方法是否结束;

  2. 终止线程运行;

我们来写一个代码获取所以线程信息;

java 复制代码
public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "还活着");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println(Thread.currentThread().getName() + "即将死亡");
        },"线程1");


        System.out.println("ID :" + Thread.currentThread().getId() + "     "  + "ID :" + thread.getId());
        System.out.println("name :" + Thread.currentThread().getName() + "     "  + "name :" + thread.getName());
        System.out.println("state :" + Thread.currentThread().getState() + "     "  + "state :" + thread.getState());
        System.out.println("优先级 :" + Thread.currentThread().getPriority() + "     "  + "优先级 :" + thread.getPriority());
        System.out.println("是否存活 :" + Thread.currentThread().isAlive() + "     "  + "是否存活 :" + thread.isAlive());
        System.out.println("中断? :" + Thread.currentThread().isInterrupted() + "     "  + "中断? :" + thread.isInterrupted());

        thread.start();
        while (thread.isAlive()){
            System.out.println("ID :" + Thread.currentThread().getId() + "     "  + "ID :" + thread.getId());
            System.out.println("name :" + Thread.currentThread().getName() + "     "  + "name :" + thread.getName());
            System.out.println("state :" + Thread.currentThread().getState() + "     "  + "state :" + thread.getState());
            System.out.println("优先级 :" + Thread.currentThread().getPriority() + "     "  + "优先级 :" + thread.getPriority());
            System.out.println("是否存活 :" + Thread.currentThread().isAlive() + "     "  + "是否存活 :" + thread.isAlive());
            System.out.println("中断? :" + Thread.currentThread().isInterrupted() + "     "  + "中断? :" + thread.isInterrupted());

            System.out.println(Thread.currentThread().getState() + " " + thread.getState());
            Thread.sleep(1000);
        }
        System.out.println("ID :" + Thread.currentThread().getId() + "     "  + "ID :" + thread.getId());
        System.out.println("name :" + Thread.currentThread().getName() + "     "  + "name :" + thread.getName());
        System.out.println("state :" + Thread.currentThread().getState() + "     "  + "state :" + thread.getState());
        System.out.println("优先级 :" + Thread.currentThread().getPriority() + "     "  + "优先级 :" + thread.getPriority());
        System.out.println("是否存活 :" + Thread.currentThread().isAlive() + "     "  + "是否存活 :" + thread.isAlive());
        System.out.println("中断? :" + Thread.currentThread().isInterrupted() + "     "  + "中断? :" + thread.isInterrupted());

    }
}

运行之后就能看到整个过程了;

2.3 启动一个线程

我们之前用过run方法来启动线程,实际上着并不是真正创建了线程,我们使用start真正在操作系统底层创建了一个线程,只有创建了线程对象再start才是让线程真正独立执行了;

2.4 中断一个线程

线程一旦工作就会等到任务结束才会停下来,但是有时候我们有让线程立即停下的需求,我们有两种办法来中断一个线程,其实叫终止更好,因为不是间断,而是线程就结束了;

我们来模拟一个场景,有两个员工张三,李四,老板让他们去给别人转账,张三正在转给骗子,李四及时阻止;

1,共享标记来中断线程

java 复制代码
public class Demo3 {
    public static boolean a = true;
    public static void main(String[] args) {
        Thread thread1 = new Thread(()->{
            while (a){
                System.out.println(Thread.currentThread().getName() + "正忙着转账呢");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println(Thread.currentThread().getName() + "我嘞个豆,差点转走了");
        },"张三");

        Thread thread2 = new Thread(()->{
            try {
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() + "老板来电话了!" + "张三在给骗子转账!");
                a  = false;
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        },"李四");

        thread1.start();
        thread2.start();
    }
}

来看运行结果

哈哈哈哈哈,好玩吧;

2,调用interrupt()方法来通知

|--------------------------------------|------------------------------------|
| 方法 | 说明 |
| Thread对象.interrupt() | 中断对象关联的线程,如果线程正在阻塞,以异常方式通知,否则设置标志位 |
| public static boolean interrputed(); | 判读当前线程的标志位是否设置,调用后清除标志位; |
| public boolean | 判读当前线程的标志位是否设置,调用后不清除标志位; |

在Java线程的上下文中,中断标志位是Thread对象维护内部的布尔值用于表示该线程是否被请求中断。

java 复制代码
public class Demo3 {
    public static void main(String[] args) {
        Thread thread1 = new Thread(()->{
            //或者用Thread.interrupted();
            while (!Thread.currentThread().isInterrupted()){
                System.out.println(Thread.currentThread().getName() + "正忙着转账呢");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println(Thread.currentThread().getName() + "我嘞个豆,差点转走了");
        },"张三");

        Thread thread2 = new Thread(()->{
            try {
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() + "老板来电话了!" + "张三在给骗子转账!");
                thread1.interrupt();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        },"李四");

        thread1.start();
        thread2.start();
    }
}

这里的原理就一样了,但是代码运行会报一个异常,

这个是因为

java 复制代码
thread1.interrupt();

唤醒了sleep让他直接抛出InterruptedException,被捕获到,抛出RuntimeException异常,所以我们在这里直接break就行;

这样结果就对了;

2.5 等待一个线程

|------------------------------------------|---------------------|
| 方法 | 说明 |
| public void join() | 等待线程结束 |
| public void join(long millis) | 等待线程结束,最多等待millis毫秒 |
| public void join(long millis, int nanos) | 等待线程结束,精度更高;后面是纳秒; |

java 复制代码
public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(()->{
            while (true){
                System.out.println(Thread.currentThread().getName() + "线程正在工作");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"张三");

        Thread thread2 = new Thread(()->{
            while (true){
                System.out.println(Thread.currentThread().getName() + "线程正在工作");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"李四");

        thread1.start();
        thread1.join();

        thread2.start();
        thread2.join();

        System.out.println("全部线程打印结束");
    }
}

我们创建了两个线程和主线程,thread1.join意思为让主线程等待thread1线程执行完再执行 ,此时thread2还没开启,我们来看运行结果

张三始终在工作我天,因为我们使用的join没有放参数,是无休止的等待;如果我们放参数就不会这样傻傻的等待了,

java 复制代码
public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(()->{
            while (!Thread.currentThread().isInterrupted()){
                System.out.println(Thread.currentThread().getName() + "线程正在工作");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    break;
                }
            }
        },"张三");

        Thread thread2 = new Thread(()->{
            while (!Thread.interrupted()){
                System.out.println(Thread.currentThread().getName() + "线程正在工作");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    break;
                }
            }
        },"李四");

        thread1.start();
        thread1.join(2000);
        thread1.interrupt();

        thread2.start();
        thread2.join(2000);
        thread2.interrupt();

        System.out.println("全部线程打印结束");
    }
}
");

修改一下代码。。。。。

我们这回看到第一个join先让thread1插队,thread1运行2毫秒后,主线程启动,设置中断标志位,thread1停止,thread2插队,等待两毫秒后嗝屁,主线程也结束了;

2.6 获取当前线程的引用

这个之前我们就使用过了;

|--------------------------------------|------------------|
| 方法 | 说明 |
| public static Thread currentThread() | 返回当前对象的引用,类似this |

没啥好说的嗷,来段代码就好了;

java 复制代码
public class Demo2 {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            System.out.println(Thread.currentThread().getName());
        },"线程1");

        thread.start();
        System.out.println(Thread.currentThread().getName());

    }
}

运行结果

2.7 休眠当前线程

这个也没啥好说的,我们一直在使用

|-----------------------------------------------------------------------------|----------------|
| 方法 | 说明 |
| public static void sleep (long millis) throws InterputedException | 休眠当前线程millis毫秒 |
| public static void sleep (long millis,int nanos) throws InterputedException | 更高精度 |

不演示了嗷,马上下一期

相关推荐
慢慢成长的码农2 分钟前
Kotlin 常量定义
开发语言·数据库·kotlin
THE WHY3 分钟前
IDEA Maven构建时报错:无效的目标发行版17
java·后端·jdk·maven·intellij-idea
白露与泡影16 分钟前
Spring Boot性能提升的核武器,速度提升500%!
java·spring boot·后端
昔我往昔19 分钟前
Spring Boot中的依赖注入是如何工作
java·spring boot·后端
Eiceblue21 分钟前
Python在Excel工作表中创建数据透视表
开发语言·python·visualstudio·excel
2401_8582861140 分钟前
124.【C语言】数据结构之快速排序的小区间优化和非递归的解决方法
c语言·开发语言·数据结构·算法·排序算法·
编程小筑44 分钟前
TypeScript语言的网络编程
开发语言·后端·golang
无名3871 小时前
FreeSWITCH rtp session 的初始化
java·服务器·前端
老大白菜1 小时前
第6章:Go语言并发编程
开发语言·后端·golang
Java.慈祥1 小时前
[程序设计]—代理模式
java·设计模式·代理模式·cglib