多线程编程

目录

♫什么是线程

♫Java的线程和操作系统线程的关系

♫第一个多线程代码

♫Thread类常见的构造方法

♫创建线程的方式

♪继承Thread类

♪使用匿名内部类继承Thread类

♪实现Runnable接口

♪使用匿名内部类实现Runnable接口

♪使用Lambda表达式

♫Thread类的常见属性

 ♫线程终止

♪使用自定义的标志位

♪使用Thread自带的标志位

 ♫等待一个线程

 ♫线程的状态

♫多线程的意义


♫什么是线程

我们已经知道进程能充分利用CPU,实行并发编程,但因为进程消耗资源多且执行速度慢,所以就有了比进程更轻量的线程(线程创建,销毁和调度所需开销都比进程低),因此线程也被称为“轻量级进程”。

线程可以看作是进程中的一个实体,一个进程可以包含一个或多个线程,这些线程共享进程的同一份资源(主要指内存(一个线程new的对象其他线程也能用)和文件描述符表(一个线程打开的文件其他线程也能用)),但是每个线程都拥有自己的栈空间和局部变量,都可以执行各自的上下文,且不会相互影响。

注:线程是系统调度的最小单位,进程是系统资源分配的最小单位

♫Java的线程和操作系统线程的关系

线程是操作系统中的概念, 操作系统内核实现了线程这样的机制,并且对用户层提供了一些 API 供用户使用(例如 Linux 的 pthread 库)。Java虚拟机JVM对很多操作系统提供的API进行封装,因此我们只需了解JVM提供的API就能进行Java 线程的相关操作。标准库中的 Thread 类就是对操作系统提供的 API 进行了进一步的抽象和封装的类。

♫第一个多线程代码

Thread类在Java.lang包(java的内置包,无需显式导入)下,创建线程主要用到Thread类的两个方法:

♩.start()start()的作用是调用系统的API,通过操作系统内核创建新的线程,并把要执行的指令交给新线程,当新线程调度到CPU上执行是,就执行到run()方法。

♩.run()run()是线程要执行的任务

下面就是通过继承Thread,重写run()的方式在主线程下创建了一个新线程thread,主线程和thread线程并发打印五次自己的线程名:

class MyThread extends Thread {
    @Override
    public void run() {
        int a = 5;
        while (a-- > 0) {
            //打印该线程的线程名
            System.out.println(Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
public class ThreadDemol {
    public static void main(String[] args) {
        Thread thread = new MyThread();
        //创建线程
        thread.start();
        int a = 5;
        while (a-- > 0) {
            //打印所在线程的线程名
            System.out.println(Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

运行结果如下:

多线程编程_第1张图片

注:

①.由于线程之间是抢占式执行的,所以在应用程序这一层无法判断main线程和thread线程谁先执行,谁后执行。

②.public static Thread currentThread()方法用于返回当前线程对象的引用。(类比获取所在对象的this)

③.public static void sleep(long millis) throws InterruptedException方法用于休眠当前线程millis毫秒。

使用jdk自带的jconsole工具可以查看java进程里的所有线程:

多线程编程_第2张图片

点进thread.ThreadDemol就可以查看进程里的线程:

多线程编程_第3张图片

♫Thread类常见的构造方法

方法 描述
Thread() 创建线程对象
Thread(Runnable target) 使用Runnable对象创建线程对象
Thread(String name) 创建线程对象,并命名
Thread(Runnable target, String namre) 使用Runnable对象创建线程对象,并命名

Thread(ThreadGroup group, Runnable target)

线程可以被用来分组管理,分好的组即为线程组

Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("张三");
Thread t4 = new Thread(new MyRunnable(), "李四");
注:t3和t4是代码里的线程名,张三和李四是系统里创建的线程名

♫创建线程的方式

♪继承Thread类

创建一个继承Thread类的子类,并重写run方法,通过调佣start创建线程:

//继承Thread类
class MyThread extends Thread {
    @Override
    public void run() {
        //线程执行内容
    }
}
public class ThreadTest {
    public static void main(String[] args) {
        Thread thread = new MyThread();
        thread.start();
    }
}

♪使用匿名内部类继承Thread类

还可以通过使用匿名内部类的方式继承Thread类:

//使用匿名内部类继承Thread类
public class ThreadTest {
    public static void main(String[] args) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        };
        thread.start();
    }
}

♪实现Runnable接口

创建一个实现Runnable接口的类,并重写run方法,通过调用start创建线程:

//实现Runnable接口
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
public class ThreadTest {
    public static void main(String[] args) {
        Runnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
    }
}

♪使用匿名内部类实现Runnable接口

还可以通过匿名内部类实现Runnable接口:

//匿名内部类
public class ThreadTest {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
    }
}

♪使用Lambda表达式

还可以把任务用Lambda表达式描述,直接将表达式传递给Thread的构造方法:

//使用lambda表达式创建Runnable子类对象
public class ThreadTest {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            System.out.println(Thread.currentThread().getName());
        });
    }
}

♫Thread类的常见属性

属性 获取属性的方法
ID getId()
名称 getName()
状态 getState()
优先级 getPriority()
是否为守护线程 isDaemon()
是否存活 isAlive()
是否被中断 isInterrupted()

♩.ID 是线程的唯一标识,不同线程不会重复。

♩.名称 是各种调试工具用到。(指系统里的线程名)

♩.状态 表示线程当前所处的一个情况,下面我们会进一步说明。

♩.优先级 高的线程理论上来说更容易被调度到。(优先级可以获取也可以设置,只是设置了没啥用)

♩.守护线程 需要记住一点:JVM会在一个进程的所有非守护线程结束后,才会结束运行。(代码里主线程和自己创建的线程默认都是守护线程,其他JVM自带的线程则是守护线程)

♩.是否存活 即简单的理解为 run 方法是否运行结束了。(run()正在跑,isAlive()就是true;run()还没跑或run()跑完了,isAlive()就是false)

♩.线程是否中断 取决于线程本身的代码的实现。

 ♫线程终止

终止线程并不是让线程直接终止掉,而是通知线程应该终止,线程究竟终不终止取决于线程本身的代码实现(就好比老师布置的作业,你可以马上写,你也可以过会在写,你甚至还可以不写)。我们可以通过标志位来实现线程终止操作:

♪使用自定义的标志位

设置公共标志位flag,通过设置flag的值控制线程终止:

public class Test {
    public static boolean flag = true;
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            while (flag) {
                System.out.println(Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
        Thread.sleep(1000);
        //主线程里可以通过修改flag的值控制thread线程的终止
        flag = false;
    }
}

运行结果:

多线程编程_第4张图片

注:使用自定义标志位这种方式不能及时响应,尤其在sleep休眠时间较长的情况下

♪使用Thread自带的标志位

通过Thread.currentThread.isInterrupted()方法判断标志位,通过thread.interrupt()设置标志位来实现线程终止:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println(Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}

运行结果:

多线程编程_第5张图片

可以观察到:使用interrupt()会令sleep捕获到InterruptedException异常,这样就能使sleep提前终止。此外,虽然interrupt修改了线程内部的标志位,但sleep在唤醒的时候会将标志位重新修改回去,因此在sleep被唤醒后仍会执行线程里的代码(不写作业)。当然我们也可以在sleep里加个break让线程立即终止(马上去写作业):

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println(Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;-
                }
            }
        });
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}

运行结果:

多线程编程_第6张图片

当然,break也可以换成其它代码(过会再写作业),这取决于thread的代码实现。

 ♫等待一个线程

线程是随机调度的,等待一个线程就是让一个线程等待另一个线程执行后完毕再执行,这样可以控制两个线程执行的先后顺序

等待一个线程可以通过join关键字来实现,下面就是一个main线程等待thread线程的例子:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName());
        });
        thread.start();
        System.out.println("join前");
        thread.join();
        System.out.println("join后");
    }
}

运行结果:

多线程编程_第7张图片

上面这种写法相当于main线程死等thread线程,我们还可以通过传递参数,让main线程阻塞等待thread一定时间,thread线程执行完毕或超过指定的时间main线程都会开始执行:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName());
        });
        thread.start();
        System.out.println("join前");
        thread.join(100);
        System.out.println("100毫秒后");
    }
}

运行结果:

 ♫线程的状态

线程状态是针对当前线程调度的情况来描述的,线程的状态是一个枚举类型 Thread.State。通过for-earch可以遍历线程状态:

public class Test {
    public static void main(String[] args) {
        for (Thread.State state : Thread.State.values()) {
            System.out.println(state);
        }
    }
}

运行结果:

多线程编程_第8张图片

♩NEW(新建状态):当线程对象被创建时,它处于新建状态。

♩RUNNABLE(就绪状态)当线程对象被创建后,其他线程调用了该对象的 start() 方法时,该线程进入就绪状态。

♩BLOCKED(阻塞状态):线程在等待某个监视器锁时,进入阻塞状态。

♩WAITING(等待状态):线程在等待其他线程通知调度器已经满足了某个条件时,进入等待状态。

♩TIMED_WAITING(等待状态):线程等待某个特定时间段的状态。

♩TERMINATED(终止状态):run方法执行完毕,但Thread对象还在。

下面是各种状态的大致流程:

多线程编程_第9张图片

♫多线程的意义

多线程可以利用计算机的多个处理器核心或多个CPU实现并发执行多个任务,提高计算机的执行效率和性能。

对于CPU密集型的任务,多线程的执行速率远高于单线程,如:自增20亿次的操作,在单线程中花费的时间:

public class Test {
    public static void main(String[] args) {
        long beginTime = System.currentTimeMillis();
        long a = 0;
        for (long i = 0; i < 20_0000_0000L; i++) {
            a++;
        }
        long endTime = System.currentTimeMillis();
        System.out.println("执行时间:" + (endTime-beginTime) + "ms");
    }
}

运行结果:

在多线程中花费的时间:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            long a = 0;
            for (long i = 0; i < 10_0000_0000L; i++) {
                a++;
            }
        });
        Thread t2 = new Thread(()->{
            long a = 0;
            for (long i = 0; i < 10_0000_0000L; i++) {
                a++;
            }
        });
        //记录开始执行的时间
        long beginTime = System.currentTimeMillis();
        t1.start();
        t2.start();
        //主线程等待t1,t2线程都执行完
        t1.join();
        t2.join();
        //记录执行完毕的时间
        long endTime = System.currentTimeMillis();
        System.out.println("执行时间:" + (endTime-beginTime) + "ms");
    }
}

 运行结果:

注:多线程提供运行速率的前提是CPU是多核CPU,且核心处于空闲状态

你可能感兴趣的:(java)