欢迎访问宙启技术站
智能推送

Java函数实现线程同步与异步操作

发布时间:2023-05-28 14:03:08

Java中线程同步与异步操作是非常重要的概念,它们可以用来控制Java程序并发访问和执行的顺序。在实际应用中,我们经常需要多个线程同时执行某个任务,或者要求某个线程在其它线程执行完之后再执行,这时就需要使用线程同步和异步操作。

一、线程同步操作

线程同步操作是指多个线程执行某个任务时,它们需要协同工作以便每个线程能够正确执行。这时我们需要通过锁和信号量来实现线程同步。Java中的锁一般是指synchronized关键字和ReentractLock类,而信号量则是指Semaphore类和CountDownLatch类。

1. synchronized关键字

synchronized关键字可以使一个线程在进入某段代码之前获取锁,从而保证多个线程不会同时访问该代码段,确保代码的执行顺序。synchronized可以用在方法和代码块上,下面是使用synchronized关键字的一个例子:

public synchronized void print(String msg) {

    System.out.println(msg);

}

上面的代码使用synchronized关键字修饰了方法print,这样当多个线程访问这个方法时,只有一个线程能够获得锁并且进入执行,而其它线程则需要等待直到执行完毕。使用synchronized关键字时需要注意锁的范围,以免锁定过宽或过窄导致性能不佳或死锁。

2. ReentractLock类

ReentractLock类是Java5中新增的一个类,它可以提供更加灵活的锁机制。与synchronized关键字不同,ReentractLock类提供了lock()和unlock()方法,我们需要手动获取和释放锁。下面是使用ReentractLock类实现线程同步的一个例子:

class BankAccount {

    private final Lock lock = new ReentrantLock();

    private double balance;

    public void deposit(double amount) {

        lock.lock();

        try {

            balance += amount;

        } finally {

            lock.unlock();

        }

    }

}

3. Semaphore类

Semaphore类可以用来控制同时访问特定资源的线程数量,例如线程池中的线程数目。Semaphore中两个最常用的方法是acquire()和release(),分别表示获取许可和释放许可。下面是使用Semaphore类实现线程同步的一个例子:

Semaphore s = new Semaphore(4);

for (int i = 0; i < 8; i++) {

    new Thread(() -> {

        try {

            s.acquire();

            // ...

        } finally {

            s.release();

        }

    }).start();

}

上面的代码中,创建了8个线程并通过Semaphore限制了同时执行的线程数量不超过4个。

4. CountDownLatch类

CountDownLatch类可以用来控制一个线程等待多个线程完成后再执行。CountDownLatch只有一个计数器,它通过await()和countDown()方法实现线程的等待和计数,线程完成任务后,调用countDown()方法减少计数器的值。

下面是使用CountDownLatch类实现线程同步的一个例子:

CountDownLatch startupLatch = new CountDownLatch(1);

CountDownLatch shutdownLatch = new CountDownLatch(2);

new Thread(() -> {

    System.out.println("Worker 1 starting");

    startupLatch.countDown();

    // do some work

    shutdownLatch.countDown();

    System.out.println("Worker 1 stopped");

}).start();

new Thread(() -> {

    System.out.println("Worker 2 starting");

    startupLatch.countDown();

    // do some work

    shutdownLatch.countDown();

    System.out.println("Worker 2 stopped");

}).start();

由于startupLatch计数器的值为1,两个线程都需要等待它减为0后才能开始执行。而shutdownLatch计数器的值为2,当两个线程完成任务后,计数器的值减少为0,此时主线程就可以继续执行了。

二、线程异步操作

线程异步操作是指多个线程执行某个任务时,它们不需要协同工作,而是独立执行,并且可以通过回调等机制获取执行结果。Java中常用的异步编程方式有以下几种:

1. FutureTask

FutureTask是一个包装了Callable的类,它可以异步执行Callable,并在完成后返回结果或抛出异常。FutureTask提供了get()方法,它会阻塞当前线程直到任务完成,然后返回执行结果或抛出异常。

下面是使用FutureTask实现线程异步操作的一个例子:

FutureTask<Integer> task = new FutureTask<>(() -> {

    // do some work

    return 42;

});

new Thread(task).start();

// do some work

int result = task.get();

注意:FutureTask默认使用线程池中的一个线程执行Callable,如果需要手动执行,可以通过FutureTask.cancel(false)取消异步操作。

2. CompletableFuture

CompletableFuture是Java8新增的一种异步编程方式,它可以在任务完成时执行某个操作,或者将多个任务的执行结果合并后再执行某个操作。CompletableFuture提供了thenApply()、thenCombine()、thenAccept()等方法来实现异步操作。

下面是使用CompletableFuture实现线程异步操作的一个例子:

CompletableFuture.supplyAsync(() -> {

    // do some work

    return 42;

}).thenAccept(result -> {

    // do some work with the result

});

使用supplyAsync()方法可以异步执行任务,并返回一个CompletableFuture对象。而thenAccept()方法则表示在任务完成后执行某个操作。

3. 回调函数

回调函数是一种在异步编程中常用的机制,它可以在任务完成后通知调用方执行某个操作,例如将执行结果传递给回调函数。

下面是使用回调函数实现线程异步操作的一个例子:

public interface Callback {

    void onResult(int result);

}

public void asyncCalculate(Callback callback) {

    new Thread(() -> {

        // do some work

        callback.onResult(42);

    }).start();

}

调用方需要实现Callback接口,并将回调函数传递给asyncCalculate()方法。当任务完成后,线程会执行回调函数的onResult()方法。

总结:

Java中的线程同步和异步操作可以有效地控制并发访问和执行的顺序,从而提高程序的性能和效率。线程同步可以通过锁和信号量来实现,而线程异步可以通过FutureTask、CompletableFuture等机制来实现。在实际应用中,需要根据具体的场景和需求来选择合适的同步或异步操作方式。