【JAVA EE初阶】多线程(1)

发布于:2025-04-22 ⋅ 阅读:(11) ⋅ 点赞:(0)

这样的代码,虽然也能打印hello thread,但是没有创建新的线程,而是直接在main方法所在的主线程中执行了run的逻辑

start方法,是调用系统api,真正在操作系统内部创建一个线程。这个新的线程会以run作为入口方法(执行run)的逻辑,run方法,不需要在代码中显式调用

此处遇见受查异常为什么不能用throws呢?

因为,如果给run方法声明加上throws,此时就意味着和父类Thread的run的方法声明不一致,就无法构成方法重写了。

package Thread;

class MyThread extends Thread {
    @Override
    public void run() {
        while(true){
        System.out.println("hello thread");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
}
public class Demo1 {
public static void main(String[] args) {
    System.out.println("你好!");
    
    // 创建并启动MyThread线程
    MyThread myThread = new MyThread();
    myThread.start();
   // myThread.run();

   while(true){
    System.out.println("hello main");
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
   }
}
}

输出:

但是如果换成这样的代码:

package Thread;

class MyThread extends Thread {
    @Override
    public void run() {
        while(true){
        System.out.println("hello thread");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
}
public class Demo1 {
public static void main(String[] args) {
    System.out.println("你好!");
    
    // 创建并启动MyThread线程
    MyThread myThread = new MyThread();
   // myThread.start();
    myThread.run();

   while(true){
    System.out.println("hello main");
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
   }
}
}

输出:

由此可见run方法并没有创建一个新的进程,从始至终只有一个主线程在运行,只有将run的方法执行完才能去打印hello main 但是由于这里的 run方法是一个死循环,所以一直没有打印hello main

通过观察我们可以发现这里的main和thread的打印,并不是严格交替的。由于两个打印,都加了sleep(1000)。当1000ms时间到的时候,这两个线程哪一个限制性,这个顺序是不确定的。

(操作系统调度线程的顺序是无需,不可预测的,随机的 )

可以通过Java jdk中的jconsole工具来观察进程

我的jdk-jconsole工具目录:C:\Program Files\Java\jdk-17\bin

折线图

这张图列出了当前进程中所有的线程。剩下的线程,都是JVM自带的,这些线程进行了一些背后的操作,比如负责垃圾回收,记录统计信息,记录一些调试信息。

如果将Thread.start()改成Thread.run()——>

我们会发现刚刚的Thread0没有了

一、Java中创建线程的方法

(1)创建子类,继承Thread,重写run,调用start

package Thread;

class MyThread extends Thread {
    @Override
    public void run() {
        while(true){
        System.out.println("hello thread");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
}
public class Demo1 {
public static void main(String[] args) {
    System.out.println("你好!");
    
    // 创建并启动MyThread线程
    MyThread myThread = new MyThread();
   // myThread.start();
    myThread.run();

   while(true){
    System.out.println("hello main");
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
   }
}
}

(2)创建子类,实现Runnable,重写run,搭配Thread对象进行start

package Thread;

class MyRunnable implements Runnable {
    @Override
    public void run() {
        while(true){
            System.out.println("hello thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

public class Demo2 {

    public static void main(String[] args) {
        Runnable runnable = new MyRunnable();
        //runnable没有start方法,所以不能直接调用run方法,需要将runnable对象作为参数传递给Thread类的构造方法,然后调用start方法启动线程 
        Thread thread = new Thread(runnable);
        thread.start();
    }
}

使用Runnable描述线程要执行的任务是啥,真正去创建线程的工作,还是由Thread来负责。

interface是接口,不包含具体的方法的实现,只是提供一系列抽象方法,让子类去重写

*接口的的default一般不用,日常开发的时候,用到接口,都是希望全都提供抽象方法。除非是接口提供了一组方法,这一组方法中存在一些“公共的逻辑”,就可以在接口中搞default方法,使得这个方法表示公共逻辑,后面就可以在重写其他抽象方法的时候去调用了。

对于第一种写法(继承Thread)描述任务的时候,任务代码是写到Thread子类中的,意味着任务内容和Thread类的耦合程度更高(*写代码要高内聚/低耦合)

未来如果想把这个任务给别的“主体”去执行(执行一个任务,线程只是其中一个选项,进程也可以,协程亦可以...)

第二种写法,任务是写到Runnable中的,几乎不涉及到任何和“线程”相关的概念,任务内容和Thread概念的耦合是很小的,几乎没有

任务内容和Thread概念的耦合是很小的,几乎没有

后序就可以把这个任务交给进程、协程来执行

(*协程:近几年提出的概念,可以理解为“轻量级线程”)

(3)继承Thread,重写run,调用start 通过匿名内部类

package Thread;

public class Demo3 {

    public static void main(String[] args) {
        
        Thread t = new Thread() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        
        };
        t.start();
    }

}

此处就是创建了没有名字的匿名内部类,这个类就是Thread的子类,子类重写了run方法,同时也创建了子类的实例,通过t引用指向。

(4)实现Runnable,重写run,搭配Thread调用start 通过匿名内部类

package Thread;

public class Demo4 {

    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        };
        Thread t = new Thread(runnable);
        t.start();
        while(true){
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

}

(5)更简单的写法,基于lambda表达式创建线程

lambda表达式,本质上是“匿名方法”

package Thread;

public class Demo5 {

    public static void main(String[] args) {
        Thread t = new Thread(()->{
            while(true){
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        });
        t.start();
        while(true){
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

}

二、后台线程与前台线程

后台线程:线程没运行完,进程可以结束(线程不能够组织进程结束)

前台线程:线程没有运行完,进程就不会结束(线程能够组织进程结束)

main线程和我们自己创建的线程都是前台线程,isDaemon = false;

剩下的线程就是后台线程,isDaemon = true;(守护线程)

通过setDaemon设置线程为后台(*必须在start之前set)

什么样子的线程是前台线程,什么样子的线程是后台线程呢?

如果一个线程做的任务很重要,这个任务必须要昨晚你,就应该设置这个线程为前台线程

如果一个线程做的任务无关紧要/周期性无期限执行(比如说JVM的垃圾回收线程),就应该设置为后台线程

package Thread;

public class Demo5 {

    public static void main(String[] args) {
        Thread t = new Thread(()->{
            while(true){
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        });
        t.setDaemon(true);
        t.start();//把t线程设置为守护线程,当main线程结束时,t线程也会结束
        for(int i = 0;i<3;i++){
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

}

三、判断线程是否存活

package Thread;

public class Demo5 {

    public static void main(String[] args) {
        Thread t = new Thread(()->{
            while(true){
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        });
        System.out.println("t线程是否存活:"+t.isAlive());//false,t线程还没有启动,所以isAlive返回false
        t.setDaemon(true);
        t.start();//把t线程设置为守护线程,当main线程结束时,t线程也会结束
        System.out.println("t线程是否存活:"+t.isAlive());//true,t线程已经启动,所以isAlive返回true
        for(int i = 0;i<3;i++){
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        System.out.println("t线程是否存活:"+t.isAlive());//false,t线程已经结束,所以isAlive返回false
    }

}


网站公告

今日签到

点亮在社区的每一天
去签到