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
可以看到解决了并发问题