Callable接⼝

Callable是⼀个interface.相当于把线程封装了⼀个"返回值".⽅便程序猿借助多线程的⽅式计算结 果.

代码⽰例:创建线程计算1+2+3+...+1000,不使⽤Callable版本

• 创建⼀个类Result,包含⼀个sum表⽰最终结果,lock表⽰线程同步使⽤的锁对象.

• main⽅法中先创建Result实例,然后创建⼀个线程t.在线程内部计算1+2+3+...+1000.

• 主线程同时使⽤wait等待线程t计算结束.(注意,如果执⾏到wait之前,线程t已经计算完了,就不 必等待了).

• 当线程t计算完毕后,通过notify唤醒主线程,主线程再打印结果.

static class Result {
     public int sum = 0;
     public Object lock = new Object();
}


public static void main(String[] args) throws InterruptedException {
    Result result = new Result();

    Thread t = new Thread() {
       @Override
       public void run() {
         int sum = 0;
         for (int i = 1; i <= 1000; i++) {
            sum += i;
        }
        synchronized (result.lock) {
             result.sum = sum;


             result.lock.notify();
           }
        }
     };
     t.start();


     synchronized (result.lock) {
         while (result.sum == 0) {
            result.lock.wait();
         }
         System.out.println(result.sum);
     }
}

可以看到,上述代码需要⼀个辅助类Result,还需要使⽤⼀系列的加锁和waitnotify操作,代码复杂, 容易出错.

代码⽰例:创建线程计算1+2+3+...+1000,使⽤Callable版本

• 创建⼀个匿名内部类,实现Callable接⼝.Callable带有泛型参数.泛型参数表⽰返回值的类型.

• 重写Callable的call⽅法,完成累加的过程.直接通过返回值返回计算结果.

• 把callable实例使⽤FutureTask包装⼀下.

• 创建线程,线程的构造⽅法传⼊FutureTask.此时新线程就会执⾏FutureTask内部的Callable的 call⽅法,完成计算.计算结果就放到了FutureTask对象中.

• 在主线程中调⽤futureTask.get() 能够阻塞等待新线程计算完毕.并获取到FutureTask中的 结果.

Callable<Integer> callable = new Callable<Integer>() {
     @Override
     public Integer call() throws Exception {
         int sum = 0;
         for (int i = 1; i <= 1000; i++) {
            sum += i;
         }
       return sum;
     }
};

FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread t = new Thread(futureTask);
t.start();


int result = futureTask.get();
System.out.println(result);

可以看到,使⽤Callable和FutureTask之后,代码简化了很多,也不必⼿动写线程同步代码了.

理解Callable

Callable和Runnable相对,都是描述⼀个"任务".Callable描述的是带有返回值的任务,Runnable 描述的是不带返回值的任务. Callable通常需要搭配FutureTask来使⽤.FutureTask⽤来保存Callable的返回结果.因为 Callable往往是在另⼀个线程中执⾏的,啥时候执⾏完并不确定. FutureTask就可以负责这个等待结果出来的⼯作.

理解FutureTask

想象去吃⿇辣烫.当餐点好后,后厨就开始做了.同时前台会给你⼀张"⼩票".这个⼩票就是 FutureTask.后⾯我们可以随时凭这张⼩票去查看⾃⼰的这份⿇辣烫做出来了没.

ReentrantLock

可重⼊互斥锁.和synchronized定位类似,都是⽤来实现互斥效果,保证线程安全.     ReentrantLock也是可重⼊锁.ReentrantLock的⽤法:

• lock():加锁,如果获取不到锁就死等.

• trylock(超时时间):加锁,如果获取不到锁,等待⼀定的时间之后就放弃加锁.

• unlock():解锁

ReentrantLock lock = new ReentrantLock(); 
-----------------------------------------
lock.lock(); 
try { 
   // working 
} finally { 
      lock.unlock() 
} 

ReentrantLock和synchronized的区别:

• synchronized是⼀个关键字,是JVM内部实现的(⼤概率是基于C++实现).ReentrantLock是标准 库的⼀个类,在JVM外实现的(基于Java实现).

• synchronized使⽤时不需要⼿动释放锁.ReentrantLock使⽤时需要⼿动释放.使⽤起来更灵活,但 是也容易遗漏unlock.

• synchronized在申请锁失败时,会死等.ReentrantLock可以通过trylock的⽅式等待⼀段时间就放 弃.

• synchronized是⾮公平锁,ReentrantLock默认是⾮公平锁.可以通过构造⽅法传⼊⼀个true开启 公平锁模式.

// ReentrantLock 的构造⽅法 
public ReentrantLock(boolean fair) {
 sync = fair ? new FairSync() : new NonfairSync();
}

• 更强⼤的唤醒机制.synchronized是通过Object的wait/notify实现等待-唤醒.每次唤醒的是⼀个 随机等待的线程.ReentrantLock搭配Condition类实现等待-唤醒,可以更精确控制唤醒某个指定 的线程.

 如何选择使⽤哪个锁?

• 锁竞争不激烈的时候,使⽤synchronized,效率更⾼,⾃动释放更⽅便.

• 锁竞争激烈的时候,使⽤ReentrantLock,搭配trylock更灵活控制加锁的⾏为,⽽不是死等.

• 如果需要使⽤公平锁,使⽤ReentrantLock.

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐