多线程一

314 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

进程与线程之间的区别和联系

1.进程是包含线程的,一个进程里面可以有多个线程
2.进程和线程都能解决并发编程问题场景,但是进程在频繁创建和销毁中,开销更大(线程比进程更轻量)
3.进程是系统分配资源的基本单位,线程是系统调度执行的基本单位
4.进程之间是相互独立的,各自有各自的虚拟地址空间,同一个进程内部的多个线程之间,
  共用一个内存空间及文件资源。一个进程挂了,其他的进程一般没事,但是一个线程挂了,
  很有可能把整个进程带走。

创建线程

class MyThread extends Thread{
    @Override
    public void run() {                      //并不是写个这run方法,线程就创建的。
        System.out.println("hello Thread");  //run方法的逻辑,是在新创建出来的线程中,被执行的代码。
    }
}
public class Demo1 {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();    //我们调用这个方法的时候才是真正创建线程
    }
}

上面就是创建了一个线程输出: hello Thread 但是上面的代码并没有体现出线程争抢资源的性质。

线程的抢占式执行

class MyThread1 extends Thread{
    @Override
    public void run() {
        while(true){
            System.out.println("hello Thread");
            try {
                Thread.sleep(1000);    //强制让线程休眠一段时间,单位是ms
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
public class Demo2 {
    public static void main(String[] args) {
        MyThread1 t = new MyThread1();
        t.start();
        while(true){
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

屏幕截图 2022-09-17 200201.jpg

这个是代码执行之后的样子,我们可以看到MyThread1 和 main 交错打印两者是并发执行的。可是我们要明白,这边的并发执行并不只有并发,是并发加并行的只是我们看不出来而已。

我们上面让他们休眠了1S,那1S之后系统唤醒的是谁呢?

我们可以看到有一个地方是连续两个的hello Thread,那就说明唤醒谁是不知道的是随机的。

这个也被称为抢占式执行。

同时要注意一下,sleep并不是休眠1000ms之后就唤醒,是1000ms之内,是访问不了的。

Thread类创建线程

第一种就是上面的一样,第二种就是实现Runnable接口
class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("hello Runnable");
    }
}
public class Demo3 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable());
        t.start();
    }
}
第三种,利用匿名内部类
public class Demo4 {
    public static void main(String[] args) {
        Thread t = new Thread(){
            @Override
            public void run() {
                System.out.println("hello!");
            }
        };
        t.start();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello!");
            }
        });
        t.start();
    }
    
}

那么Thread 和 Runnable 这两个哪个更好呢?

Runnable更好一点,因为这样可以让线程和线程执行的任务进行更好的解耦。

Runnable只是描述了一个任务,至于是什么来执行这个任务都可以。

第四种方法,使用lambda
public class Demo5 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            System.out.println("hello");
        });
        t.start();
    }
}

多线程带来的优势

让我们通过两段代码来看效果。
public class Demo6 {
    public static void add(){
        long begin = System.currentTimeMillis();
        long a = 0;
        for(long i = 0;i<10_0000_0000;i++){
            a++;
        }
        long b = 0;
        for(long i = 0;i<10_0000_0000;i++){
            b++;
        }
        long end = System.currentTimeMillis();
        System.out.println("消耗时间: " + (end - begin) + "ms");
    }
    public static void main(String[] args) {
        add();
    }
}

执行结果是 消耗时间: 564ms

那我们来看第二个

public class Demo7 {
    public static void addThread(){
        long begin = System.currentTimeMillis();
        Thread t1 = new Thread(()->{
            long a = 0;
            for(long i = 0;i<10_0000_0000;i++){
                a++;
            }
        });
        t1.start();
        Thread t2 = new Thread(()->{
            long b = 0;
            for(long i = 0;i<10_0000_0000;i++){
                b++;
            }
        });
        t2.start();
        try {
            t2.join();                         //我们不能直接去运算时间,而是要先join一下。因为main和这个线程是并发的。
            t1.join();                           //我们要让main等我们的t1和t2执行完之后才能执行。
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        long end = System.currentTimeMillis();
        System.out.println("消耗时间: " + (end - begin) + "ms");
    }
    public static void main(String[] args) {
        addThread();
    }
}

运行结果: 消耗时间: 332ms

我们比较上面两个程序消耗的时间就可以看出多线程解决问题的速度加快了。毕竟人多力量大。

同时我们可以看出第二个程序的时间并不是第一个程序的时间的一半,从中可以看出线程是并行加并发

当数据比较小的时候,我们有可能看到多线程的时间比单线程的时间还要多,这个是为什么呢?

这是因为线程的创建是要消耗时间的。

当然线程也不是越多越好的。

Thread类的常见构造器

屏幕截图 2022-09-17 205215.jpg

我们可以看到Thread类的构造方法有很多,但是我们就讲一些常见的。

第一个是空构造器

第二个是传入一个String,这个就是线程的名字。这个是有助于程序员的调试。

第三个是传入一个Runnable接口。

第五个就是传入一个线程组,还有名字。

Thread类的属性

属性获取的方法
IDgetId()
名称getName()
状态getState()
优先级getPriority()
是否后台进程getDaemon()
是否存活isAlive()
是否被中断isInterrupted()

这边对于第五个属性——是否后台进程解释一下

创建t1,t2线程的时候默认都是前台的,这样的话,只有等到t1,t2都执行完之后main才会结束。

如果两个线程都是后台进程的话,那么main函数一执行完,main就结束,两个线程也会被迫结束

Thread的重要方法

1. start() 决定线程是否被创建

2. run() 重写了父类的一个普通方法,调用start的时候会调用这个。

3. Thread.interrupted() 静态方法 Thread.currentThread.interrupted() 实例方法

以上的两种方法和 t1.interrupt(); 联合使用。

中断线程,第一种是不用方法有缺陷,不严谨
第二种用Thread方法。会报错

//main函数是不能被中断的。
public class Demo8 {
    public static boolean Quit = false;
    public static void main(String[] args) {
        Thread t = new Thread(()->{
           while(!Quit){
               System.out.println("hello");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }
        });
        t.start();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        Quit = true;
        System.out.println("end");
    }
}
public class Demo9 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            while(!Thread.currentThread().isInterrupted()){
                System.out.println("hello");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        t.interrupt();
        System.out.println("end");
    }
}

4. join() 线程等待

这个我们在比较单线程和多线程速度的时候就用到了。

就是我们要知道的是。t1.join()的时候,并不是t1在调用,而是main线程在调用,所以main阻塞了,直到t1执行完

join里面也可以传参数,就是让调佣他的线程等待相应的时间。join(1000) main函数调用的时候就是等待1S之后就不等了。

5. Thread.currentThread() 获得当前线程的实例。

public class Demo10 {
    public static void main(String[] args) {
        Thread t1 = new Thread(){
            @Override
            public void run() {
                System.out.println(this.getName());
            }
        };
        t1.start();
        Thread t2 = new Thread(()->{
            System.out.println(Thread.currentThread().getName());
        });
        t2.start();
    }
}

我们可以看出,t1可以用this来代替Thread.currentThread 但是第二个却不可以,这是因为t2是Runnable

6. sleep() 线程休眠

之前也有用到,