多线程初探

1、程序、进程、线程

程序是为实现特定功能使用某语言而编写的代码,程序是静态的。例如:为了实现360安全卫生的垃圾清理功能而编写的代码即为程序。

进程是程序的一次动态执行过程,它需要经历从代码加载,代码执行到执行完毕的一个完整的过程,这也是进程的生命周期,它有完整的开始及消亡。每个进程有各自独立的一块内存,使得各个进程之间内存地址相互隔离。

线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。

线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位。

2、线程的创建

在JDK1.5以前线程有两种创建方式,分别为集成Thread类、实现Runnable接口。在JDK1.5以后增加两种线程创建方式:实现Callable接口、使用线程池创建。

以下通过窗口售票示例来进行说明。

2.1 通过继承Thread类创建创建线程

通过继承Thread类,重写run方法,在run方法内实现自己的业务即可。

package com.javaboy.thread.blob;

/**
 * 售票窗口线程
 */
public class Window extends Thread {

    /**
     * 票总数
     */
    private int tickets = 100;

    @Override
    public void run() {
        while(true) {
            if(tickets > 0) {
                System.out.println(this.getName() + "售票,票号:" + tickets);
                tickets--;
            } else {
                // 当票数为0时跳出循环
                break;
            }
        }
    }
}

上述代码Window为自定义的线程,通过继承Thread来达到线程的目的,荣国重写run方法来达到线程内售票的业务逻辑。

测试代码:

package com.javaboy.thread.blob;

public class WindowTest {

    public static void main(String[] args) {

        Window window1 = new Window();
        // 设置线程名称
        window1.setName("窗口1");

        Window window2 = new Window();
        window2.setName("窗口2");

        Window window3 = new Window();
        window3.setName("窗口3");

        // 启动线程
        window1.start();
        window2.start();
        window3.start();

    }
}

在上述测试代码中创建了三个Window对象,即为三个线程。
通过setName设置线程的名称,setName为Thread的方法,因为Window继承Thread,因此可以直接使用。
通过start方法来启动线程,在启动后会自动调用线程的run方法进行执行。此处需要注意,不能直接调用run方法,否则会变成方法调用,而不是线程启动

测试结果(部分结果,未包含全部):

窗口2售票,票号:100
窗口1售票,票号:100
窗口1售票,票号:99
窗口1售票,票号:98
窗口1售票,票号:97
窗口1售票,票号:96
窗口1售票,票号:95
窗口1售票,票号:94
窗口1售票,票号:93
窗口3售票,票号:100
窗口1售票,票号:92
窗口1售票,票号:91
窗口1售票,票号:90
窗口2售票,票号:99
窗口1售票,票号:89
窗口3售票,票号:99
窗口1售票,票号:88
窗口2售票,票号:98
窗口1售票,票号:87
窗口3售票,票号:98
窗口1售票,票号:86
窗口2售票,票号:97
窗口1售票,票号:85
窗口3售票,票号:97
窗口1售票,票号:84
窗口2售票,票号:96
窗口1售票,票号:83
窗口3售票,票号:96
窗口1售票,票号:82
窗口2售票,票号:95
窗口1售票,票号:81
窗口3售票,票号:95
窗口1售票,票号:80
窗口2售票,票号:94
窗口1售票,票号:79
窗口3售票,票号:94
窗口1售票,票号:78
窗口2售票,票号:93
窗口1售票,票号:77
窗口3售票,票号:93
窗口1售票,票号:76
窗口2售票,票号:92
窗口1售票,票号:75
窗口3售票,票号:92
窗口1售票,票号:74
窗口2售票,票号:91
窗口1售票,票号:73
窗口1售票,票号:72
窗口1售票,票号:71
窗口3售票,票号:91
窗口3售票,票号:90
窗口3售票,票号:89
窗口3售票,票号:88
窗口3售票,票号:87
窗口3售票,票号:86
窗口1售票,票号:70
窗口1售票,票号:69
窗口1售票,票号:68
窗口2售票,票号:90
窗口1售票,票号:67
窗口1售票,票号:66
窗口3售票,票号:85
窗口1售票,票号:65
窗口2售票,票号:89
窗口1售票,票号:64
窗口3售票,票号:84
窗口1售票,票号:63
窗口2售票,票号:88
窗口1售票,票号:62
窗口3售票,票号:83
窗口3售票,票号:82
窗口3售票,票号:81
窗口3售票,票号:80

线程的执行是由CPU决定的,因此不一定会知道哪个线程先执行,当然可以通过设置线程优先级等方式来提高优先执行的命中率。
在上述的测试结果中已经看到不同的窗口售出的票号出现了重复,这就是线程同步问题,在以后的介绍中进行解决。

2.2 通过实现Runnable接口创建线程

通过实现Runnable接口,重写run方法来实现线程。
创建步骤:

  1. 创建一个类(A)并实现Runnable接口;
  2. 重写run方法,将线程所要实现的功能写在run方法中;
  3. 创建实现Runnable接口类(A)的对象,将对象当做Thread类构造函数的参数参入;
  4. 使用Thread构造方法创建一个对象,调用start方法运行新线程。
package com.javaboy.thread.my;

/**
 * 线程创建方式:实现Runnable接口
 */
class Thread2 implements Runnable {
    @Override
    public void run() {
        for(int i=1; i<=10; i++) {
            System.out.println(Thread.currentThread().getName() + "运行的结果。。。" + i);
        }
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        Thread.currentThread().setName("主线程");
        System.out.println(Thread.currentThread().getName() + "正在运行。。。");

        Thread2 thread2 = new Thread2();

        // 创建新线程
        Thread thread = new Thread(thread2);

        thread.setName("测试线程");
        // 启动线程
        thread.start();

        System.out.println(Thread.currentThread().getName() + "运行结束。");
    }
}

运行结果:

主线程正在运行。。。
主线程运行结束。
测试线程运行的结果。。。1
测试线程运行的结果。。。2
测试线程运行的结果。。。3
测试线程运行的结果。。。4
测试线程运行的结果。。。5
测试线程运行的结果。。。6
测试线程运行的结果。。。7
测试线程运行的结果。。。8
测试线程运行的结果。。。9
测试线程运行的结果。。。10

2.3 通过实现Callable接口创建线程

通过实现Callable接口与实现Runnable接口相比,实现Callable接口时线程可以有返回值,同时可以抛出异常
创建步骤:

  1. 创建一个类(A)并实现Callable接口,需要指定泛型,泛型表示线程返回的类型;
  2. 重写Callable的call方法,将线程要实现的功能写在call方法中,同时返回指定的数据,如果有异常,可以抛出异常;
  3. 创建FutureTask对象,构造函数的参数为实现Callable接口的类(A)的对象;
  4. 创建Thread类,构造函数参数为创建的FutureTask对象;
  5. 通过Thread的start方法启动线程;
  6. 如果需要获取线程返回值和异常,需要调用FutureTask对象的get方法进行获取。
package com.javaboy.thread.my;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 线程创建方式:实现Callable接口
 */
public class CallableThreadTest {

    public static void main(String[] args) {
        // 创建FutureTask类,构造器参数为线程实例
        FutureTask<String> futureTask = new FutureTask<>(new ThreadDemo());
        Thread thread = new Thread(futureTask);
        thread.setName("测试线程");

        // 线程启动
        thread.start();

        // 获取线程返回值和异常
        try {
            String result = futureTask.get();
            System.out.println(thread.getName() + "返回的结果:" + result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }

}

/**
 * 线程类实现Callable接口<br>
 *     泛型标识线程返回的类型
 */
class ThreadDemo implements Callable<String> {
    @Override
    public String call() throws Exception {
        String threadName = Thread.currentThread().getName();

        for(int i=0; i<=10; i++) {
            System.out.println(threadName + "执行" + i);
        }

        return "这里是线程【" + threadName + "】返回的结果";
    }
}

运行结果:

测试线程执行0
测试线程执行1
测试线程执行2
测试线程执行3
测试线程执行4
测试线程执行5
测试线程执行6
测试线程执行7
测试线程执行8
测试线程执行9
测试线程执行10
测试线程返回的结果:这里是线程【测试线程】返回的结果

2.4 通过线程池创建线程

创建步骤:

  1. 使用Executors类的newFixedThreadPool创建指定数量的线程池;
  2. 调用线程池的execute方法执行实现Runnable接口的线程;通过线程池的submit方法执行实现Callable接口的线程;
  3. 调用线程池的shutdown方法关闭线程池。
package com.javaboy.thread.my;

import java.util.concurrent.*;

/**
 * 线程创建:通过线程池创建
 */
public class ThreadPoolTest {
    public static void main(String[] args) {
        // 创建线程数量为2的线程池
        ExecutorService service = Executors.newFixedThreadPool(1);

        // 通过execute执行实现Runnable接口的线程
        service.execute(new Demo1());
        service.execute(new Demo2());
        // 通过submit执行实现Callable接口的线程
        Future<String> task = service.submit(new Demo3());

        // 获取实现Callable接口的线程返回值和异常
        try {
            String result = task.get();
            System.out.println("返回的结果:" + result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        // 关闭线程池
        service.shutdown();

    }
}

class Demo1 extends Thread {
    @Override
    public void run() {
        Thread.currentThread().setName("线程1");
        for(int i=1; i<=10; i++) {
            System.out.println(Thread.currentThread().getName() + "输出的结果:" + i);
        }
    }
}

class Demo2 implements Runnable {
    @Override
    public void run() {
        Thread.currentThread().setName("线程2");
        for(int i=1; i<=10; i++) {
            System.out.println(Thread.currentThread().getName() + "输出的结果:" + i);
        }
    }
}

class Demo3 implements Callable<String> {
    @Override
    public String call() throws Exception {
        Thread.currentThread().setName("线程3");
        for(int i=1; i<=10; i++) {
            System.out.println(Thread.currentThread().getName() + "输出的结果:" + i);
        }

        return Thread.currentThread().getName() + "返回";
    }
}

执行结果:

线程1输出的结果:1
线程1输出的结果:2
线程1输出的结果:3
线程1输出的结果:4
线程1输出的结果:5
线程1输出的结果:6
线程1输出的结果:7
线程1输出的结果:8
线程1输出的结果:9
线程1输出的结果:10
线程2输出的结果:1
线程2输出的结果:2
线程2输出的结果:3
线程2输出的结果:4
线程2输出的结果:5
线程2输出的结果:6
线程2输出的结果:7
线程2输出的结果:8
线程2输出的结果:9
线程2输出的结果:10
线程3输出的结果:1
线程3输出的结果:2
线程3输出的结果:3
线程3输出的结果:4
线程3输出的结果:5
线程3输出的结果:6
线程3输出的结果:7
线程3输出的结果:8
线程3输出的结果:9
线程3输出的结果:10
返回的结果:线程3返回

线程的创建方式说明完毕,也从中发现一些问题:线程同步问题,在以后的介绍中进行解决。

多线程初探

上一篇:JZ35 数组中的逆序对


下一篇:数组题目请计算数组的 中心下标 724