多线程基础

多线程基础小结

1.概念

1.1什么是线程

每个线程都是一个执行流,每个执行流都单独处理自己的工作,多个执行流是并发执行的。

1.2进程和线程的区别

进程包含线程,一个进程中可以有多个线程,但至少有一个线程,即要有一个主线程。
每一个进程都相当于一个单独的房间,进程之间不共享内存空间。同一个进程内的线程共享同一个内存空间。(进程和线程就像房间和房客的关系,一个房间可以有多个房客,房间与房间直接互不影响,同一个房间内的房客共享房间的面积)

1.3多进程和多线程

(1)多进程

例如一个公司任务数量为100,它可以将任务分配给2个只有一套流水线的工厂去完成,每个工厂完成50的任务。
如下图这里的一个工厂就是一个进程,两个进程同时执行互不影响提高了工作效率,但是这种方式开支比较大,因为多建造了一个工厂。
在这里插入图片描述

(2)多线程

还是例如一个公司任务数量为100,但是公司将任务交给一个有2个流水线的工厂去完成,每条流水线完成50任务数量。
如下图,工厂就相当于一个进程,每个流水线都相当于一个线程,这样也提高了工作效率,但是开支就比较小。
在这里插入图片描述

2.使用java操作多线程

2.1创建线程

2.1.1继承Thread类

(1)创建一个线程类MyThread继承Thread

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

(2)在main方法中实例化MyThread并且调用start()方法启动线程

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

以上代码中MyThread线程类中run()方法只是表述了线程要做的工作,此时线程仅仅被创建了出来,直到main()方法中t调用start()方法线程才被创建出来,直到run()方法执行完毕线程才会销毁,上述代码为死循环所以线程会一直进行。

运行结果如下:可以看到控制台在交替打印线程类MyThread和main方法中的信息。
在这里插入图片描述

2.1.2实现Runnable接口

(1)创建一个类MyRunnable实现Runnable接口

class MyRunnable implements Runnable{

    @Override
    public void run() {
        System.out.println("hello thread");
    }
}

(2)在main方法中实例化MyRunnable类,创建 Thread 类实例, 调用 Thread 的构造方法时将 Runnable 对象作为参数,然后调用start()方法开始线程。

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

    }
}

在这里插入图片描述
这里run()方法只打印了一次信息,打印结束后线程销毁,程序终止。

2.1.3匿名内部类创建 Thread 子类对象
public class ThreadDemo3 {
    public static void main(String[] args) {
        Thread t = new Thread(){
            @Override
            public void run() {
                System.out.println("hello");
            }
        };
        t.start();
    }
}
2.1.4匿名内部类创建 Runnable 子类对象
public class ThreadDemo4 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello thread");
            }
        });
        t.start();
    }
}
2.1.5lambda 表达式创建 Runnable 子类对象
public class ThreadDemo5 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            System.out.println("hello thread");
        });
        t.start();
    }
}

以上方法中运行结果均相同,其中lambda表达式方法是最常用的线程创建方法。

2.2启动线程start()

在上述五种线程创建的方法中,我们已经看到如何覆写run()方法来创建一个线程,但是线程被创建出来并不意味着线程开始运行了,只有调用start()方法时线程才开始运行。
实际上覆写run()方法后只是将线程的任务给创建了出来,只有在调用start方法时操作系统底层才真正将线程创建出来。

如下图:run()方法就像一个加了密码的保险柜,而start()方法就是密码,只有知道了密码才能拿出里面的东西。
在这里插入图片描述

2.3线程的中断

(1)使用自定义的变量来作为标志.,来控制线程中断

public class ThreadDemo7 {
    private static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            while (flag){
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();
        Thread.sleep(3000);
        flag = false;
    }
}

通过修改flag的值改变while循环的判断条件达到中断线程的目的

(2)使用 thread 对象的 interrupted() 方法通知线程结束.

public class ThreadDemo8 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            while (!Thread.currentThread().isInterrupted()){
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
            }
        });
        t.start();
        Thread.sleep(3000);
        t.interrupt();
    }
}

interrupted()是Thread内置的一个标志位,可以更方便的实现线程的中断操作。

interrupt方法的作用
1设置标志位为true。
2如果线程处于阻塞状态(比如正在执行sleep)此时会把阻塞状态唤醒通过抛异常的方式让sleep立即结束。

2.4等待一个线程

线程之间是并发执行的,各个线程抢占式执行,所以操作系统对线程的调度是随机的,我们无法判断那个线程先开始哪个线程先结束。
有时候我们需要等待一个线程完成后才能继续接下来的工作,这时候我们就需要一个方法来明确等待一个线程结束。

public class ThreadDemo9 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            try {
                for (int i = 0; i < 5; i++) {
                    System.out.println("hello thread");
                }
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t.start();
        System.out.println("join之前");
        try {
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("join 之后");
    }
}

在这里插入图片描述

由代码和运行结果可以明确看出,程序运行后main方法和Thread分头行动,在遇到join之后main方法被阻塞直到t线程完全结束main方法才恢复,这里main方法便是等待了t线程的结束。

3.线程的状态

3.1线程的所有状态

public class ThreadDemo10 {
    public static void main(String[] args) {
        for (Thread.State state : Thread.State.values()) {
            System.out.println(state);
       }
   }
}

在这里插入图片描述

NEW: 安排了工作, 还未开始行动
RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作.
BLOCKED: 这几个都表示排队等着其他事情
WAITING: 这几个都表示排队等着其他事情
TIMED_WAITING: 这几个都表示排队等着其他事情
TERMINATED: 工作完成了.

3.2观察线程的状态和转移

public class ThreadDemo11 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            for (int i = 0; i < 100; i++) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //这个循环体啥都不干,也不sleep
            }
        });
        //启动之前获取T的状态也就是NEW状态
        System.out.println("start之前的状态" + t.getState());
        t.start();
        for (int i = 0; i < 100; i++) {
            System.out.println("t执行中的状态" + t.getState());
        }
        t.join();
        //线程执行完毕之后就是TERMINATED状态
        System.out.println("t结束之后" + t.getState());
    }
}

在这里插入图片描述
上述代码和运行结果可以清晰的看出线程执行过程中的状态转移。

4单线程与多线程速度对比

(1)单线程方法

 public static void serial(){
        //currenttimemillis获取当前ms级时间戳
        long beg = System.currentTimeMillis();
        long a =0;
        for (long i = 0; i < 100_0000_0000L; i++) {
            a++;
        }
        long b = 0;
        for (long i = 0; i < 100_0000_0000L; i++) {
            b++;
        }
        long end = System.currentTimeMillis();
        System.out.println("单线程执行时间:" + (end - beg) + "ms");
    }

currentTimeMillis为获取当前ms级的时间戳,分别在线程开始和结束时获取一次,再相减得到线程的执行时间。

(2)多线程方法

 public static void concurrency() {
     //使用两个线程分别完成自增
     Thread t1 = new Thread(()->{
         long a = 0;
         for (long i = 0; i <100_0000_0000L ; i++) {
             a++;
         }
     });
     Thread t2 = new Thread(()->{
         long b = 0;
         for (long i = 0; i <100_0000_0000L ; i++) {
             b++;
         }
     });
     long beg = System.currentTimeMillis();
     t1.start();
     t2.start();
     try {
         t1.join();
         t2.join();
     } catch (InterruptedException e) {
         e.printStackTrace();
     }

     long end = System.currentTimeMillis();
     System.out.println("多线程执行时间:" + (end - beg) + "ms");
 }

这里同样获取线程开始和结束时的时间戳,来得到执行时间,但是这里要注意必须执行 t1.start()和 t2.start()等待t1线程和t2线程都完全结束后再获取结束时间戳,否则程序会在线程未完全结束时的到end时间戳,影响最终结果。

(3)在main方法中先后调用两个方法进行对比

public static void main(String[] args) throws InterruptedException {
      
      serial();
      concurrency();
  }

在这里插入图片描述
由上图可看出多线程的执行时间要远高于单线程,并且在执行任务越多时,结果越明显。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值