多线程及多线程程序的编写

36 篇文章 0 订阅
29 篇文章 0 订阅

多线程

什么是线程

线程就像进程的“分身”,进程在同一时间只能干一件事,如果需要同时干多件事,就需要线程,一个线程就是一个“执行流”,多个线程之间可以“同时”执行。

引入线程的原因

随着发展,单核CPU的发展接近极致,为了继续提高算力,就引入了多核CPU,而所谓的并发编程就是为了最大程度上充分地利用多核资源;虽然多进程也可以实现并发编程,但是如果频繁的创建进程再销毁进程,就会较为低效,而多线程就可以更好地解决这个问题,因此线程也被称为“轻量级编程”。

为什么线程较进程要更轻量呢?其实是由于进程和线程创建过程的区别:
进程的创建过程简单可以理解为:
①创建PCB;
②分配系统资源,比较消耗时间;
③把PCB加入到内核的双向链表中;
线程的创建过程:
①创建PCB;
②把PCB加入到内核的双向链表中;
线程的创建过程是对进程的一个改进,没有了较为消耗时间的分配资源操作,也就更加轻量。

多线程的一些问题

多线程的引入是为了提高效率,但是如果多核资源紧张,有可能就会适得其反,因此多核资源充分是多线程提高效率的主要前提;
多线程编程的难点是线程安全问题,当多个线程访问同一个变量,就可能引发线程安全问题;
当一个进程中的一个线程出现了问题,比如抛出了异常,必须要及时进行处理,否则就可能会导致整个进程的崩溃;

进程和线程的区别(!)

  1. 进程包含了线程,一个进程可以有多个线程,最少有一个线程,即主线程;
  2. 进程之间不会共享同一块内存(隔离性),一个进程的不同线程之间共享同一块内存;
  3. 进程是系统分配资源的最小单位,线程是系统调度的最小单位;
  4. 当多个进程同时执行,一个进程出现故障,一般不会影响其它进程(进程的独立性),一个进程中的多个线程同时执行,一个线程出现故障,会影响到其他线程;

创建线程

一个多线程程序

class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("hello thread");
    }
}
public class Test1 {
    public static void main(String[] args) {
        Thread t=new MyThread();          //向上转型
        t.start();
        System.out.println("hello main");
    }
}

在这里插入图片描述

一个进程至少有一个线程,当运行上面的程序,操作系统就会调用一个java进程,在这个java进程中的一个线程调用了main( )方法,这个线程就是主线程;

一般我们认为,对于进程而言,如果在进程A中创建了进程B,我们就称进程A为进程B的父进程,进程B为进程A的子进程;但是对于线程而言,一般认为线程之间就是并行的,不存在父线程或子线程的说法。

上面的运行结果显示是首先打印了 hello main,但实际上每个线程都是独立的执行流,这些执行流之间的执行是并发进行的,两个线程之间的执行顺序取决于操作系统的内部调度,于我们而言,就是随机的。因此,即使我们运行数次程序得到的打印结果都是一致的,我们也不能轻易确定下一次程序的执行结果;

很多时候,虽然我们没有手动创建许多线程,但当程序运行调用java进程,java进程中一般就会包含许多线程,那么如何查看有哪些线程正在执行呢?

为了可以清楚的看到正在执行的线程,我们使用死循环的方式延长程序的执行时间:

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

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

    }
}

使用 jconsole.exe查看正在执行的进程:
在这里插入图片描述
双击运行:
在这里插入图片描述在这里插入图片描述
找到线程:
在这里插入图片描述这里的main与Thread-0就是我们刚刚程序运行中自己的线程,而其他的线程就是java进程中的:
在这里插入图片描述利用死循环的方式,程序执行过快,就可以使用Thread.sleep( )方法来进行改进:

class MyThread extends Thread{
    @Override
    public void run() {
        while (true){
            System.out.println("hello thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}
public class Test1 {
    public static void main(String[] args) {
        Thread t=new MyThread();
        t.start();
        while (true){
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

在这里插入图片描述

通过运行结果,是不是也更加清楚的观察到2个线程的调度是随机的呢!

创建多线程程序

  • 创建一个类继承Thread,重写其中的run( )方法;

上面多线程程序的创建其实就是采用了这种方法,在run( )内部指明这个多线程是要完成什么任务,然后创建实例就是将任务交给这个新创建的引用,最后使用start( )方法才是真正创建了线程;

  • 创建一个类实现Runnable,重写run( )方法;
class MyRunnable implements Runnable{
    @Override
    public void run() {
        while (true){
            System.out.println("hello thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}
public class Test2 {
    public static void main(String[] args) {
        Runnable runnable=new MyRunnable();   //创建实例
        Thread t=new Thread(runnable);   //将任务交给线程
        t.start();
        while (true){
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

前面创建类继承类的方式,实际是将线程与任务内容绑定在了一起;而这种实现接口的方法则是做到了线程和任务的分离;

  • 创建一个类继承Thread类,非显示继承,使用匿名内部类;
public class Test3 {
    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) {
                        e.printStackTrace();
                    }

                }
                
            }
        };
        t.start();
    }
}

  • 以匿名内部类的方式使用Runnable;
public class Test4 {
    public static void main(String[] args) {
        Thread t=new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        t.start();
    }
}

  • 使用lambda表达式来定义任务;
public class Test5 {
    public static void main(String[] args) {
        Thread t=new Thread(()->{
            while (true){
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
    }
}

lambda表达式的本质是一个匿名函数,由参数和返回值两部分组成,语法格式是:(参数)- > 返回值
(参数)- > {返回值} (参数可以有多个,也可以没有);

多线程编程的优势

多线程编程的最大优势就是可以提高运行速度,下面通过单线程和多线程对于运行时间的长短来证明这一点:
首先是一个单线程的程序:

public class Test6 {
    private static final long count=50_0000_0000l;

    private static void serial(){
        //使用System.currentTimeMillis()来记录当前的毫秒时间戳
        long beg=System.currentTimeMillis();
        int a=0;
        for(long i=0;i<count;i++){
            a++;
        }
        a=0;
        for(long i=0;i<count;i++){
            a++;
        }
        long end=System.currentTimeMillis();
        System.out.println("单线程消耗的时间: " +(end-beg)+" ms ");

    }

    private static void concurrency(){
        long beg=System.currentTimeMillis();
        Thread t1=new Thread(()->{
            int a=0;
            for(long i=0;i<count;i++){
                a++;
            }
        });
        Thread t2=new Thread(()->{
            int a=0;
            for(long i=0;i<count;i++){
                a++;
            }
        });
        t1.start();
        t2.start();

        try {
            t1.join();//调用join方法为了使t1的线程执行结束主线程再执行
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long end =System.currentTimeMillis();
        System.out.println("并发执行消耗的时间:"+(end-beg)+ " ma");

    }


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

    }
}

运行结果:
在这里插入图片描述通过运行结果可以看出,并发执行的方式执行速度要更快;

join()方法要做的事就是,当有新的线程加入时,主线程会进入等待状态,一直到调用join()方法的线程执行结束为止,就有效避免了主线程对执行时间的影响;

多线程的使用场景

  • CPU密集场景
    前面提到,多线程的引入就是为了最大程度的利用CPU的多核资源,因此,在CPU密集场景使用多线程编程也就再合适不过了;
  • IO密集型场景
    IO即输入输出,因为在IO密集型场景中,几乎不需要CPU就能快速完成读写数据的操作,一般都需要很大的时间等待,这时,使用多线程就可以避免CPU过于闲置。
    over!
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值