译 -- Java 并发编程(多线程)一 | Callable and Future | CountDownLatch | Runable

Introduction

并行计算是一种计算的形式, 一些计算被并行的执行而不是串行。 Java 语言通过线程的使用被设计为支持并发编程。对象和资源能够被多线程访问, 每个线程能够在程序中潜在的访问任何对象, 并且程序设计者在多线程中必须确保读和写的对象访问被正确的同步。

Examples


Callable and Future

虽然Runnable 提供了一种包裹要被在一个不同的线程中执行的代码的方式。它有一个缺陷, 不能从执行中返回结果。仅有的一种从一个Runnable 的执行中返回值的方式是把结果赋值给一个在Runnable 外部作用域中可访问的变量。

Callable 在java 5中被引入, 作为Runnable 的一个对等(peer)。 Callable除了有一个Call 方法而不是Run 方法外(和Runnable)本质相同。 Call 方法有其它的能力去返回一个结果并且允许抛出检查时异常。

Callable 任务提交所产生的结果可用后能够被Future 获取

Future 可以被看作一个整理存储Callable 计算结果的容器。callable的计算能持续在另一个线程中, 并且任何获取一个Future 结果的尝试都将被阻塞,并且一旦(结果)变得可用,便会返回结果。

Callable 接口
public interface Callable<V> {
    V call() throws Exception;
}
Future
interface Future<V> {
    V get();
    V get(long timeout, TimeUnit unit);
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
}
使用CallableFuture 示例
public static void main(String[] args) throws Exception {
    ExecutorService es = Executors.newSingleThreadExecutor();

    System.out.println("Time At Task Submission : " + new Date());
    Future<String> result = es.submit(new ComplexCalculator());
    // the call to Future.get() blocks until the result is available.So we are in for about a 10 sec wait now
    //对Future.get()的调用将被阻塞,直到结果变得可用, 所以我们将要有10秒的等待
    System.out.println("Result of Complex Calculation is : " + result.get());
    System.out.println("Time At the Point of Printing the Result : " + new Date());
}
我们的Callable 做了一个漫长的计算
public class ComplexCalculator implements Callable<String> {

    @Override
    public String call() throws Exception {
        // just sleep for 10 secs to simulate a lengthy computation
        Thread.sleep(10000);            
        System.out.println("Result after a lengthy 10sec calculation");
        return "Complex Result"; // the result 
    }
}
输出
Time At Task Submission : Thu Aug 04 15:05:15 EDT 2016
Result after a lengthy 10sec calculation
Result of Complex Calculation is : Complex Result
Time At the Point of Printing the Result : Thu Aug 04 15:05:25 EDT 2016
其它在Future 上被允许的操作

当然get() 是一个从Future 提供者中抽取实际结果的方法。

  • get(long timeout, TimeUnit unit) 定义了等待在当前线程中等待结果的最大时间
  • 调用cancel(mayInterruptIfRunning) 取消任务, 标记mayInterrupt表示如果任务已经开始并且此刻正在运行,那么应该被中断。
  • 通过调用isDone(); 检查一个任务是否已经被完成
  • 通过调用isCancelled(); 检查任务是否被取消

    基本的多线程

    如果你有很多任务要去执行, 并且所有的任务并不依赖前一个的执行结果, 如果你的计算机允许, 你可以使用多线程使用多个处理器同时处理所有的工作。这能够使你的程序执行的更快,如果你有一些大的非互相依赖的任务。

class CountAndPrint implements Runnable {

    private final String name;

    CountAndPrint(String name) {
        this.name = name;
    }

    /** This is what a CountAndPrint will do */
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            System.out.println(this.name + ": " + i);
        }
    }

    public static void main(String[] args) {
        // Launching 3 parallel threads
        for (int i = 1; i <= 3; i++) {
            // `start` method will call the `run` method 
            // of CountAndPrint in another thread
            new Thread(new CountAndPrint("Instance " + i)).start();
        }

        // Doing some others tasks in the main Thread
        for (int i = 0; i < 10000; i++) {
            System.out.println("main: " + i);
        }
    }
}

各个CountAndPrint 实例中的run方法中的代码将会以一种不可预期的次序执行。一个样例执行片段可能看起来如下:

Instance 4: 1
Instance 2: 1
Instance 4: 2
Instance 1: 1
Instance 1: 2
Main: 1
Instance 4: 3
Main: 2
Instance 3: 1
Instance 4: 4
...

CountDownLatch

一个同步助手 允许一个或多个线程等待直到一组操作在其它的线程中被执行完。

 1. 一个`CountDownLatch ` 以一个给定的计数 进行初始化
 2. `await` 方法阻塞直到由于`countDown()` 方法的调用,当前计数器到达0, 之后所有的线程被放行并且后续的`await` 调用也会很快被返回。
 3. 这是只会出现一次的现象——这个计数器count不能够被重置。如果你需要一个能够重置计数器count的版本, 考虑使用`CyclicBarrier` 。

关键方法:

public void await() throws InterruptedException

造成当前线程等待直到latch减为0, 除非线程被中断

public void countDown()

latch的减少, 释放所有的等待线程如果计数到达0
示例:

import java.util.concurrent.*;

class DoSomethingInAThread implements Runnable {
    CountDownLatch latch;
    public DoSomethingInAThread(CountDownLatch latch) {
        this.latch = latch;
    } 
    public void run() {
        try {
            System.out.println("Do some thing");
            latch.countDown();
        } catch(Exception err) {
            err.printStackTrace();
        }
    }
}

public class CountDownLatchDemo {
    public static void main(String[] args) {
        try {
            int numberOfThreads = 5;
            if (args.length < 1) {
                System.out.println("Usage: java CountDownLatchDemo numberOfThreads");
                return;
            }
            try {
                numberOfThreads = Integer.parseInt(args[0]);
            } catch(NumberFormatException ne) {

            }
            CountDownLatch latch = new CountDownLatch(numberOfThreads);
            for (int n = 0; n < numberOfThreads; n++) {
                Thread t = new Thread(new DoSomethingInAThread(latch));
                t.start();
            }
            latch.await();
            System.out.println("In Main thread after completion of " + numberOfThreads + " threads");
        } catch(Exception err) {
            err.printStackTrace();
        }
    }
}

输出:

java CountDownLatchDemo 5
Do some thing
Do some thing
Do some thing
Do some thing
Do some thing
In Main thread after completion of 5 threads

解释:

  1. CountDownLatch 以一个为5的计数器在Main线程中初始化
  2. Main 线程通过调用await() 方法处于等待状态
  3. 五个DoSomethingInAThread 实例被创建, 每一个实例通过countDown() 方法减少计数器counter。
  4. 一旦计数器counter 变成0, Main线程将会重新开始执行

    Locks as Synchronisation aids

    Java 5 之前的cocurrent 包中引入的线程非常低级。之后的包中引入提供了一些更加高级的并发编程工具/设计
    锁是和同步块以及关键词本质上相同有相同目的的线程同步机制。
    锁的本质

int count = 0; // shared among multiple threads

public void doSomething() {
    synchronized(this) {
        ++count; // a non-atomic operation
    }
}

使用锁来同步

int count = 0; // shared among multiple threads

Lock lockObj = new Lock();
public void doSomething() {
    try {
        lockObj.lock()
        ++count; // a non-atomic operation
    } finally {    
        lockObj.unlock(); // sure to release the lock without fail
    }
}

有一些锁变量的变体, 更详细参考api文档here

Runnable 对象

Runnable 接口定义了单个方法, run(), 包含了在线程中被执行的代码。
Runnable 对象被传递到Thread 构造器里, 然后线程的start() 方法被调用
示例

public class HelloRunnable implements Runnable {

    @Override
    public void run() {
        System.out.println("Hello from a thread");
    }

    public static void main(String[] args) {
        new Thread(new HelloRunnable()).start();
    }
}

在java 8 示例

public static void main(String[] args) {
    Runnable r = () -> System.out.println("Hello world");
    new Thread(r).start();
}

Runnable vs Thread subclass
Runnable 对象的使用更为常见, 因为Runable 对象能够成为一个类的子类而不是Thread 的。
Thread 子类在一个简单的应用中更容易使用, 但是受限于你的任务类必须是Thread 类的子类的事实。
一个Runnable 对象适合更高级的线程管理APIs。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值