Java 并发编程(一)多线程基础

本贴最后更新于 1827 天前,其中的信息可能已经事过景迁

线程简介

  学习多线程之前,我们先要了解几个关于多线程有关的概念。

  进程:进程指正在运行的程序。确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能。

  线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

  简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程

  什么是多线程呢?即就是一个程序中有多个线程在同时执行。

  单线程程序与多线程程序的不同:

  • 单线程程序:即,若有多个任务只能依次执行。当上一个任务执行结束后,下一个任务开始执行。如,去网吧上网,网吧只能让一个人上网,当这个人下机后,下一个人才能上网。

  • 多线程程序:即,若有多个任务可以同时执行。如,去网吧上网,网吧能够让多个人同时上网。

线程的创建

1、继承 Thread 类,并覆盖 run() 方法

  Thread 的实现类继承 Thread 类,并覆盖其 run() 方法,run() 方法中定义线程需要执行的任务,并调用实现类的 start() 方法创建线程.

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("子线程启动,ID为:" + Thread.currentThread().getId() + ",名字为:" + Thread.currentThread().getName());
    }

}

class Test{
    public static void main(String[] args) {
        // 创建一个线程并开启线程
        MyThread thread=new MyThread();
        thread.start();
        // 多创建几个线程
        new MyThread().start();
        new MyThread().start();
        new MyThread().start();
    }
}

输出如下:

子线程启动,ID为:13名字为Thread-0
子线程启动,ID为:15名字为Thread-2
子线程启动,ID为:16名字为Thread-3
子线程启动,ID为:14名字为Thread-1

2、实现 Runnable 接口,并覆盖 run() 方法

因为 Java 是单继承的,因此直接继承 Thread 类常常并不是一个好主意.

我们常常通过实现 Runnable 接口或 Callable 接口定义一个任务,并将其传给 Thread 类构造函数来创建一个线程.其中 Runnable 类的 run()方法没有返回值,而 Callable 类的 call()方法可以有返回值.

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("子线程启动,ID为:" + Thread.currentThread().getId() + ",名字为" + Thread.currentThread().getName());
    }
}
class Test{
    public static void main(String[] args) {
        // 通过将Runnable对象传入Thread构造函数来创建线程,并开启线程
        Runnable runnable=new MyRunnable();
        Thread thread1=new Thread(runnable,"线程1");
        thread1.start();
        // 一个Runnable对象可以用来创建多个线程
        new Thread(runnable, "线程2").start();
        new Thread(runnable, "线程3").start();
        new Thread(runnable, "线程4").start();
    }
}

输出如下:

子线程启动,ID为:14,名字为线程2
子线程启动,ID为:13,名字为线程1
子线程启动,ID为:16,名字为线程4
子线程启动,ID为:15,名字为线程3

3、实现 Callable 接口,并覆盖 call() 方法

通过该方法创建的任务可以有返回值.但 Callable 对象只能传给线程池,创建线程的具体方法见后边文章.

要注意 CallableRunnable 定义的都是任务而不是线程,要将其传入一个线程或线程池后才可以执行.

线程的状态

线程有五个状态:

  1. 新生(new)状态: 用 new 关键字建立一个线程对象后,该线程对象就处于新生状态,有自己的内存空间.
  2. 就绪(runnable)状态: 调用了 start()方法后,线程转为就绪状态,处于就绪状态线程具备了运行条件,但还没分配到 CPU,等待系统 CPU 调度.
  3. 运行(running)状态: 处于运行状态的线程正在执行自己的 run()方法中代码.
  4. 阻塞(blocked)状态: 线程暂停执行,让出 CPU 并将其交给其他线程使用.
  5. 死亡(dead)状态: 当线程完成工作或抛出异常时,线程死亡,不再执行.

线程控制基本方法

判断线程状态的方法

  1. public long getId(): 得到线程的 ID

  2. public String getName()和 public void setName(String name): 得到或设置线程名称

  3. public boolean isAlive(): 判断当前线程是否处于活动状态

  4. public int getPriority()public void setPriority(int newPriority): 得到或设置线程优先级。Java 中线程的优先级为 1~10 之间的整数,一个线程的默认优先级为 5。优先级的高低只是意味着获得调度概率的高低,并不代表调度的绝对顺序.

  5. public static Thread currentThread() 返回当前代码段正在被哪个线程调用.

阻塞线程的方法

  • public void join(),public void join(long millis),public void join(long millis, int nanos):合并线程,调用某线程的 join()方法,会将当前线程与该线程合并,即等待该线程执行结束之后再恢复当前线程的运行。

join() 方法可以看作把并发的线程变为在一个线程内的函数调用

class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <=3 ; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class Test{
    public static void main(String[] args) throws InterruptedException{
        Thread thread1=new MyThread();
        thread1.start();
        thread1.join();

        Thread thread2=new MyThread();
        thread2.start();
        for (int i = 1; i <=3 ; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
            sleep(1000);
        }
    }
}

程序输出如下:

Thread-0:1
Thread-0:2
Thread-0:3
main:1
Thread-1:1
Thread-1:2
main:2
main:3
Thread-1:3
  • public static sleep(long millis),public void sleep(long millis, int nanos): 使线程停止运行一段时间并转入阻塞状态.
  1. sleep() 方法不会交出锁.
  2. 因为 sleep() 方法使线程进入 阻塞状态,因此若调用了 sleep() 方法后,即使没有其他等待执行的线程,当前线程也不会马上恢复执行.
  • public static void yield(): 礼让线程,让当前正在执行线程暂停并转入就绪状态.
  1. yield() 方法不会交出锁.
  2. 因为 yield() 方法使线程进入就绪状态,因此若调用了 yield() 方法后,没有其他等待执行的线程,当前线程就会马上恢复执行.

学习资源

多线程的学习主要是在 B 站上看的马士兵老师的 java 多线程高并发编程
https://www.bilibili.com/video/av33688545
博客总结参考评论区一位大佬的博客
https://blog.csdn.net/ncepu_Chen/article/details/94615325

  • 并发
    75 引用 • 73 回帖 • 1 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...