目录

基本概念

进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1–n个线程。(进程是资源分配的最小单位)

线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位)

线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。

  • 多进程:操作系统能同时运行多个任务(程序)
  • 多线程:在同一程序中有多个顺序流在执行

在Java中实现多线程有三种方式:

  1. 继承Thread类
  2. 实现Runnable接口
  3. 实现Callable接口(与Future、线程池结合使用)

一、扩展java.lang.Thread类

这种方法比较常用,适用于简单的线程需求。

package com.multithread.learning;

/**
 * 多线程学习
 * @author 林炳文
 * @time 2015.3.9
 */
class Thread1 extends Thread{
    private String name;
    
    public Thread1(String name) {
        this.name = name;
    }
    
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(name + "运行 : " + i);
            try {
                sleep((int) Math.random() * 10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Thread1 mTh1 = new Thread1("A");
        Thread1 mTh2 = new Thread1("B");
        mTh1.start();
        mTh2.start();
    }
}

输出示例:

A运行 : 0
B运行 : 0
A运行 : 1
A运行 : 2
A运行 : 3
A运行 : 4
B运行 : 1
B运行 : 2
B运行 : 3
B运行 : 4

注意事项:

  • start()方法调用后线程变为可运行态,具体执行时间由操作系统决定
  • 多线程程序是乱序执行的
  • Thread.sleep()方法让出CPU资源给其他线程
  • 重复调用start()方法会抛出IllegalThreadStateException

二、实现java.lang.Runnable接口

推荐使用这种方式,更灵活且支持资源共享。

package com.multithread.runnable;

/**
 * 多线程学习
 * @author 林炳文
 * @time 2015.3.9
 */
class Thread2 implements Runnable{
    private String name;
    
    public Thread2(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(name + "运行 : " + i);
            try {
                Thread.sleep((int) Math.random() * 10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class Main {
    public static void main(String[] args) {
        new Thread(new Thread2("C")).start();
        new Thread(new Thread2("D")).start();
    }
}

三、Thread和Runnable的区别

实现Runnable接口的优势:

  1. 适合多个相同程序代码的线程处理同一资源
  2. 可以避免Java中的单继承限制
  3. 增加程序健壮性,代码可以被多个线程共享
  4. 线程池只能放入实现Runnable或Callable的线程

重要提醒:

  • main方法本身也是一个线程
  • 所有线程同时启动,执行顺序取决于CPU资源分配
  • 每次Java程序运行至少启动2个线程:main线程和垃圾收集线程

四、线程状态转换

start
获取CPU
wait
同步锁被占用
sleep/join/I/O
notify/notifyAll
获取锁
sleep结束/join结束/I/O完成
执行完毕/异常退出
新建状态 New
就绪状态 Runnable
运行状态 Running
等待阻塞
同步阻塞
其他阻塞
死亡状态 Dead

详细说明:

  1. 新建状态(New):新创建线程对象
  2. 就绪状态(Runnable):调用start()方法后,等待获取CPU
  3. 运行状态(Running):获取CPU,执行程序代码
  4. 阻塞状态(Blocked)
    • 等待阻塞:执行wait()方法,释放持有的锁
    • 同步阻塞:获取同步锁时被其他线程占用
    • 其他阻塞:执行sleep()join()或I/O请求
  5. 死亡状态(Dead):线程执行完毕或异常退出

五、线程调度

1. 调整线程优先级

// 优先级常量
Thread.MAX_PRIORITY    // 10
Thread.MIN_PRIORITY    // 1  
Thread.NORM_PRIORITY   // 5

// 设置优先级
thread.setPriority(Thread.MAX_PRIORITY);

2. 线程调度方法

  • sleep():线程睡眠,转为阻塞状态
  • wait():线程等待,直到其他线程调用notify()
  • yield():暂停当前线程,让出执行机会
  • join():等待其他线程终止
  • notify()/notifyAll():唤醒等待的线程

注意suspend()resume()方法在JDK1.5中已废除,因为有死锁倾向。

六、常用函数说明

① sleep(long millis)

在指定毫秒数内让当前线程休眠。

② join()

等待线程终止。

Thread t = new AThread();
t.start();
t.join(); // 等待t线程结束

使用场景:主线程需要等待子线程执行完成。

③ yield()

暂停当前线程,让出CPU给其他线程。

class ThreadYield extends Thread{
    public ThreadYield(String name) {
        super(name);
    }
    
    @Override
    public void run() {
        for (int i = 1; i <= 50; i++) {
            System.out.println("" + this.getName() + "-----" + i);
            if (i == 30) {
                this.yield(); // 让出CPU
            }
        }
    }
}

④ setPriority()

更改线程优先级。

⑤ interrupt()

向线程发送中断信号。

⑥ wait()和notify()

必须与synchronized一起使用。

经典示例:三线程交替打印ABC

public class MyThreadPrinter2 implements Runnable {
    private String name;
    private Object prev;
    private Object self;
    
    private MyThreadPrinter2(String name, Object prev, Object self) {
        this.name = name;
        this.prev = prev;
        this.self = self;
    }
    
    @Override
    public void run() {
        int count = 10;
        while (count > 0) {
            synchronized (prev) {
                synchronized (self) {
                    System.out.print(name);
                    count--;
                    self.notify();
                }
                try {
                    prev.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    public static void main(String[] args) throws Exception {
        Object a = new Object();
        Object b = new Object();
        Object c = new Object();
        
        MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, a);
        MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, b);
        MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, c);
        
        new Thread(pa).start();
        Thread.sleep(100);
        new Thread(pb).start();
        Thread.sleep(100);
        new Thread(pc).start();
        Thread.sleep(100);
    }
}

sleep()和wait()区别

特性 sleep() wait()
所属类 Thread Object
锁释放 不释放 释放
使用范围 任意位置 同步块内
异常处理 必须捕获 不需捕获

共同点

  • 都可以暂停线程指定时间
  • 都可以被interrupt()打断

七、常见线程名词解释

  • 主线程:JVM调用main()产生的线程
  • 当前线程:通过Thread.currentThread()获取的线程
  • 后台线程(守护线程):为其他线程提供服务,不阻止JVM退出
  • 前台线程:接受后台线程服务的线程

常用线程方法:

  • sleep():线程睡眠
  • isAlive():判断线程是否存活
  • join():等待线程终止
  • activeCount():活跃线程数
  • currentThread():获取当前线程
  • setDaemon():设置守护线程
  • setName():设置线程名称

八、线程同步

synchronized关键字作用域

  1. 对象实例内

    public synchronized void method() {
        // 同步方法
    }
    
  2. 类的范围

    public synchronized static void staticMethod() {
        // 同步静态方法
    }
    
  3. 同步代码块

    synchronized(this) {
        // 同步代码块
    }
    

重要原则

  • synchronized取得的锁都是对象,不是代码段
  • 每个对象只有一个锁
  • 同步会带来系统开销,可能造成死锁
  • 静态同步方法的锁是Class对象

同步示例

对象锁:

public void methodAAA() {
    synchronized(this) {  // 对象锁
        // 同步代码
    }
}

类锁:

public void methodBBB() {
    synchronized(Foo.class) {  // 类锁
        // 同步代码
    }
}

特殊实例变量作为锁:

class Foo implements Runnable {
    private byte[] lock = new byte[0];  // 特殊的instance变量
    
    public void methodA() {
        synchronized(lock) {
            // 同步代码
        }
    }
}

九、线程数据传递

9.1 通过构造方法传递数据

public class MyThread1 extends Thread {
    private String name;
    
    public MyThread1(String name) {
        this.name = name;
    }
    
    public void run() {
        System.out.println("hello " + name);
    }
    
    public static void main(String[] args) {
        Thread thread = new MyThread1("world");
        thread.start();
    }
}

9.2 通过变量和方法传递数据

public class MyThread2 implements Runnable {
    private String name;
    
    public void setName(String name) {
        this.name = name;
    }
    
    public void run() {
        System.out.println("hello " + name);
    }
    
    public static void main(String[] args) {
        MyThread2 myThread = new MyThread2();
        myThread.setName("world");
        Thread thread = new Thread(myThread);
        thread.start();
    }
}

9.3 通过回调函数传递数据

class Data {
    public int value = 0;
}

class Work {
    public void process(Data data, Integer... numbers) {
        for (int n : numbers) {
            data.value += n;
        }
    }
}

public class MyThread3 extends Thread {
    private Work work;
    
    public MyThread3(Work work) {
        this.work = work;
    }
    
    public void run() {
        java.util.Random random = new java.util.Random();
        Data data = new Data();
        int n1 = random.nextInt(1000);
        int n2 = random.nextInt(2000);
        int n3 = random.nextInt(3000);
        
        work.process(data, n1, n2, n3);  // 回调函数
        
        System.out.println(n1 + "+" + n2 + "+" + n3 + "=" + data.value);
    }
    
    public static void main(String[] args) {
        Thread thread = new MyThread3(new Work());
        thread.start();
    }
}
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐