【JAVA】JAVA多线程基础1

目录

一、什么是线程

二、进程与线程区别

三、Java的线程和操作系统线程的关系

四、一个简单的多线程程序

五、通过jconsole观察进程里面的多线程情况

五、Thread.sleep方法

六、创建线程其他方法

七、Thread类及其常见方法

1、构造方法

2、Thread常见属性


一、什么是线程

一个线程就是一个 "执行流" 。每个线程之间都可以按照顺序执行自己的代码。多个线程之间 "同时" 执行着多份代码。

我们举个例子看看:

一家公司要去银行办理业务,既要进行财务转账,又要进行福利发放,还得进行缴社保。

如果只有张三一个会计就会忙不过来,耗费的时间特别长。为了让业务更快的办理好,张三又找来两位同事李四、王五一起来帮助他,三个人分别负责一个事情,分别申请一个号码进行排队,自此就有了三个执行流共同完成任务,但本质上他们都是为了办理一家公司的业务。

此时,我们就把这种情况称为多线程,将一个大任务分解成不同小任务,交给不同执行流就分别排队执行。其中李四、王五都是张三叫来的,所以张三一般被称为主线程(Main Thread)。


那为什么要有线程呢?主要有以下两点:

  • 首先, "并发编程" 成为 "刚需"

单核CPU的发展遇到了瓶颈。要想提高算力,就需要多核CPU。而并发编程能更充分利用多核CPU资源。有些任务场景需要"等待IO",为了让等待IO的时间能够去做一些其他的工作,也需要用到并发编程。

  • 其次, 虽然多进程也能实现并发编程, 但是线程比进程更轻量

创建线程比创建进程更快;销毁线程比销毁进程更快;调度线程比调度进程更快。

二、进程与线程区别

  • 进程是包含线程的,每个进程至少有一个线程存在,即主线程
  • 进程和进程之间不共享内存空间。同一个进程的线程之间共享同一个内存空间

比如之前的多进程例子中,每个客户来银行办理各自的业务,但他们之间的票据肯定是不想让别人知道的,否则钱不就被其他人取走了么。而上面我们的公司业务中,张三、李四、王五虽然是不同的执行流,但因为办理的都是一家公司的业务,所以票据是共享着的。这个就是多线程和多进程的最大区别。

  • 进程是系统分配资源的最小单位,线程是系统调度的最小单位

三、Java的线程和操作系统线程的关系

线程是操作系统中的概念。操作系统内核实现了线程这样的机制,并且对用户层提供了一些API供用户使用(例如Linux的pthread库)。

Java 标准库中Thread类可以视为是对操作系统提供的API进行了进一步的抽象和封装。

四、一个简单的多线程程序

我们来看一个简单的多线程代码:

class Mythread extends Thread{
    @Override
    public void run(){
        while(true){
            System.out.println("hello thread");
        }
    }
}

public class Demo1 {
    public static void main(String[] args){
        Thread t = new Mythread();
        t.start();
        while(true){
            System.out.println("hello main");
        }
    }
}

我们运行它,可以看到,这两个while循环在“同时执行”。我们所看到的结果是两边的日志都在交替打印。


上述代码中,start和run都是Thread的成员,那为什么要用start呢?

run只是描述了线程的入口(线程要做什么任务),而start才是真正调用了系统API,在系统中创建出线程,让线程再调用run。

我们使用run的话就只会打印“hello thread”这一句话。这里是run里面的while循环,并且不会循环结束。也就无法执行到“hello main”这个循环了。如下图所示:

把t.start改成t.run,代码中不会创建出新的线程,只有一个主线程,这个主线程里面只能一次执行循环,执行完一个再执行另一个。

五、通过jconsole观察进程里面的多线程情况

jconsole是JDK中带有的工具,怎么查找呢?

我们可以点击左上角File然后点击Project Structure选项,然后在跳出的页面里点击SDKs即可查看。如下图所示:

打开之后,我们运行JConsole工具:

 

在jconsole可以看到一个java进程,即使是最简单的,也包含了很多的线程。其中大部分是JVM自动创建的。

一个java进程启动之后,JVM会在后面默默做很多的事情,比如垃圾回收、资源统计、远程方法调用等。

未来写一些多线程程序时可以通过这个功能来看线程实时运行情况,比如你的线程卡死了。

五、Thread.sleep方法

while循环太快,我们可以使用sleep方法来减速。

Thread.sleep()是Thread类的一个静态方法,使当前线程休眠,进入阻塞状态(暂停执行),如果线程在睡眠状态被中断,将会抛出IterruptedException中断异常。抛异常的同时,该线程的中断状态会被清除。

我们运行下如下代码:

class Mythread extends Thread{
    @Override
    public void run(){
        System.out.println("thread begin");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("thread end");
    }
}

public class Demo1 {
    public static void main(String[] args){
        Thread t = new Mythread();
        t.start();
        System.out.println("main begin");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("main end");
    }
}

注意:sleep方法会抛出异常,此处必须try catch,不能throws。因为在这个代码中是重写了父类的run,而父类的run没有throws,所以子类方法不能有throws。

六、创建线程其他方法

我们知道了创建线程可以重写Thread的run方法,我们也可以实现Runnable类来重写run,即interface。

class MyRunnable implements Runnable{
    @Override
    public void run(){
        while(true){
            System.out.println("hello thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

public class Demo1 {
    public static void main(String[] args){
        Runnable runnable = new MyRunnable();
        Thread t = new Thread(runnable);
        t.start();
    }
}

Runnable表示的是一个“可以运行的任务”,这个任务交给谁来运行Runnable本身并不关心 。使用Runnable的写法,和直接继承Thread之间的区别,主要就是三个字,解耦合。就是把任务本身给提取出来,此时就可以随时把代码改成使用其他方式来执行这个任务。


当然,我们可以使用匿名内部类:

{}相当于Thread的子类,可以重写父类的方法,比如下面代码:

new Thread() {
             //重写方法
            @Override
            public void run() {
                // run 方法里面写线程的任务
                for (int i = 0; i < 6; i++) {
                    System.out.println("我是大帅逼");
                }
            }
        }.start();

我们还可以使用lambda表达式创建Runnable子类对象:

// 使用 lambda 表达式创建 Runnable 子类对象
Thread t3 = new Thread(() -> System.out.println("使用匿名类创建 Thread 子类对象"));
Thread t4 = new Thread(() -> {
 System.out.println("使用匿名类创建 Thread 子类对象");
 });

七、Thread类及其常见方法

1、构造方法

方法说明
Thread()创建线程对象
Thread(Runnable target)使用 Runnable 对象创建线程对象
Thread(String name)创建线程对象,并命名
Thread(Runnable target, String name)使用 Runnable 对象创建线程对象,并命名
Thread(ThreadGroup group, Runnable target)线程可以被用来分组管理,分好的组即为线程组

 其中,线程名字可以被Jconsole工具查看。给线程起名字,后续在调试的时候,比较方便区分。

Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");

2、Thread常见属性

属性获取方法
IDgetId()
名称getName()
状态getState()
优先级getPriority()
是否后台线程isDaemon()
是否存活isAlive()
是否被中断isInterrupted()
  1. ID是线程的唯一标识,不同线程不会重复,这个id是java给你这个线程分配的,不是系统API提供的线程id,更不是pcb中的id
  2. 名称是各种调试工具用到
  3. 状态表示线程当前所处的一个情况
  4. 优先级高的线程理论上来说更容易被调度到
  5. 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。相比之下,后台线程不结束不影响整个进程的结束,而前台线程没有执行结束,整个进程一定不会结束。
  6. 是否存活,即简单的理解,为run方法是否运行结束了

  • 23
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值