Skip to content

简介

是指从软件或者硬件上实现多个线程并发执行的技术

具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,提升性能

简单来说就比如你正在听歌,但是你想要在听歌的同时发布评论并且听歌不受影响,这就是多线程了,一共两个线程,一条线程供你听歌不干其他的事情就光顾着你听歌就行,而另一条线程则是供你发布评论,两条线程同时工作,但又互不影响

概念

并发和并行

  • 并行:在同一时刻,有多个指令在多个CPU上同时执行

    一共三件事情,同时执行是为并行

  • 并发:在同一时刻,有多个指令在单个CPU上交替执行

    还是三个事情,在第一个事情上搞一会,然后在第二个事情上在在搞一会,然后跑到第三个事情上,来回在这三个事情上搞是为并发

进程和线程

  • 进程:正在运行的软件
    1. 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位
    2. 动态性:进程的实质是程序的一次执行过程,进程是动态产生的,动态消亡的
    3. 并发性:任何进程都可以和其他的进程一起并发执行
  • 线程:是进程中的单个顺序控制流,是一条执行的路径
    1. 单线程:一个进程如果只有一条执行的路径,则被称为单线程程序
    2. 多线程:一个进程如果有多个执行路径,则被成为多线程程序

多线程实现

一个有三种实现方式:

  1. 继承Thread类的方式进行实现
  2. 实现Runnable接口的方式进行实现
  3. 利用CallableFuture接口的方式实现

继承Thread类

  1. 定义一个类继承Thread
  2. 在类中重写run()方法
  3. 创建类的对象,启动线程

单线程

java
public class Main {
    public static void main(String[] args) {
        Run r = new Run();
        r.start();
    }
}

class Run extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
        }
    }
}

run方法里面写的内容就是线程开启之后要执行的事情

因为只有一条线程也可以算是单线程

多线程

java
public class Main {
    public static void main(String[] args) {
        // 创建第一条线程
        Run r1 = new Run();
        // 创建第二条线程
        Run r2 = new Run();
        // 开启第一条线程
        r1.start();
        // 开启第二条线程
        r2.start();
    }
}

class Run extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
        }
    }
}

因为线程的执行是CPU来决定的,所以第一次运行和第二次运行的结果可能不一样,线程之间的执行有一个随机性

小问题

run和stage方法有什么区别

run方法这个用来重写是因为里面的内容是线程要执行的内容所以要重写

run和stage方法有什么区别

run:调用run方法是就跟调用普通方法一样,当执行完成第一个方法之后在进行执行第二条——并没有开启线程

stage:启动线程,然后由虚拟机调用run方法

实现Runnable接口

  1. 定义一个类实现Runnable接口
  2. 在类中重写run方法
  3. 创建类的对象
  4. 创建Thread类的对象,把之前创建的类对象作为构造方法的参数,启动线程

单线程

java
public class Main {
    public static void main(String[] args) {
        // 创建了参数对象
        Run r = new Run();
        // 创建线程对象,把参数传递给线程
        Thread thread = new Thread(r);
        thread.start();
    }
}

class Run implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
        }
    }
}

多线程

java
public class Main {
    public static void main(String[] args) {
        Run r1 = new Run();
        Thread thread1 = new Thread(r1);
        thread1.start();

        Run r2 = new Run();
        Thread thread2 = new Thread(r2);
        thread2.start();
    }
}

class Run implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
        }
    }
}

Callable和Future

  1. 定义一个类并实现Callable
  2. 在类中重写call()方法
  3. 创建类对象
  4. 创建Future的实现类FutureTask对象,把类对象作为构造方法的参数
  5. 创建Thread类的对象,把FutureTask对象作为构造方法的参数
  6. 启动线程

单线程

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

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 线程开启之后需要执行的call方法
        Run r = new Run();
        // 可以获取线程执行的结果,也可以作为参数传递给Thread
        FutureTask<String> ft = new FutureTask<>(r);
        // 创建线程对象
        Thread thread = new Thread(ft);
        // 开启线程
        thread.start();
        // 获取线程执行结束返回的值
        System.out.println(ft.get());
    }
}

class Run implements Callable<String> {
    @Override
    public String call() throws Exception {

        for (int i = 0; i < 100; i++) {
            System.out.println(i);
        }
        // 返回值表示线程运行完毕之后的结果
        return "执行完毕";
    }
}

注意:ft.get()不可在thread.start()之前运行

多线程

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

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Run r1 = new Run();
        FutureTask<String> ft1 = new FutureTask<>(r1);
        Thread thread1 = new Thread(ft1);

        Run r2 = new Run();
        FutureTask<String> ft2 = new FutureTask<>(r2);
        Thread thread2 = new Thread(ft2);

        thread1.start();
        thread2.start();
        System.out.println(ft1.get());
        System.out.println(ft2.get());
    }
}

class Run implements Callable<String> {
    @Override
    public String call() throws Exception {

        for (int i = 0; i < 100; i++) {
            System.out.println(i);
        }
        // 返回值表示线程运行完毕之后的结果
        return "执行完毕";
    }
}

三种实现方式

类型优点缺点
实现类扩展性强,实现该接口的同时开可以继承其他的类变成相对复杂,不能直接使用Thread类中的方法
继承类编程比较简单,可以直接使用Thread类中的方法可扩展性较差,不能在继承其他的类

线程状态

创建状态 --> (就绪状态 <--> 运行状态 <--> 阻塞状态) --> 死亡状态

其中括号中间三种状态是随机变动的

获取线程状态

Thread.getState():获取线程当前的状态

NEW:新建后的状态

RUNNABLE:执行中

BLOCKED:阻塞

WAITING:等待另一个线程执行某个特定的动作

TIMED_WAITING:等待另一个线程执行某个特定的动作达到指定等待时间的状态

TERMINATED:线程死亡进入此状态,线程死亡后不可再启动

线程停止

java
public class Test implements Runnable {
    // 标识
    private boolean flag = true;

    // 重写 run 方法
    @Override
    public void run() {
        int i = 0;
        while (flag) {
            System.out.println(i++);
        }
    }

    // 停止方法
    public void stop() {
        this.flag = false;
        System.out.println("线程已停止");
    }

    public static void main(String[] args) {
        Test test = new Test();
        // 开启线程
        new Thread(test).start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("main" + i);
            if (i == 999) {
                test.stop();
            }
        }
    }
}

线程休眠 sleep

sleep:指定当前线程阻塞的毫秒(1000ms = 1s)数,会存在异常(interruptedException)

每一个对象都有一把锁,sleep不会释放锁

线程强制执行 join

join:待此线程执行完成后,在执行其余线程,其他线程阻塞

java
public class Test implements Runnable {
    // 标识
    private boolean flag = true;

    // 重写 run 方法
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    // 停止方法
    public void stop() {
        this.flag = false;
        System.out.println("线程已停止");
    }

    public static void main(String[] args) throws InterruptedException {
        Test test = new Test();
        // 开启线程
        Thread thread = new Thread(test);
        thread.start();

        for (int i = 0; i < 500; i++) {
            System.out.println("main" + i);
            if (i == 250) {
                thread.join();
            }
        }
    }
}

上面代码的意思就是两条线程同时开启,如果主线程执行太快,那么等到主线程执行到250次的时候阻塞,等另一个线程执行完毕之后,主线程继续执行

线程优先级

java提供一个线程调度器来监控有哪些线程进入就绪状态,然后会按照优先级来决定应该调度哪个线程

线程优先级使用数字比较,范围 1 ~ 10

  1. Thread.MIN_PRIORITY = 1
  2. Thread.NORM_PRIORITY = 5
  3. Thread.MAX_PRIORITY = 10

方法:

  • getPriority():获取优先级
  • setPriority(int):改变优先级

优先级设定建议设置再调度之前

优先级低并不代表不会调用

守护线程

线程分为用户线程和守护线程,虚拟机必须确保用户线程执行完毕,但是不必确保守护线程执行完毕

开启方法:线程.setDaemon(true)默认是false

线程同步

使用场景:多个线程访问同一个对象,并且还要修改这些对象

由于共享同一块存储空间,有访问冲突的问题,所以加入锁机制(synchronized),等这个线程使用完之后,其余线程才能使用

同步方法&同步代码块

synchronized:被修饰的方法称为同步方法,每个对象都对应一把锁,每个方法只有获取调用该方法的对象锁才能执行,否则会线程阻塞,一旦执行就会独占锁,直到该方法返回才释放锁

缺陷:影响效率

synchronized(Obj){}:同步块

Obj是同步监视器,它可以是任何对象,但是推荐使用共享资源

synchronized锁

  1. 在同步代码块中,使用的锁是由自己选择的
  2. 在同步成员方法中,使用的锁是调用者本身
  3. 在同步静态方法中,使用的锁是静态方法所属的类的class对象

Lock锁

java
ReentrantLock lock = new ReentrantLock();
lock.lock(); // 上锁
lock.unlock(); // 去锁