线程小白的学习笔记

本篇为 Java编程的逻辑 的并发内容的学习笔记

什么是线程?

线程是表示一条单独执行的程序执行流,它有自己的程序计数器和栈。

创建线程

Java 中的 Thread对象 实现了 Runnable 接口,因此创建线程的方法有两种。

public class Thread implements Runnable {}
1.继承Thread

通过继承 Thread 并重写其中的 run 方法来实现一个线程:

public class MyThread extends Thread {
 @Override
 public void run() {
   System.out.println("实现了一个线程");
 }
}

定义了这么一个类并不代表代码就会立即执行,线程是需要启动的,启动就需要一个 Thread 对象并调用 start 方法:

public static void main(String[] args) {
    MyThread thread = new MyThread();
    thread.start();
}

start 方法表示启动该线程,使其成为一个单独的程序执行流 ,操作系统会分配相关资源给它,让它参与 cpu 的抢夺,抢到 cpu 后执行的方法就是 run 方法

但是要怎么确认代码是在哪个线程中执行的呢?

Thread 有一个静态方法可以返回当前执行代码所在的线程对象:

public static native Thread currentThread();

每个线程都有一个 idname :

public long getId() 
public final String getName() 

通过上述方法我们可以判断代码是在哪个线程里面执行了,修改 MyThread 里面的 run 方法:

@Override
 public void run() {
   System.out.println("thread name: " + Thread.currentThread().getName());
   System.out.println("实现了一个线程");
 }

为了更为直观,我们在main 方法里面也增加类似的获取线程名字的打印,那么输出结果就是:

thread name: main
thread name: Thread-0
实现了一个线程
2.实现Runnable接口

上面的方法实现线程的方法虽然简单,但是 Java 是单继承的,这样子并不利于扩展。所以,推荐都是通过 Runnable 接口来实现线程。

public interface Runnable {
    public abstract void run();
}

具体的实现调用方法如下:

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("通过Runnable实现了一个线程");
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start();
    }
}

实现了 Runnable 接口后还需要一个线程对象来装载它并启动线程,执行 Runnable 接口里面的 run 方法。

线程里面的基本属性和方法

线程里面有一些基本属性和方法,这里简单介绍一下。

1.id和name

这个前面提过了,每个线程都有idname

id 是一个整数,这个是递增的整数,只在线程创建时进行加一赋值,不提供额外的修改方法。

name 的默认值是 Thead- 后面跟一个编号,可以通过 setName 进行修改,可以给线程取名来方便调试,但是个人更加推荐使用 id 来进行调试,因为它具备唯一性

2.线程的优先级

线程有一个优先级的概念,Java 中的线程优先级是 1 到 10 ,默认为 5,优先级相关方法为:

public final void setPriority(int newPriority)
thread.getPriority()

一般来说,优先级越高越容易抢到 CPU ,但是这个并非强制性的,只是相对容易而已,所以实际开发中不要太过依赖线程的优先级。

3.状态

线程里面有一个状态的概念,也可以简单理解为线程生命周期的一部分,通过下面方法获取:

public State getState()

返回对象 State 是一个枚举对象,它有如下值:

public enum State {
        /**
         * 线程创建了,但是没有调用start()
         */
        NEW,
        /**
         * 线程抢夺CPU中,抢到就执行run()
         */
        RUNNABLE,
        /**
         * 线程阻塞了
         */
        BLOCKED,
        /**
         * 线程等待唤醒的阻塞
         */
        WAITING,
        /**
         * 线程在等待某个时间点给唤醒
         */
        TIMED_WAITING,
        /**
         * 线程运行结束了
         */
        TERMINATED;
}

Thread 结束运行了,并不代表它已经死掉了,只能说它将要给回收掉,判断 Thread 是否活着要看这个方法:

public final native boolean isAlive();

只要 Thread 启动后没有给回收,返回值都是 true 。

4.是否守护线程

Java中线程分为两种类型:用户线程守护线程,默认启动的线程都是用户进程,通过 Thread.setDaemon(true) 将线程设置为守护线程

守护线程**有什么用呢?它就是个辅助线程,所以当全部的用户线程都给回收后,它就没有存在的意义了。

最典型的例子就是,Java 里面的垃圾回收器就执行在守护线程里面,当 main 线程执行结束后,垃圾回收器也会一起退出了。

5.sleep 方法

Thread 有个静态的 sleep 方法可以让线程让出 CPU 并睡上指定的时间,而且在线程睡眠的情景可以给中断,如果给中断了就会抛出中断异常了。

public static native void sleep(long millis) throws InterruptedException;
6.yield 方法

这个也是一个静态方法,调用该方法是告诉系统: 我现在不急着要抢 CPU ,你可以优先分配给其他线程。

不过,这个并没有强制作用,系统可能会忽略这个声明。

public static native void yield();
7.join 方法
public final void join() throws InterruptedException

如果 A 线程里面启动了 B 线程,B 线程调用了 join 方法的话,那么 A 线程必须等到 B 线程结束之后,才可以结束:

 public static void main(String[] args) throws InterruptedException {
        MyThread thread = new MyThread();
        thread.join();
        thread.start();
        while (Thread.currentThread().isAlive()) {
            System.out.println("Test : " + thread.isAlive());
            if (!thread.isAlive()) {
                System.out.println("finally : " + thread.isAlive());
                break;
            }
        }
    }

这么一来,只有 MyThread 结束了,main 线程才可以结束了。

thread name: Thread-0
实现了一个线程
Test : true
finally : false

join 方法还有一个重载体,可以指定等待时间的时长,默认为0,也就是无限等待了。

public final synchronized void join(long millis) throws InterruptedException
8.过时方法

Thread 类里面还有一些过时的方法,这些方法都有各种隐患,我们不应该去调用它们了,如下:

public final void stop()
public final void suspend()
public final void resume()

线程的成本

我们为什么需要开启其他的线程呢?不少人都知道这样子可以充分利用 CPU 的计算能力和硬件资源,进一步提高效率。

但是,我们也必须要知道,线程是有成本,尤其是大量线程运行时,线程之间调度和切换,有可能让系统卡死。那么,我们程序的最大线程数应该是多少才合适?

在 Java 的并发里面,最常见的两个模型就是 CPU 密集型IO 密集型 ,对于这两个模式,最大线程数是不一致的。

CPU 密集型 顾名思义就是需要非常多的 CPU 计算资源,对于这种类型的应用,完全是靠 CPU 的核数来工作,所以为了让它的优势完全发挥出来,避免过多的线程上下文切换,比较理想方案是:

线程数 = CPU核数 + 1

IO 密集型 是指应用里面基本都是涉及到网络、磁盘等 IO 的任务, 一旦发生 IO,线程就会处于等待状态,当 IO结束,数据准备好后,线程才会继续执行。

针对这种并发模型,网上推荐如果是 web 应用的话,理想线程数为:

线程数 = CPU核心数 / (1- 阻塞系数 )

这个阻塞系数一般为0.8~0.9之间,也可以取0.8或者0.9,需要根据实际应用来判断。

但是如果是用在 Android 应用里面的话,网上资料大多推荐为:

线程数 = CPU核数 * 2 + 1

猜你喜欢

转载自blog.csdn.net/f409031mn/article/details/80672214