初识多线程

多线程的创建


前言

多线程在我们实际的编程应用中经常会用到,在日常软件的使用中也很常见,比如:你可以边使用QQ聊天、边听音乐,也可以边写程序边给朋友打视频电话等等。这些功能都依赖于多线程的实现,如果没有多线程的话系统处理的效率将会大打折扣,当然在单核处理器下也不可能做到以上的行为。今天就来简单地聊一下我认为的多线程和其中的一些应用。


一、线程和进程是什么?

在学习多线程的内容之前,我们必须对线程和进程有一个大概的了解。
进程:顾名思义,简单来讲就是,当一个软件运行了,它就被看做是一个进程(”行进中的程序")
线程:指的是,当一个软件运行后,他可以同时干很多事情,那么这时每件事情就是一个线程。
例如:QQ可以聊天的同时传文件,两者同时进行。 那么QQ就是一个进程,它拥有聊天和传文件这两个线程。
PS:当然实际中QQ绝对不止这两个线程,因为软件在后台还会运行很多对用户不透明的工作
截取的一张任务管理器的资源监视器,大家也可以在这里看到此时自己PC的线程和进程情况

二、单核处理器是否支持多线程?

如果电脑是单核的,那么此时支不支持多线程呢? 答案是支持!

此时多个线程会抢占CPU的执行权,执行到就执行,执行完就释放资源,如果还要执行则继续抢。
因为这个抢占和执行的速度很快,所以在我们看来虽然是单核处理器,但是进程是同时进行的。
(这里详细的讲解可以去翻阅进程的五/七状态模型,此处不做展开,有基础概念即可)

三、创建多线程

我们现在已经了解了进程和线程的概念和区别,下面就进入主题:多线程
首先来了解几种常见的创建多线程的方法

1.通过继承Thread类创建

代码如下(示例):

public class ThreadDemo extends Thread{
    /**
     * 每一个方法都有一个无参数的构造方法
     * 如果是继承则会调用父类的构造方法
     */
    //默认的无参构造方法
    public ThreadDemo(){
        super();
    }
    @Override
    public void run(){
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+"*****"+i);
        }
    }

    /**
     * 目前这个代码是三个线程同时执行 谁先执行谁后执行还不一定
     * 跟之前不一样 以前是只有一个main线程(主线程) 从上到下执行
     * @param args
     */
    public static void main(String[] args) {
        System.out.println("我是main方法,我是入口");//main方法是主线程

        ThreadDemo thread1 = new ThreadDemo();//线程1
        ThreadDemo thread2 = new ThreadDemo();//线程2
        
        thread1.start();//线程进入就绪态 准备抢cpu 抢到就进入运行态
        thread1.start();

        System.out.println("main方法结束");
    }
}

运行结果:

1、我们可以通过继承Thread使用它的默认无参构造方法,new一个新的ThreadDemo实例化对象thread1,这个对象就是一个线程。
2、thread1中有start()方法,在调用该方法后,线程就进入就绪态,准备抢占CPU,当抢占到后则变为运行态。处于运行态后会执行重写过的run()方法。
3、可以使用Thread.currentThread().getName()方法获得当前线程的Name。
4、可以看到main函数中的语句并不是顺序输出的,而是乱序,当线程抢占到CPU资源后才进行输出。

2.通过实现Runnable接口创建

代码如下(示例):

public class MyRunnable implements Runnable{
    //函数式接口
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+"*****"+i);
        }
    }

    public static void main(String[] args) {
        System.out.println("main 开始");
        //第一种
        MyRunnable runnable = new MyRunnable();
        Thread thread1 = new Thread(runnable,"窗口1");
        Thread thread2 = new Thread(runnable,"窗口2");
        thread1.start();
        thread2.start();
		//第二种
        Runnable runnable1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("线程内容是:窗口3");
            }
        };
        Thread thread3 = new Thread(runnable1,"窗口3");

        /**
         * 以上代码也可以使用lambda表达式编写 但是不利于代码的重复利用 创建多个线程的话 run中的方法需要重复好几次
         */
         //第三种
        Thread thread4 = new Thread(() -> System.out.println("线程内容是:窗口4"), "窗口4");
        thread3.start();
        thread4.start();

        System.out.println("main 结束...");
    }
}

1、Runnable是一个接口,所以我们要实现它
2、我们可以使用Thread,传入一个Runnable和一个String传线程的名字,new一个Thread对象,那么这个对象就是一个线程(第一种)
3、同样的,我们也可以在main函数中实现Runnable接口(第二种)
4、第三种方法是对第二种方法的简写,使用了lambda表达式,但这样写也有缺陷,因为使用一次及被回收,如果还需要的话还要再写一遍。


3.通过实现Callable接口创建

public class CallableDemo implements Callable<String> {
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+"+++"+i);
        }
        return "hello";
    }

    public static void main(String[] args) throws Exception{
        System.out.println("主线程");
        //未来的任务 可以将一些耗时的操作交给他完成
        FutureTask<String> futureTask = new FutureTask<String>(new CallableDemo());
        new Thread(futureTask).start();

        //FutureTask间接实现了Runnable的实现类
        //想获得子线程的返回值 需要通过get方法 只有子线程执行完 才能拿到结果
        System.out.println(futureTask.get());
        System.out.println("主线程结束");
    }
}

1、一般用于子线程需要跑一个任务 这个任务工作量很大 并且需要返回值 可以考虑使用Callable。
2、Callable和Runnable一样都是接口,都需要实现。
3、Callable通常和FutureTask搭配使用。
4、Callable接口有返回值,而Runnable接口没有。

四、线程名设置

public class MyThread extends Thread{
    /**
     * 每一个方法都有一个无参数的构造方法
     * 如果是继承则会调用父类的构造方法
     */
    //默认无参
    public MyThread(){
        super();
    }
    //有参构造
    public MyThread(String name){
        super(name);
    }

    @Override
    public void run(){
    }

    public static void main(String[] args) {
        //给线程起名字
        
        //第一种 直接调用setName方法修改
        MyThread thread1 = new MyThread();//线程1
        thread1.setName("线程1");
        
        //第二种 创建线程的时候通过构造方法指定
        MyThread thread2 = new MyThread("线程2");//线程2
        
        //demo01.run();//不要直接run 否则不是多线程
    }
}

1、每一个方法都有一个无参数的构造方法,如果是继承则会调用父类的构造方法。继承中,子类的构造方法必须先调用父类的构造方法,父类的构造有有参和无参。
2、默认每个线程都有自己的默认的名字 Thread-xxxx


小结

三种创建方式对比:
继承Thread 是方案一, 实现Runnable 接口是方案二
1、优先使用 方案二
2、方案一是继承,java是单继承的,有局限性,而对于接口,一个类可以实现多个接口。
3、关于多线程的操作,都是Thread类,Thread类是专门管理多线程的,比如启动,设置名字等。而Runnable只是编写业务代码,将两者分开更加合理(代码的解耦)【高内聚低耦合】
4、使用Callable接口和Runnable接口相比
1)有返回值
2)抛异常
3)需要放入FutureTask中才能使用

PS(一些题外话)

创建线程的方法一共有四种,此处先列出3种,最后一种是从线程池中获取,下次再加上 嘿嘿
初次写文章,如有错误敬请指教,希望和大家多多交流学到更多的知识!

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值