多线程

Java提供了丰富的多线程支持,使开发者能够轻松创建和管理多线程应用程序。以下是Java多线程的一些重要概念和相关内容:

  1. 线程(Thread):
    线程是程序执行的最小单位,它代表了一个独立的执行路径。在Java中,线程是由Thread类表示的,可以通过创建Thread类的实例来创建和管理线程。

  2. 创建线程:
    有两种主要的方式来创建线程:

    • 继承Thread类:可以创建一个新的类,继承Thread类并覆盖其run()方法,在run()方法中定义线程的执行逻辑。
    • 实现Runnable接口:可以创建一个实现了Runnable接口的类,并实现其run()方法,然后将该实现类的实例传递给Thread类的构造函数创建线程。
  3. 启动线程:
    创建线程后,需要调用线程的start()方法来启动线程。start()方法会在新的线程中调用run()方法,并使线程进入可运行(Runnable)状态。

  4. 线程生命周期:
    线程生命周期包括以下几个状态:

    • 新建(New):线程被创建但尚未启动。
    • 可运行(Runnable):线程已经启动并可以运行。
    • 阻塞(Blocked):线程被阻塞,等待某些条件的发生。
    • 等待(Waiting):线程等待其他线程的特定操作。
    • 计时等待(Timed Waiting):线程等待一段指定的时间。
    • 终止(Terminated):线程执行完毕或被中断。
  5. 线程同步:
    多个线程访问共享资源时可能引发线程安全问题,如竞态条件和数据不一致。Java提供了多种机制来实现线程同步,包括:

    • synchronized关键字:通过在方法或代码块上加锁,确保同一时间只有一个线程可以进入被锁定的代码段。
    • ReentrantLock类:提供了显示的锁定和解锁机制,比synchronized关键字更灵活。
    • volatile关键字:用于保证变量的可见性,禁止指令重排序。
    • 同步容器类:Java提供了线程安全的容器类,如ConcurrentHashMap和ConcurrentLinkedQueue等。
  6. 线程间通信:
    多个线程之间可以通过以下机制进行通信:

    • wait()、notify()和notifyAll()方法:在synchronized代码块中使用,线程通过wait()方法等待通知,其他线程通过notify()或notifyAll()方法发送通知。
    • Lock和Condition接口:使用ReentrantLock类的条件变量来实现线程间的等待和通知机制。
    • 线程间共享变量:多个线程通过共享变量来进行通信,需要使用同步机制确保线程安全。
  7. 线程池(ThreadPool):
    线程池是一组预先创建的线程,用于执行任务并管理线程的生命周期。Java提供了ThreadPoolExecutor类来创建线程池,可以有效地管理线程的创建和销毁,提高应用程序的性能和资源利用率。

  8. 并发工具类:
    Java还提供了一些并发工具类来简化多线程编程,如CountDownLatch、CyclicBarrier、Semaphore和BlockingQueue等,用于控制线程的执行顺序、同步和通信。

通过合理地使用多线程,可以充分发挥多核处理器的优势,提高程序的并发性和性能。同时,也需要注意线程安全和避免常见的多线程问题,如死锁、活锁和资源竞争等。

以下是Java中Thread类常用的方法:

  1. 构造方法

    • Thread(): 创建一个新的线程对象。
    • Thread(Runnable target): 创建一个带有指定 Runnable 任务的线程对象。
    • Thread(String name): 创建一个新的线程对象,指定线程名称。
    • Thread(Runnable target, String name): 创建一个带有指定 Runnable 任务和名称的线程对象。
    • Thread(ThreadGroup group, Runnable target): 创建一个带有指定线程组和 Runnable 任务的线程对象。
    • Thread(ThreadGroup group, Runnable target, String name): 创建一个带有指定线程组、Runnable 任务和名称的线程对象。
  2. 线程状态控制方法

    • void start(): 启动线程,使其进入可运行状态。线程启动后会调用 run() 方法。
    • void run(): 线程的主体方法,需要自行实现。当线程处于可运行状态时,run() 方法会被调用。
    • void sleep(long millis) throws InterruptedException: 让线程休眠指定的毫秒数。在休眠期间,线程处于阻塞状态。
    • static void sleep(long millis, int nanos) throws InterruptedException: 让线程休眠指定的毫秒数和纳秒数。
    • void join() throws InterruptedException: 等待调用线程结束。在调用线程执行完毕前,当前线程会一直阻塞。
    • void join(long millis) throws InterruptedException: 最多等待指定毫秒数,然后继续执行。
    • void join(long millis, int nanos) throws InterruptedException: 最多等待指定毫秒数和纳秒数。
    • void interrupt(): 中断线程。可以通过检查线程的中断状态(使用 isInterrupted() 方法)来决定是否停止线程的执行。
  3. 线程信息查询方法

    • String getName(): 获取线程名称。
    • long getId(): 获取线程的唯一标识符。
    • Thread.State getState(): 获取线程的当前状态。
    • boolean isAlive(): 判断线程是否处于活动状态。
    • boolean isInterrupted(): 判断线程是否被中断,但不清除中断状态。
    • static Thread currentThread(): 获取当前正在执行的线程对象。
  4. 线程优先级控制方法

    • void setPriority(int priority): 设置线程的优先级,范围从 Thread.MIN_PRIORITYThread.MAX_PRIORITY
    • int getPriority(): 获取线程的优先级。
  5. 线程同步方法

    • static void yield(): 提示线程调度器当前线程愿意放弃当前对处理器的使用,以便其他线程有机会执行。
  6. 线程组相关方法

    • ThreadGroup getThreadGroup(): 获取线程所属的线程组。
    • static int activeCount(): 获取当前线程组及其子组中活动线程的总数。
  7. 其他方法

    • static void sleep(long millis) throws InterruptedException: 让当前线程休眠指定毫秒数。

这些方法是Thread类的常用方法,可以用于创建和管理线程,控制线程的执行顺序和状态。使用这些方法可以实现多线程编程中的线程控制、同步和通信等功能。

终端线程

1.t.interrupt()

try {
    Thread.sleep(100);
    hello.join();
    //当一个线程调用了一个阻塞方法(如 Thread.sleep()、Object.wait()、Thread.join() 等)或者等待某个锁时,它可能会进入等待或阻塞状态。如果此时其他线程调用了该线程的 interrupt() 方法,就会导致被阻塞的线程抛出 InterruptedException 异常。
} catch (InterruptedException e) {
    System.out.println("interrupted!");
    break;
}

2.共享变量

线程间共享的变量public volatile boolean running = true;

volatile关键字的目的是告诉虚拟机:

  • 每次访问变量时,总是获取主内存的最新值;
  • 每次修改变量后,立刻回写到主内存。
┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
           Main Memory
│                               │
   ┌───────┐┌───────┐┌───────┐
│  │ var A ││ var B ││ var C │  │
   └───────┘└───────┘└───────┘
│     │ ▲               │ ▲     │
 ─ ─ ─│─│─ ─ ─ ─ ─ ─ ─ ─│─│─ ─ ─
      │ │               │ │
┌ ─ ─ ┼ ┼ ─ ─ ┐   ┌ ─ ─ ┼ ┼ ─ ─ ┐
      ▼ │               ▼ │
│  ┌───────┐  │   │  ┌───────┐  │
   │ var A │         │ var C │
│  └───────┘  │   │  └───────┘  │
   Thread 1          Thread 2
└ ─ ─ ─ ─ ─ ─ ┘   └ ─ ─ ─ ─ ─ ─ ┘

如果我们去掉volatile关键字,运行上述程序,发现效果和带volatile差不多,这是因为在x86的架构下,JVM回写主内存的速度非常快,但是,换成ARM的架构,就会有显著的延迟。

守护线程

有一种线程的目的就是无限循环,例如,一个定时触发任务的线程

JVM退出时,不必关心守护线程是否已结束。

Thread t = new MyThread();
t.setDaemon(true);
t.start();

线程同步

多个线程同时读写共享变量->原子操作

┌───────┐    ┌───────┐
│Thread1│    │Thread2│
└───┬───┘    └───┬───┘
    │            │
    │ILOAD (100) │
    │            │ILOAD (100)
    │            │IADD
    │            │ISTORE (101)
    │IADD        │
    │ISTORE (101)│
    ▼            ▼
class Counter {
    public static final Object lock = new Object();
    public static int studentCount = 0;
    public static int teacherCount = 0;
}



synchronized(Counter.lock) { // 获取锁
    ...
} // 释放锁
  1. 操作需要进行同步,以避免多个线程同时修改相同的数据库记录或文件内容,引发冲突和数据错误。
  2. 线程间的通信和协作:当线程之间需要进行协作、通信或等待特定条件时,需要使用同步锁来确保线程的正确等待和唤醒,以避免竞态条件和不正确的执行顺序。
  3. 缓冲区或队列的操作:在多线程环境下使用缓冲区或队列时,需要使用同步锁来保护共享数据结构,以确保线程安全的插入、删除或读取操作。
  4. 单例模式:在多线程环境下使用单例模式时,需要对单例对象的创建和访问进行同步锁的保护,以避免多个线程同时创建多个实例或访问未完全初始化的实例。
  5. 跨对象的操作:当多个线程同时操作多个相关的对象时,需要使用同步锁来保护整个操作过程,以避免不一致的状态或并发修改引起的问题。

下面是一些常见的单条原子操作的语句,它们不需要额外的同步保护:

  1. 基本数据类型的赋值操作,如 intlongbooleanchar 等。
int x = 5; // 原子操作,不需要同步
  1. 引用类型的赋值操作,当引用变量的赋值操作是原子性的时候。
MyObject obj = new MyObject(); // 原子操作,不需要同步
  1. volatile 关键字修饰的变量的读取和写入操作。
volatile boolean flag = true; // 原子操作,不需要同步
  1. 对象的引用传递。
public void doSomething(MyObject obj) {
    // ...
}

在上述情况下,由于操作是原子的,不会出现线程安全问题,因此不需要额外的同步措施。

然而,需要注意的是,当多个原子操作组合成一个复合操作时,就需要考虑同步问题了。

同步方法

线程自己选择锁对象?不利于封装.把synchronized逻辑封装起来!

当一个线程进入同步方法时,它将锁定该方法所属对象,其他线程将被阻塞直到该线程执行完毕释放锁。

public synchronized void synchronizedMethod() {
    // 这里是需要同步的代码
}
//或者说
public class Counter {
    private int count = 0;

    public void add(int n) {
        synchronized(this) {
            count += n;
        }
    }

    public void dec(int n) {
        synchronized(this) {
            count -= n;
        }
    }

    public int get() {
        return count;
    }
}
var c1 = Counter();
var c2 = Counter();

// 对c1进行操作的线程:
new Thread(() -> {
    c1.add();
}).start();
new Thread(() -> {
    c1.dec();
}).start();

// 对c2进行操作的线程:
new Thread(() -> {
    c2.add();
}).start();
new Thread(() -> {
    c2.dec();
}).start();
public synchronized void add(int n) { // 锁住this
    count += n;
} // 解锁

in android(java)

关于Android的多线程,其实现方式有以下几种:

  1. Handler - 可以配合Message、Runnable使用,实现线程间通信和任务调度,比较适合用于UI线程与后台线程之间的通讯。
  2. Thread - 通过继承Thread类,实现run()方法,直接创建线程对象来实现多线程。
  3. AsyncTask - 异步任务,实现了多线程功能,同时方便与UI线程交互。
  4. Executor / ThreadPoolExecutor - 线程池,可以重用线程,避免频繁创建销毁。

所以Android多线程主要是通过Handler、Thread、AsyncTask、Executor这些方式来实现的。

Handler用来配合线程间消息传递和任务调度。

Thread通过直接继承Thread来创建后台线程。

AsyncTask方便做后台线程和UI线程的交互。

Executor/ThreadPoolExecutor用来管理线程池,重用线程。

这些方式各有优劣,可以根据实际需求选择合适的多线程实现方式。常见的是配合Handler + Thread/AsyncTask或者Executor + Runnable来实现多线程。


Comment
avatar
baixie-g
欢迎,阅读,点评
跟我走
Announcement
g的blog正在建设欢迎您
Recent Post
关于周更
关于周更
梦开始地方
梦开始地方
Info
Article :
4
Total Count :
2.1k
UV :
PV :
Last Push :