Java并发编程(四):Callable接口与Lock锁

Callable接口与Lock锁

Callable接口

和Runnable有什么区别?**

  • 方法可以有返回值并且能抛出异常
  • 使用时需要FutureTask实现类支持,用于接收运算结果

可以通过FutureTask来获得线程执行完的最终结果

直接看下面一段代码

import java.util.concurrent.*;

public class TestCallable {
    public static void main(String[] args)
    {
        ThreadDemo td=  new ThreadDemo();

        //用 FutureTask 接收结果
        FutureTask<Integer> result = new FutureTask<>(td);

        new Thread(result).start();

        //接收运算结果
        try {
            Integer sum = result.get();
            System.out.println("1-100的和为:"+ sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

class ThreadDemo implements Callable<Integer>
{
    @Override
    public Integer call() throws Exception {
        int sum =0;
        for (int i=1;i<101;i++)
        {
            System.out.println(i);
            sum+=i;
        }

        return sum;
    }
}

代码逻辑:

  • 起一个callable线程,计算1-100的和
  • 在主方法里用FutureTask来接收最终计算的结果

实际执行效果:

打印了1-100所有数字,并输出了5050这个和

发生了什么?

联想上一章写的闭锁,发现此时用FutureTask也可达到闭锁的效果

Lock锁

解决多线程安全问题的方式:

隐式锁:

  • 同步代码块
  • 同步方法

显式锁:通过lock()进行上锁,unlock()释放锁

  • 同步锁

Lock锁的使用

来看一个经典多线程的卖票代码

public class TestLock {
    public static void main(String[] args)
    {
        Ticket ticket = new Ticket();
        new Thread(ticket,"1号窗口").start();
        new Thread(ticket,"2号窗口").start();
        new Thread(ticket,"3号窗口").start();

    }
}
class Ticket implements Runnable{

    private  int tick= 100;
    @Override
    public void run() {
        while (true)
        {
            if (tick >0)
            {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"完成售票,余票为:"+ --tick);
            }
        }

    }
}

代码逻辑:

  • 总票数100张,开三个线程售卖这100张票
  • 加入Thread.sleep(200);放大多线程的冲突

实际执行结果:

截取输出结果的一部分:

1号窗口完成售票,余票为:2
3号窗口完成售票,余票为:2
2号窗口完成售票,余票为:1
3号窗口完成售票,余票为:0
1号窗口完成售票,余票为:-1
2号窗口完成售票,余票为:-2

可以看到不仅有重复售票,还出现了减到零以下的现象

发生了什么?

很明显的并发冲突问题

怎么解决?

现在采用Lock锁来解决

看具体代码

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestLock {
    public static void main(String[] args)
    {
        Ticket ticket = new Ticket();
        new Thread(ticket,"1号窗口").start();
        new Thread(ticket,"2号窗口").start();
        new Thread(ticket,"3号窗口").start();

    }
}
class Ticket implements Runnable{

    private  int tick= 100;

    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true)
        {
            //上锁
            lock.lock();
            try{
                if (tick >0)
                {
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"完成售票,余票为:"+ --tick);
                }
            }
            //无论如何不能忘记在finally中释放锁
            finally {
                lock.unlock();
            }

        }

    }
}

注意在循环中上锁后,用try和finally包裹住了具体操作,利用finally无论如何都会执行的特点,将锁释放

修改后的执行结果

3号窗口完成售票,余票为:8
3号窗口完成售票,余票为:7
3号窗口完成售票,余票为:6
2号窗口完成售票,余票为:5
2号窗口完成售票,余票为:4
2号窗口完成售票,余票为:3
2号窗口完成售票,余票为:2
2号窗口完成售票,余票为:1
2号窗口完成售票,余票为:0

可以看到解决了并发问题

原创文章 40 获赞 16 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_43925277/article/details/105127089