Callable接口类似于Runnable接口的增强版,call()方法为线程执行体;
call()方法与run方法的区别:
(1)call()方法可以有返回值;
(2)call方法可以声明抛出异常;
Java5开始提供了Future接口来代表Callable接口里call()方法的返回值,并提供了一个FutureTask实现类.
FutureTask还实现了Runnable接口,因此可以作为Thread的target创建线程.
创建并启动有返回值的线程步骤:
(1)创建Callable接口,实现call()方法作为线程执行体.该call()方法有返回值.创建Callable实现类的实例;
(2)使用FutureTask类来包装Callable对象.该FutureTask对象封装了该Callable对象的call()方法的返回值;
(3)使用FutureTask对象作为Thread的对象的target创建并启动新线程;
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值.
仍然以火车售票系统为例:
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class SellTicket {
public static void main(String[] args) throws InterruptedException, ExecutionException {
Ticket tickets = new Ticket(10);
TicketWindow tw = new TicketWindow(tickets);
for(int i=1; i<4; i++){
FutureTask<Void> ft = new FutureTask<Void>(tw);//注意,每个线程都需要new一个新的FutureTask,否则会出现只有一个窗口卖票
Thread t = new Thread(ft,"TickWindow-" + i);
t.start();
}
}
}
class TicketWindow implements Callable<Void>{
//private int tickets = 10;//车票总量
private Ticket tickets;
public TicketWindow(Ticket tickets){
this.tickets = tickets;
}
@Override
public Void call(){
while(true){
synchronized (tickets) {
int currentNo = tickets.getNum();
if(currentNo>0){
System.out.println(Thread.currentThread().getName() + "准备出票,剩余票数:" + currentNo + "张");
--currentNo;
tickets.setNum(currentNo);
System.out.println(Thread.currentThread().getName() + "卖出一张,剩余票数:" + currentNo + "张");
try {
Thread.sleep(500);//出票成功后让当前售票窗口睡眠,以便让其他售票窗口卖票
} catch (InterruptedException e) {
e.printStackTrace();
}
//Thread.yield();
}
else{
System.out.println(Thread.currentThread().getName() + "余票不足,停止售票!");
break;
}
}
}
return null;
}
}
class Ticket{
private int num;
public Ticket(int n){
this.num = n;
}
public void setNum(int n){
this.num = n;
}
public int getNum(){
return num;
}
}
输出:
TickWindow-1准备出票,剩余票数:10张
TickWindow-1卖出一张,剩余票数:9张
TickWindow-3准备出票,剩余票数:9张
TickWindow-3卖出一张,剩余票数:8张
TickWindow-3准备出票,剩余票数:8张
TickWindow-3卖出一张,剩余票数:7张
TickWindow-3准备出票,剩余票数:7张
TickWindow-3卖出一张,剩余票数:6张
TickWindow-3准备出票,剩余票数:6张
TickWindow-3卖出一张,剩余票数:5张
TickWindow-3准备出票,剩余票数:5张
TickWindow-3卖出一张,剩余票数:4张
TickWindow-3准备出票,剩余票数:4张
TickWindow-3卖出一张,剩余票数:3张
TickWindow-2准备出票,剩余票数:3张
TickWindow-2卖出一张,剩余票数:2张
TickWindow-2准备出票,剩余票数:2张
TickWindow-2卖出一张,剩余票数:1张
TickWindow-2准备出票,剩余票数:1张
TickWindow-2卖出一张,剩余票数:0张
TickWindow-2余票不足,停止售票!
TickWindow-3余票不足,停止售票!
TickWindow-1余票不足,停止售票!
对于实现了Callable接口的多线程类,还可以利用线程池方式提交任务.
主要步骤如下,
(1)通过Executors.newFixedThreadPool(int nThreads)等方法创建线程池对象;
(2)创建Callable接口的实现类,作为线程池对象的submit提交对象;
(3)使用Future对象来获得Callable对象的call()方法的返回值;
ExecutorService executorService = Executors.newFixedThreadPool(nThreads);
someClassImplimentCallable someClass = new someClassImplimentCallable();
Future<T> future = executorService.submit(someClass);
这种方式模拟的火车售票系统如下:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class SellTicket {
public static void main(String[] args) throws InterruptedException, ExecutionException {
Ticket tickets = new Ticket(10);
ExecutorService executorService = Executors.newFixedThreadPool(3);
for(int i=1; i<4; i++){
TicketWindow tw = new TicketWindow(tickets);
Future<Void> future = executorService.submit(tw);
}
executorService.shutdown();
}
}
class TicketWindow implements Callable<Void>{
//private int tickets = 10;//车票总量
private Ticket tickets;
public TicketWindow(Ticket tickets){
this.tickets = tickets;
}
@Override
public Void call(){
while(true){
synchronized (tickets) {
int currentNo = tickets.getNum();
if(currentNo>0){
System.out.println(Thread.currentThread().getName() + "准备出票,剩余票数:" + currentNo + "张");
--currentNo;
tickets.setNum(currentNo);
System.out.println(Thread.currentThread().getName() + "卖出一张,剩余票数:" + currentNo + "张");
try {
Thread.sleep(500);//出票成功后让当前售票窗口睡眠,以便让其他售票窗口卖票
} catch (InterruptedException e) {
e.printStackTrace();
}
//Thread.yield();
}
else{
System.out.println(Thread.currentThread().getName() + "余票不足,停止售票!");
break;
}
}
}
return null;
}
}
class Ticket{
private int num;
public Ticket(int n){
this.num = n;
}
public void setNum(int n){
this.num = n;
}
public int getNum(){
return num;
}
}
Java5开始通过显式定义同步锁对象来实现同步-Lock对象
接口 | Lock互斥锁 | ReadWriteLock 读写锁 |
实现类 | ReentrantLock 可重入锁 | ReentrantReadWriteLock |
JDK8新增 | StampedLock |
ReentrantLock较为常用,格式如下:
class Example{
//定义锁对象
private final ReentrantLock lock = new ReentrantLock();
//定义需要保证线程安全的方法
public void function(){
//加锁
lock.lock();
try{
//需要保证线程安全的代码;
}finally{
lock.unlock();
}
}
}
使用同步锁实现的多线程模拟火车售票系统:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.locks.ReentrantLock;
public class SellTicket {
public static void main(String[] args) throws InterruptedException, ExecutionException {
//方法1,创建Thread子类进行提交,结果是一个窗口卖票?
Ticket tickets = new Ticket(10);
TicketWindow tw = new TicketWindow(tickets);
for(int i=1; i<4; i++){
FutureTask<Void> ft = new FutureTask<Void>(tw);
Thread t = new Thread(ft,"TickWindow-" + i);
t.start();
}
}
}
class TicketWindow implements Callable<Void>{
//private int tickets = 10;//车票总量
private Ticket tickets;
private final ReentrantLock lock = new ReentrantLock();
public TicketWindow(Ticket tickets){
this.tickets = tickets;
}
@Override
public Void call(){
while(true){
lock.lock();
try{
int currentNo = tickets.getNum();
if(currentNo>0){
System.out.println(Thread.currentThread().getName() + "准备出票,剩余票数:" + currentNo + "张");
--currentNo;
tickets.setNum(currentNo);
System.out.println(Thread.currentThread().getName() + "卖出一张,剩余票数:" + currentNo + "张");
try {
Thread.sleep(500);//出票成功后让当前售票窗口睡眠,以便让其他售票窗口卖票
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else{
System.out.println(Thread.currentThread().getName() + "余票不足,停止售票!");
break;
}
}finally{
lock.unlock();
}
}
return null;
}
}
class Ticket{
private int num;
public Ticket(int n){
this.num = n;
}
public void setNum(int n){
this.num = n;
}
public int getNum(){
return num;
}
}
本文参考:
https://blog.csdn.net/pengych_321/article/details/50664749
https://my.oschina.net/u/566591/blog/1576410
http://blog.cnsyear.com/2018/03/12/1520865594911.html
https://www.cnblogs.com/dolphin0520/p/3949310.html
http://www.cnblogs.com/felixzh/p/6036074.html