Bootstrap

Java多线程编程-基础篇

多线程相关的概念

并发

并发是指在同一时间段内,两个或多个任务在同一个处理器上交替执行,使得在宏观上看起来像是同时进行。并发是通过快速切换任务来模拟同时执行的效果,实际上在任何一个时刻点上只有一个任务在执行。

 也就是说,并发看起来像多个程序同时运行,但真相是CPU一个个按顺序快速执行的结果,给人造成程序在同一时刻都在运行的”假象“,仿佛在并行

并行

并行是指两个或多个事件在同一时刻发生,即多个指令或任务在同一时刻被多个处理器同时执行。在并行计算中,每个处理器都独立地执行任务,互不干扰。

并发就是真的多个程序在同时运行了,现在很多电脑的处理器都是多核处理器,其中多核处理器就是能够完成并行的必要条件 ,因为每个处理器在同一时刻只能处理一个事件,当我们想要达到并发的状态需要多个处理器来分别处理每个事件。

进程

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

 也就是说,一个程序在运行时就会产生一个进程,这个进程就是这个程序的一切数据和操作的集合,例如当我们打开浏览器时,系统中就会产生一个和浏览器相绑定的进程

线程

线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。

 一个程序运行后会执行各种操作,这种操作就是以线程的方式在计算机系统下执行的,例如我们打开QQ,会产生一个进程,当我们打开一个好友的聊天框又会生成这个进程的聊天框线程来完成发送消息语音通话等任务。

下面来介绍如何在java程序中实现多线程运行

Java中创建线程

1. 通过实现Runnable接口

在Java中提供了Runnable这个接口,我们想要创建新的线程可以定义新的类实现这个接口,然后实现其中的run()方法,在方法中编写我们的业务代码,这个run方法是线程的入口

查看源码我们可以看到它的代码就是这么朴实无华

public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

然后我们需要知道,java中创建新的进程需要调用Thread类的start方法,但是Runnable接口中也没有start方法呀,但是我们可以创建一个Thread类型的对象,用我们定义的实现了Runnable接口的类当作构造器参数实例化一个Thread类型的对象,然后利用这个对象调用start方法,它会自动调用我们新实现的run方法

查看源码我们可以看到Thread有这样一个构造器,它的参数是Runnable类型,我们知道接口类型可以接收实现了该接口的类的引用,所以我们直接传入我们新定义的类即可

    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

下面上代码示例,大家可以自己运行试一下

public class ThRTest{

    public static void main(String []args) throws InterruptedException{

    
        Thread thread1=new Thread(new Dog());

        thread1.start();//调用start方法才会创建一个线程

        thread1.setName("狗狗线程");//给线程设置一个名字
        System.out.println(thread1.getName()+"开始执行.......");//get方法可以获取线程的名字,如果我们不自己设置,会有一个默认的名字
        
        for(int i=0;i<10;i++){
            System.out.println("主线程在执行"+i);
            Thread.sleep(500);//Thread类的静态方法,可以让程序暂停单位时间
        }

        
    }
    
}

class Dog implements Runnable{

    int times=0;
    @Override   //run方法中定义我们需要多线程执行的代码
    public void run() {

        while(true){

            System.out.println("狗狗线程执行中"+(++times));

            try {
                Thread.sleep(500);//Thread类的静态方法,可以让程序暂停单位时间
            } catch (InterruptedException e) {
                
                e.printStackTrace();
            }
            if(times==20)
                break;
        }
        
    }
    
}

上面这个代码,在执行到主函数时会给主函数生成一个主线程,然后当主线程执行到我们调用start方法时会为生成一个新的线程去执行,我这个代码后又写了个循环输出字符串标记,执行这个代码我们可以得到以下结果

这里我们可以看到主函数中的输出代码和我们自己创建的线程中的输出代码是并行执行的,我刚学的时候感觉很神奇,居然还能这么玩,以上代码里面有一些Thread的方法,都很简单大家看注释应该也能看明白这里就不一一解释了。

2.通过继承Thread类

前面我们已经使用过Thread类了,其实Thread类也是实现了Runnable接口,只不过是又新增了一些创建线程相关的方法而已。

我们通过源码查看Thread类的定义

public class Thread implements Runnable 

可以看到它也是实现了接口Runnable

第一个方法中我们最后还是通过Thread类创建了线程,因为start方法只有Thread类中有,所以当我们新定义的类直接继承了Thread类的时候我们就可以直接创建我们定义的类的对象就可,因为大家知道子类会继承父类的方法,所以我们利用这个方法创建线程只需要对上面的代码略作修改

如下:


public class ThRTest{

    public static void main(String []args) throws InterruptedException{

        

        
        Thread thread1=new Dog();

        thread1.start();//调用start方法才会创建一个线程

        thread1.setName("狗狗线程");//给线程设置一个名字
        System.out.println(thread1.getName()+"开始执行.......");//get方法可以获取线程的名字,如果我们不自己设置,会有一个默认的名字
        
        for(int i=0;i<10;i++){
            System.out.println("主线程在执行"+i);
            Thread.sleep(500);//Thread类的静态方法,可以让程序暂停单位时间
        }

        
    }
    
}

class Dog extends Thread{

    int times=0;
    @Override   //run方法中定义我们需要多线程执行的代码
    public void run() {

        while(true){

            System.out.println("狗狗线程执行中"+(++times));

            try {
                Thread.sleep(500);//Thread类的静态方法,可以让程序暂停单位时间
            } catch (InterruptedException e) {
                
                e.printStackTrace();
            }
            if(times==20)
                break;
        }
        
    }
    
}

结果和上面的一样这里就不展示了

为什么会有两种创建方法

很多朋友很疑惑为什么会有好几种方式创建线程,这两种方法的区别和连续??

我们可以看到两种方法,一个是通过实现接口,一个是通过继承,提到继承我们就自然而然的离不开Java的继承规则,不允许多继承,即每个类最多只能继承一个类,那如果我们新定义的Dog类继承自Animal类我们就只能利用实现Runnable接口的方式来创建线程了,算是对java继承机制的一个弥补,方便用户编程,因为现实中很多类都需要继承别的类,所以一般情况下都是利用实现接口这个方法。

实现接口类创建线程的优点

我们来看实现接口类创建线程的代码

        Dog dg=new Dog();
        Thread thread1=new Thread(dg);

        thread1.start();//调用start方法才会创建一个线程

我们只需要给Thread构造器传一个参数就可以创建线程,那么当我们需要创建大量的相同的线程时,我们就可以利用这个方式,只需要创建一个Dog类对象,然后重复创建Thread即可,如果是继承的方式,每生成一个线程我们都需要生成一个新的对象,相对来说减少了内库占用,也显得代码更加简洁实用。

总结

多线程在实际的应用程序中法非常常用,例如我们在12306买票就是一个多线程的过程,每个准备抢票的人的手机上都会生成一个抢票线程,当全国几亿人同时访问这个软件,它如何能不出错,保证系统运行正常呢,这里面涉及的并发并行和多线程知识非常之多。希望大家都要学好这一块的知识,学习在路上,加油!

;