暑假闲得无聊,便读了疯狂java讲义的第16章补充了一下多线程的知识顺便做了一下书后习题练练,把解答和感想一并发到网上与大家讨论。
习题1.写2个线程,其中一个线程打印1~52,另一个线程打印A~Z,打印顺序应该是12A34B56C……5152Z。该习题需要利用多线程通信的知识。
思路:按照题目的意思是要每隔两个数字打印一个字母,需要用到进程通信,我们这里先建立一个专门用来充当同步监听器的类PrintMonitor,里面有一个同步方法printAll用于打印数组,每隔一定的间隔便通知其他线程苏醒(notifyAll)然后自己放弃同步监听器(wait),两个打印线程我打算使用同一个线程类PrintThread,所以PrintThread类需要有属性monitor(同步监听器),arr(需要打印的数组),interval(打印间隔)三个属性方便main方法向其传参,在main方法中先让打印数字的线程开始,之后让主线程休眠1毫秒,之后再开启打印字母的线程,目的是为了确保打印数字的线程先开始,如果不休眠的话经过测试,有的时候会导致打印字母的线程先开始.
源代码如下:
以下类文件在同一包下,主函数为Main类的main()方法
public class PrintMonitor {
public synchronized void printAll(String[] arr, int interval){
for ( int i = 0; i < arr.length; i++){
System.out.print(arr[i]);
if ( (i + 1) % interval == 0 ){
this.notifyAll();
myWait();
}
}
}
//出于方便将wait方法封装了一下
private void myWait(){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class PrintThread extends Thread{
PrintMonitor monitor;
String[] arr;
int interval;
public PrintThread(PrintMonitor monitor, String[] arr, int interval) {
this.monitor = monitor;
this.arr = arr;
this.interval = interval;
}
@Override
public void run() {
monitor.printAll(arr, interval);
}
}
public class Main {
public static void main(String[] args) {
//数字数组
String arr1[] = new String[52];
//字母数组
String arr2[] = new String[26];
for (int i = 1; i < 53; i++) {
arr1[i - 1] = i + "";
}
for (char i = 65; i < 65 + arr2.length; i++){
arr2[i - 65] = i + "";
}
PrintMonitor pm = new PrintMonitor();
PrintThread t1 = new PrintThread(pm, arr1, 2);
PrintThread t2 = new PrintThread(pm, arr2, 1);
t1.start();
//为了确保t1在t2之前开始运行
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
反复运行结果:
12A34B56C78D910E1112F1314G1516H1718I1920J2122K2324L2526M2728N2930O3132P3334Q3536R3738S3940T4142U4344V4546W4748X4950Y5152Z
解题反思:
习题2.假设车库有3个车位(可以用boolean[]数组来表示车库)可以停车,写一个程序模拟多个用户开车离开、停车入库的效果。注意:车库有车时不能停车
解法一:
思路:与上一题类似,建立Park类表示停车场,作为同步监听器,属性里面有一个三元的boolean数组作为成员变量表示停车位(true表示有车,false表示无车),有两个同步方法,park()表示停车,反复检查车库是否已满,如果检测出车库已满则放弃同步监听器(wait),如果不满则停车,leave()表示驶离停车场,将车驶离后,唤醒其他所有线程争抢停车位(notifyAll).创建User继承Thread类用于表示用户,用户先进行停车,休眠两秒后驶离,反复十次,这样模拟用户的行为。为了验证程序的正确与否,在Park类中我还添加了int类型的parkTotal域和leaveTotal域,对于停车场进入停车与驶离的总数进行计数,等到所有的User线程运行结束之后(将所有的User线程join进主线程),打印这两个数据,如果程序没有错误的话,这两个域中的数字应该始终是相等的.
源代码如下:
以下类文件在同一包下,主函数为Main类的main()方法
public class Park {
boolean[] cars;
//用于统计数据
int parkTotal;
int leaveTotal;
public Park(){
cars = new boolean[3];
}
public synchronized void park(){
//此处一定要用while不能用if,因为可能需要反复争抢
while ( isFull() ){
myWait();
}
//停车
cars[getOneEmpty()] = true;
parkTotal++;
show();
}
public synchronized void leave(){
//空出车位
cars[getOneCar()] = false;
this.notifyAll();
leaveTotal++;
show();
}
private boolean isFull(){
boolean flag = true;
for ( boolean place : cars ){
if ( !place ){
flag = false;
break;
}
}
return flag;
}
private int getOne(boolean flag){
int i = 0;
for ( ; i < cars.length; i++ ){
if ( cars[i] == flag ){
return i;
}
}
return -1;
}
//得到一个空车位的索引
private int getOneEmpty(){
return getOne(false);
}
//得到一个有车的车位的索引
private int getOneCar(){
return getOne(true);
}
private void myWait() {
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException();
}
}
private void show(){
for( boolean place : cars ){
System.out.print(place + " ");
}
System.out.println();
}
public int getParkTotal() {
return parkTotal;
}
public int getLeaveTotal() {
return leaveTotal;
}
}
public class User extends Thread {
Park park;
public User(Park park) {
this.park = park;
}
@Override
public void run() {
for ( int i = 0; i < 10; i++ ){
park.park();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
park.leave();
}
}
}
public class Main {
public static void main(String[] args) {
Park park = new Park();
User u1 = new User(park);
User u2 = new User(park);
User u3 = new User(park);
User u4 = new User(park);
User u5 = new User(park);
User u6 = new User(park);
u1.start();
u2.start();
u3.start();
u4.start();
u5.start();
u6.start();
try {
u1.join();
u2.join();
u3.join();
u4.join();
u5.join();
u6.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("停车总计:" + park.getParkTotal());
System.out.println("离开总计:" + park.getLeaveTotal());
}
}
反复运行结果(仅给出最后三行输出):
false false false
停车总计:60
离开总计:60
停车总计=离开总计,程序正确.
解题反思:
解法二:
思路:不再使用Park类作为同步监听器,而是将同步监听器细化为每一个停车位,这里建立一个ParkPlace类表示停车位,Park类中也不再使用boolean数组,而是使用ParkPlace数组,每个ParkPlace对象中有一个boolean型的域表示这个车位是否为空,当调用,调用了Park对象的park()方法后,会不断地尝试获取各个车位的锁,如果得到了某个车位的锁就进行且该车位没车就进行相关操作,leave()方法也是类似,为了实现这一点,这里不再使用synchronized来控制同步,而是给每个ParkPlace对象一个Lock域来控制同步,使用Lock的tryLock()方法尝试获得同步锁,返回true则说明成功获得锁,返回false则去尝试获得下一个车位的锁。最后会打印停车场停车与驶离次数总计,方法与解法一一样,如果两个值一样则程序正确.
源代码如下:
以下类文件在同一包下,主函数为Main类的main()方法,其中User类直接使用上一解法中的User类即可
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ParkPlace {
private String id;
private boolean status;
//用于统计数据使用
private int parkNum;
private int leaveNum;
public ParkPlace(String id) {
this.id = id;
}
private final Lock lock = new ReentrantLock();
public boolean getStatus() {
return status;
}
private void setStatus(boolean status){
if ( status ){
parkNum++;
} else {
leaveNum++;
}
this.status = status;
}
private boolean isEmpty(){
if ( status ){
return false;
}
return true;
}
public boolean park() {
if ( !lock.tryLock() ){
return false;
}
try {
if ( isEmpty() ){
//停车
setStatus(true);
System.out.println( Thread.currentThread().getName() + "在车位" + id + "停车");
} else {
return false;
}
} finally {
lock.unlock();
}
return true;
}
public boolean leave() {
if ( !lock.tryLock() ){
return false;
}
try {
if ( !isEmpty() ){
//离开
setStatus(false);
System.out.println(Thread.currentThread().getName() + "从车位" + id + "驶离");
} else {
return false;
}
} finally {
lock.unlock();
}
return true;
}
public int getParkNum() {
return parkNum;
}
public int getLeaveNum() {
return leaveNum;
}
}
public class Park {
private ParkPlace places[];
public Park(){
places = new ParkPlace[3];
for ( int i = 0; i < places.length; i++ ){
places[i] = new ParkPlace(i + "");
}
}
public void park() {
boolean flag = false;
while (true){
for ( ParkPlace place : places ){
if ( place.park() ){
flag = true;
break;
}
}
if ( flag ){
break;
}
}
}
public void leave() {
boolean flag = false;
while (true){
for ( ParkPlace place : places ){
if ( place.leave() ){
flag = true;
break;
}
}
if ( flag ){
break;
}
}
}
//数据统计使用
public int getParkTotal() {
int parkTotal = 0;
for ( ParkPlace place : places ){
parkTotal += place.getParkNum();
}
return parkTotal;
}
public int getLeaveTotal() {
int leaveTotal = 0;
for ( ParkPlace place : places ){
leaveTotal += place.getLeaveNum();
}
return leaveTotal;
}
}
public class Main {
public static void main(String[] args) {
Park park = new Park();
User u1 = new User(park);
User u2 = new User(park);
User u3 = new User(park);
User u4 = new User(park);
User u5 = new User(park);
u1.start();
u2.start();
u3.start();
u4.start();
u5.start();
try {
u1.join();
u2.join();
u3.join();
u4.join();
u5.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("停车次数总计:" + park.getParkTotal());
System.out.println("离开次数总计:" + park.getLeaveTotal());
}
}
反复运行后打印的结果(这里只给出最后三行):
Thread-3从车位2驶离
停车次数总计:50
离开次数总计:50
停车总计=离开总计,程序正确.
解题反思:
解法三:
思路:这个解法三是在我距离发表这篇博文两个月后添加的,我一直以为java是不支持信号量,直到最近才偶然发现了java的Semaphore类可以实现信号量的功能。信号量根据在大学里学习操作系统这门课的经验,最适合解决生产者消费者这一类的问题,但是这个问题并不是典型的生产者消费者问题,只设置一个代表车位数的信号量是不够的,进程还需要检查(寻找空的车位)并修改车位的状态,如果多个进程进入的话,检查会出现问题。我解决方案就是直接在解法二的基础(对单个车位上锁)上进行改进,使用信号量表示停车场的空位数,在车辆进入停车场之前必须先申请到一个车位的资源,在车辆驶离时归还资源。用这种方法可以改善解法二中的cpu的轮询,当停车场爆满时,线程无法申请到资源,就会阻塞,而不会继续没有意义地占用cpu资源。同时我还改进了一下Main类中的代码,使用线程池的invokeAll()方法执行6个User线程,这个方法会等待六个线程全部执行完才会执行下面的语句,和之前的join()方法的效果是一样的,invokeAll方法只能够执行实现Callable接口的集合,为了使用这个方法,我也将user由继承Thread类改为了实现Callable接口。
源代码如下:
以下类文件在同一包下,主函数为Main类的main()方法
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Created by 燃烧杯 on 2017/7/4.
*/
public class ParkPlace {
private String id;
private boolean status;
//用于统计数据使用
private int parkNum;
private int leaveNum;
public ParkPlace(String id) {
this.id = id;
}
private final Lock lock = new ReentrantLock();
public boolean getStatus() {
return status;
}
private void setStatus(boolean status){
if ( status ){
parkNum++;
} else {
leaveNum++;
}
this.status = status;
}
private boolean isEmpty(){
if ( status ){
return false;
}
return true;
}
public boolean park() {
if ( !lock.tryLock() ){
return false;
}
try {
if ( isEmpty() ){
//停车
setStatus(true);
System.out.println( Thread.currentThread().getName() + "在车位" + id + "停车");
} else {
return false;
}
} finally {
lock.unlock();
}
return true;
}
public boolean leave() {
if ( !lock.tryLock() ){
return false;
}
try {
if ( !isEmpty() ){
//离开
setStatus(false);
System.out.println(Thread.currentThread().getName() + "从车位" + id + "驶离");
} else {
return false;
}
} finally {
lock.unlock();
}
return true;
}
public int getParkNum() {
return parkNum;
}
public int getLeaveNum() {
return leaveNum;
}
}
import java.util.concurrent.Semaphore;
/**
* Created by 燃烧杯 on 2017/7/4.
*/
public class Park {
//停车场的位置数量
public static final int N = 3;
private ParkPlace places[];
//建立一个资源数为N的信号量
Semaphore sema = new Semaphore(N);
public Park(){
places = new ParkPlace[N];
for ( int i = 0; i < places.length; i++ ){
places[i] = new ParkPlace(i + "");
}
}
public void park() {
try {
//停车之前先申请资源
sema.acquire();
for ( ParkPlace place : places ){
if ( place.park() ){
break;
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void leave() {
for ( ParkPlace place : places ){
if ( place.leave() ){
break;
}
}
//驶离之后归还资源
sema.release();
}
//数据统计使用
public int getParkTotal() {
int parkTotal = 0;
for ( ParkPlace place : places ){
parkTotal += place.getParkNum();
}
return parkTotal;
}
public int getLeaveTotal() {
int leaveTotal = 0;
for ( ParkPlace place : places ){
leaveTotal += place.getLeaveNum();
}
return leaveTotal;
}
}
import java.util.concurrent.Callable;
/**
* Created by 燃烧杯 on 2017/7/4.
*/
public class User implements Callable<Boolean> {
Park park;
public User(Park park) {
this.park = park;
}
@Override
public Boolean call() {
for ( int i = 0; i < 10; i++ ){
park.park();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
park.leave();
}
return true;
}
}
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by 燃烧杯 on 2017/7/4.
*/
public class Main {
public static void main(String[] args) {
//线程池
ExecutorService exec = Executors.newFixedThreadPool(6);
Park park = new Park();
List users = new ArrayList();
for ( int i = 0; i < 6; i++ ){
users.add(new User(park));
}
try {
exec.invokeAll(users);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("停车次数总计:" + park.getParkTotal());
System.out.println("离开次数总计:" + park.getLeaveTotal());
//关闭线程池
exec.shutdown();
}
}
反复运行后打印的结果(这里只给出最后三行):
pool-1-thread-6从车位0驶离
停车次数总计:60
离开次数总计:60
停车总计=离开总计,程序正确。
解题反思:
解法四:
思路:解法四打算换个角度解决这个问题,为了可以使用java5提供的方便的同步工具BlockingQueue,此解法跳出题目所给的必须要用boolean数组表示车位的限制,将车库看成一个长度为3的阻塞队列,调用BlockingQueue的put方法可以在队列中放入一个元素,如果发现队列满则阻塞,调用take方法可以取出队头元素,如果队列空则阻塞,由于在本题中取车的时候车库里肯定是有车的,所以此处驶离时调用的是remove方法将自己取出。不再使用之前的User类作为线程,而是换成Car类,所谓停车就是对象将自己放入阻塞队列,驶离就是将自己从阻塞队列中取出。使用这种方法可以非常是简洁的实现题目要求的功能,而且效率也很高。
源代码如下:
以下类文件在同一包下,主函数为Main类的main()方法
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
/**
* Created by 燃烧杯 on 2017/9/26.
*/
public class Car implements Callable<Boolean> {
private BlockingQueue park;
public Car(BlockingQueue park) {
this.park = park;
}
@Override
public Boolean call() throws Exception {
for ( int i = 0; i < 10; i++ ){
park.put(this);
System.out.println(Thread.currentThread() + "停车");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
park.remove(this);
System.out.println(Thread.currentThread() + "驶离");
}
return true;
}
}
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by 燃烧杯 on 2017/9/26.
*/
public class Main {
public static void main(String[] args) {
ExecutorService exec = Executors.newFixedThreadPool(6);
BlockingQueue park = new ArrayBlockingQueue(3);
List cars = new ArrayList();
for ( int i = 0; i < 6; i++ ){
cars.add(new Car(park));
}
long time1 = System.currentTimeMillis();
try {
exec.invokeAll(cars);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("车库剩余车辆:" + park.size());
System.out.println("总计用时:" + (System.currentTimeMillis() - time1));
}
exec.shutdown();
}
反复运行结果(此处只给出最后三行):
Thread[pool-1-thread-6,5,main]驶离
车库剩余车辆:0
总计用时:4025
个人博客地址