首页 > 编程笔记

什么是线程,Java多线程编程(小白必读)

多线程是提升程序性能非常重要的一种方式,也是我们必须要掌握的技术。使用多线程可以让程序充分利用 CPU 的资源,提高 CPU 的使用效率,从而解决高并发带来的负载均衡问题。它的优点是显而易见的,比如:
任何一门技术都没有绝对的好与坏,有其优点就必有其缺点,多线程也存在一些缺点:
我们在实际开发的过程中,应该将程序设计得更加合理有效,避免多线程的缺点,将其优点发挥出来,从而提高程序的性能。

进程与线程

说到线程,就必须先提出进程的概念,什么是进程呢?

简单来理解,进程就是计算机正在运行的一个独立的应用程序,例如打开 Eclipse 编写 Java 程序就是一个进程,打开浏览器查找学习资料就是一个进程等。一个应用程序至少有一个进程。

那什么是线程呢?进程和线程之间的关系是什么呢?

线程是组成进程的基本单位,可以完成特定的功能,一个进程是由一个或多个线程组成的。进程和线程是应用程序在执行过程中的概念,如果应用程序没有执行,比如 Eclipse 工具没有运行起来,那么就不存在进程和线程的概念。应用程序是静态的概念,进程和线程是动态概念,有创建就有销毁,存在也是暂时的,不是永久性的。

进程与线程的区别在于,进程在运行时拥有独立的内存空间,即每个进程所占用的内存都是独立的,互不干扰。而多个线程是共享内存空间的,但是每个线程的执行是相互独立的,同时线程必须依赖于进程才能执行,单独的线程是无法执行的,由进程来控制多个线程的执行。

了解完进程与线程的区别,接下来说说什么是多线程。

我们通常所说的多线程是指在一个进程中,多个线程同时执行。这里说的同时执行不是真正意义上的同时执行,系统会自动为每个线程分配 CPU 资源,在某个具体时间段内 CPU 的资源被一个线程占用,在不同的时间段内由不同的线程来占用 CPU 资源,所以多个线程还是在交替执行,只不过因为 CPU 运行速度太快,感觉上是在同时执行。

我们之前写的 Java 程序都是单线程的,Java 程序运行起来就是一个进程,该进程中只有一个线程在运行,比如:
public class Test {
    public static void main(String[] args) {
        System.out.println("Thread");
    }
}

程序运行起来之后只有一个线程在执行,就是 main 方法,所以 main 方法也叫作程序的主线程,main 方法中无论调用多少个其他类的方法,也都只是一个线程,比如:
public class MyTest {
    public void test() {
        for (int i = 0; i < 5; i++) {
            System.out.println("--------------MyTest");
        }
    }
}

public class Test {
    public static void main(String[] args) {
        for(int i = 0; i < 5; i++) {
            System.out.println("++++++++++++++Test");
        }
        MyTest myTest = new MyTest();
        myTest.test();
    }
}
运行结果为:

++++++++++++++Test
++++++++++++++Test
++++++++++++++Test
++++++++++++++Test
++++++++++++++Test
--------------MyTest
--------------MyTest
--------------MyTest
--------------MyTest
--------------MyTest

通过结果可以看到,我们在 main 方法中调用了两个循环逻辑,这两个循环逻辑是顺序执行的,先执行完打印“++++++++++++++Test”的循环,再执行打印“--------------MyTest”的循环,其实还是一个线程。

那什么是多线程呢?两个循环逻辑不是顺序执行,而是同时执行,比如输出结果为:

++++++++++++++Test
++++++++++++++Test
--------------MyTest
++++++++++++++Test
--------------MyTest
......

你可以看到两个打印语句在交替输出,说明两个循环逻辑是同时在执行的,这种情况就是两个线程在同时运行。

我们如何来判断程序是单线程还是多线程呢?只需要分析程序有几条分支即可,例如:


图 1 单线程

整个程序的执行是一条回路,所以程序只有一个线程。再例如:


图 2 多线程

程序有两条回路,同时向下运行,这种情况就是多线程,两个线程同时执行。

Java中线程的使用

Java 中实现多线程的常用方式有两种,分别是继承 Thread 类和实现 Runnable 接口。

1) 继承Thread类

继承 Thread 类的实现分为两步:
  1. 创建自定义类并继承 Thread;
  2. 重写 Thread 的 run() 方法,并编写该线程的业务逻辑代码。

具体实现如下面的代码所示:
public class MyThread extends Thread{
    @Override
    public void run() {
        // TODO Auto-generated method stub
        for (int i = 0; i < 5; i++) {
            System.out.println("--------------MyThread");
        }
    }
}

线程创建好之后,如何调用呢?具体实现为:
public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        for(int i = 0; i < 5; i++) {
            System.out.println("++++++++++++++Test");
        }
    }
}
运行结果为:

--------------MyTest
--------------MyTest
++++++++++++++Test
++++++++++++++Test
++++++++++++++Test
--------------MyTest
--------------MyTest
++++++++++++++Test
--------------MyTest
++++++++++++++Test

你可以看到两个循环在交替执行,当前程序中有两个线程,一个是主线程,即 main 方法,主线程完成了两件事,第一件事是开启了一个子线程 MyThread,第二件事是执行一个循环操作,因为子线程开启之后和主线程在竞争 CPU 资源,交替执行,所以会看到交替打印两个循环信息的结果。

这里需要注意,开启子线程是通过调用线程对象的 start() 方法来完成的,一定不能调用 run() 方法,调用 run() 方法是普通的方法调用,相当于在主线程中顺序执行了两个循环,并没有开启一个可以和主线程抢占 CPU 资源的子线程。所以调用 run() 方法并不是多线程,还是一个主线程在执行,比如:
public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.run();
        for(int i = 0; i < 5; i++) {
            System.out.println("++++++++++++++Test");
        }
    }
}
运行结果为:

--------------MyThread
--------------MyThread
--------------MyThread
--------------MyThread
--------------MyThread
++++++++++++++Test
++++++++++++++Test
++++++++++++++Test
++++++++++++++Test
++++++++++++++Test

2) 实现Runnable接口

Java 中创建多线程的另外一种方式是通过 Runnable 接口的实现来完成,具体分为两步:
  1. 创建自定义类并实现 Runnable 接口;
  2. 实现 run() 方法,编写该线程的业务逻辑代码。

具体实现代码如下:
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        // TODO Auto-generated method stub
        for (int i = 0; i < 5; i++) {
            System.out.println("--------------MyRunnable");
        }
    }
}

调用 MyRunnable 线程的具体实现如下:
public class Test {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
        for(int i = 0; i < 5; i++) {
            System.out.println("++++++++++++++Test");
        }
    }
}
MyRunnable 的使用与 MyThread 略有不同,MyRunnable 相当于定义了线程业务逻辑,它本身并不是线程对象,所以还需要实例化 Thread 对象,然后将 MyRunnable 对象赋给 Thread 对象。这样 Thread 对象就知道了它要完成的业务逻辑,再通过调用 Thread 对象的 start() 方法来启动该线程,运行结果为:

++++++++++++++Test
++++++++++++++Test
++++++++++++++Test
--------------MyRunnable
--------------MyRunnable
--------------MyRunnable
++++++++++++++Test
++++++++++++++Test
--------------MyRunnable
--------------MyRunnable

推荐阅读