java疯狂讲义 16章多线程习题
习题1:
写2个线程,其中一个线程打印1-52,另一个线程打印A-Z,打印顺序应该是12A34B56C……5152Z。该习题需要利用多线程通信的知识。
刚开始的思路:
既然需要两个线程打印,那就写两个类,分别继承Thread,里面的run方法写各自的打印逻辑;但是又想?这么写,该如何通信呢。。。。。。通信需要同步监视器来实现,这么写他们两个打印逻辑其实没有所谓的共享资源作为同步监视器。那么需要创建一个打印机的类,作为共享资源(竞争资源,以上两个打印线程来竞争使用打印机),那么就有了以下的代码:
- Printer:打印机类
- PrintNumThread:打印数字的线程
- PrintAlphaThread:打印字母的线程
- Main:主函数类
Printer类,其实里面什么都没有,主要是做共享资源用
//Printer类
public class Printer {
private String name;
public Printer(){}
public Printer(String name){
this.name=name;
}
}
PrintAlphaThread类,打印字母线程
public class PrintAlphaThread extends Thread {
private Printer pr;
public PrintAlphaThread(Printer pr){
this.pr=pr;
}
@Override
public void run() {
synchronized (pr) {
for (char i=65;i<26+65;i++) {
System.out.print(i);
// 如果打印一个字符了,发送消息到其他线程,进入阻塞状态
pr.notifyAll();
try {
pr.wait();
} catch (Exception ex) {
ex.printStackTrace();
}
}
pr.notifyAll();
}
}
}
PrintNumThread 类,打印数字线程,和上面差不多
public class PrintNumThread extends Thread {
private Printer pr;
public PrintNumThread(Printer pr){
this.pr=pr;
}
@Override
public void run() {
synchronized (pr) {
for (int i=1;i<=52;i++) {
System.out.print(i);
if (i % 2 == 0) {
// 如果打印一个字符了,发送消息到其他线程,进入阻塞状态
pr.notifyAll();
try {
pr.wait();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
pr.notifyAll();
}
}
}
主类:创建线程
public class Main {
public static void main(String[] args)
{
Printer pr=new Printer();
PrintNumThread p1=new PrintNumThread(pr);
PrintAlphaThread p2= new PrintAlphaThread(pr);
p1.start();
//此处可以写一下等待逻辑,保证p1先运行;Thread.sleep(100);
p2.start();
}
}
总结:
- 以上代码采用:继承Thread实现多进程;采用同步代码块实现线程同步;注意同步代码块显式指定同步监视器pr,在代码块中需要采用pr去调用wait、等方法
- 同步代码块----可以在for循环中,也可以在for循环外;但是由于打印字母的线程打印最后Z后,进入阻塞,没有线程来唤醒,所以程序不会停止;好一点的做法是,for循环结束后再次notifyAll通知,因此,同步代码块设置为整个for循环比较合理
改进1:
因为Printer 代码看着太故意了,所以我们把打印逻辑写成-------Printer的同步方法;两个打印线程不变,只不过线程执行体中放的是打印机的两个同步方法。
- PointMonitor1 :打印机类
- PrintNumber :打印数字的线程
- PrintLetter :打印字母的线程
- Main1:主函数类
PointMonitor1 :打印机类
public class PointMonitor1 {
public synchronized void printnum(){
for (int i=1;i<=52;i++){
System.out.print(i);
if (i%2==0){
notifyAll();
try{wait();
}
catch(Exception ex){
ex.printStackTrace();
}
}
}
notifyAll();
}
public synchronized void printstr(){
for (int i=65;i<65+26;i++){
System.out.print((char)i);
notifyAll();
try{wait();
}
catch(Exception ex){
ex.printStackTrace();
}
}
notifyAll();
}
}
PrintLetter 打印字母的线程
class PrintLetter implements Runnable {
private PointMonitor1 ps;
public PrintLetter(PointMonitor1 ps) {
this.ps=ps;
}
@Override
public void run() {
ps.printstr();
}
}
PrintNumber :打印数字的线程
class PrintNumber implements Runnable {
private PointMonitor1 ps;
public PrintNumber(PointMonitor1 ps) {
this.ps=ps;
}
@Override
public void run() {
ps.printnum();
}
}
Main1:主函数类
public class Main1 {
public static void main(String[] agrs){
PointMonitor1 pm=new PointMonitor1();
PrintNumber t1=new PrintNumber(pm);
PrintLetter t2=new PrintLetter(pm);
new Thread(t1).start();
new Thread(t2).start();
}
}
总结1:
- 采用runnable接口实现多线程,在主类中需要采用Thread包装一下runnable对象
- 两个打印线程直接调用打印机的同步方法,在同步方法中,使用wait等默认对象的this当前调用该方法的对象pr
改进2:
看到有些大佬把两个打印线程综合为一个类:该类的构造器传入打印的String和打印间隔,和共享资源printer;一共使用三个类实现了相同的功能
PointMonitor
public class PointMonitor {
public synchronized void pointAll (String[] strings,int interval)
throws Exception
{
for (int i=0;i<strings.length;i++){
System.out.print(strings[i]);
if ((i+1)%interval==0){
this.notifyAll();
this.wait();
}
}
notifyAll();
}
}
PrintThread 调用打印机的通用的同步方法,完成打印
public class PrintThread extends Thread{
private PointMonitor pm;
private String[] strings;
private int interval;
public PrintThread(PointMonitor pm,String[] strings,int interval){
this.interval=interval;
this.pm=pm;
this.strings=strings;
}
@Override
public void run(){
try{
pm.pointAll(strings,interval);}
catch (Exception ex){
ex.printStackTrace();
}
System.out.println(this.getName()+"线程结束");
}
}
Main
public class Main {
public static void main(String[] args){
String Num[] = new String[52];
String Str[] = new String[26];
for (int i=1;i<=52;i++){
Num[i-1]=i+"";
}
for (int i=65;i<65+Str.length;i++){
Str[i-65]=(char)i+"";
}
for (String s:Num
) {System.out.print(s);
}
System.out.println();
for (String s:Str
) {System.out.print(s);
}
System.out.println();
PointMonitor pm=new PointMonitor();
PrintThread printnum=new PrintThread(pm,Num,2);
PrintThread printstr=new PrintThread(pm,Str,1);
printnum.start();
try{
Thread.sleep(10);
}
catch (Exception ex){
ex.printStackTrace();
}
printstr.start();
// System.out.println("run over");
}
}
习题1进阶(三个线程顺序打印ABC):
1.循序打印ABC各100次(tx面试手写忘了。。。泪崩)
此处打印机对象就不仅仅是一个空的竞争资源了,还承担了控制多个打印线程之间顺序的作用(线程间通讯)
public class main {
public static void main(String[] args) throws InterruptedException{
Printer printer=new Printer(1);
PrintA pA=new PrintA(printer);
PrintB pC=new PrintB(printer);
PrintC pB=new PrintC(printer);
pA.start();
pB.start();
pC.start();
}
}
class Printer{
public int next;
public Printer(){}
public Printer(int next){
this.next=next;
}
}
class PrintA extends Thread{
private Printer pr;
public PrintA(Printer pr){
this.pr=pr;
}
@Override
public void run() {
synchronized (pr){
try{
int i=0;
while(i++<100){
while(pr.next!=1){pr.wait();}
System.out.print("A");
pr.next=2;
pr.notifyAll();
}
}catch (Exception ex){ex.printStackTrace();}
}
}
}
class PrintB extends Thread{
private Printer pr;
public PrintB(Printer pr){
this.pr=pr;
}
@Override
public void run() {
synchronized (pr){
try{
int i=0;
while(i++<100){
while(pr.next!=2){pr.wait();}
System.out.print("B");
pr.next=3;
pr.notifyAll();
}
}catch (Exception ex){ex.printStackTrace();}
}
}
}
class PrintC extends Thread{
private Printer pr;
public PrintC(Printer pr){
this.pr=pr;
}
@Override
public void run() {
synchronized (pr){
try{
int i=0;
while(i++<100){
while(pr.next!=3){pr.wait();}
System.out.print("C");
pr.next=1;
pr.notifyAll();
}
}catch (Exception ex){ex.printStackTrace();}
}
}
}
2.分别循序打印ABC,10,5,2次
这个题如果按照上面的写法去做的话,当打印完C之后,A就不能被C再次唤醒,因此B也无法被A唤醒,程序进入阻塞;暂时还没想好怎么写
习题2:
习题2.假设车库有3个车位(可以用boolean[]数组来表示车库)可以停车,写一个程序模拟多个用户开车离开、停车入库的效果。注意:车库有车时不能停车
最开始的思路:
车库作为共享资源被竞争
实现3个类:
Park1:停车场,里面含有三个停车位,提供检查车位的方法(返回空车位)、入库,出库的方法
CarThread1:停车线程,根据逻辑实现查询车位,入库,停留,出库
Main:主类,
Park1
public class Park1 {
private boolean[] carport={true,true,true};
// 获得车库空位
public int getempty(){
for(int i=0;i<carport.length;i++){
if(carport[i]){
return i;
}
}
return -1;
}
// 入库指定车位
public void inport(int port){
carport[port]=false;
}
// 指定车位出库
public void outport(int port){
carport[port]=true;
}
public void show(){
String s="车库状态:";
for(boolean b:carport){
s=s+b+',';
}
System.out.println(s);
}
}
CarThread1
public class CarThread1 implements Runnable{
private Park1 pk;
public CarThread1(Park1 pk){
this.pk=pk;
}
@Override
public void run() {
// 有车来了,首先查询,没有空位需等待下次查询
// 因此用while
synchronized (pk) {
for (int i=0;i<10;i++){
while (pk.getempty() == -1) {
try {
pk.wait();
} catch (Exception ex) {
ex.printStackTrace();
}
}
// 有车位,记住位置,停车
int port = pk.getempty();
pk.inport(port);
System.out.print(Thread.currentThread().getName()+"入库"+port+" ");
pk.show();
// 停留1s
try {
Thread.sleep(1000);
} catch (Exception ex) {
ex.printStackTrace();
}
// 1s时间到,出库
pk.outport(port);
System.out.print(Thread.currentThread().getName()+"出库"+port+" ");
pk.show();
pk.notifyAll();
}
}
}
}
Main1
public class Main1 {
public static void main(String[] args){
Park1 pk=new Park1();
CarThread1 r1=new CarThread1(pk);
CarThread1 r2=new CarThread1(pk);
CarThread1 r3=new CarThread1(pk);
CarThread1 r4=new CarThread1(pk);
CarThread1 r5=new CarThread1(pk);
new Thread(r1).start();
new Thread(r2).start();
new Thread(r3).start();
new Thread(r4).start();
new Thread(r5).start();
}
}
问题:
由于同步方法块,包含了整个停车逻辑,因此,在车辆停留时间内,都占用着同步监视器;出现每次只有一个车位被占后,整个停车场被锁的感觉
然后想,修改停车线程,把Thread.sleep排除在同步代码块之外,也就是此时释放同步锁,允许其他车辆进入车库
public class CarThread1 implements Runnable{
private Park1 pk;
public CarThread1(Park1 pk){
this.pk=pk;
}
@Override
public void run() {
// 有车来了,首先查询,没有空位需等待下次查询
// 因此用while
int port;
for (int i = 0; i < 10; i++) {
synchronized (pk) {
while (pk.getempty() == -1) {
try {
pk.wait();
} catch (Exception ex) {
ex.printStackTrace();
}
}
// 有车位,记住位置,停车
port = pk.getempty();
pk.inport(port);
System.out.print(Thread.currentThread().getName() + "入库" + port + " ");
pk.show();
}
// 停留1s
try {
Thread.sleep(1000);
} catch (Exception ex) {
ex.printStackTrace();
}
// 1s时间到,出库
synchronized (pk) {
pk.outport(port);
System.out.print(Thread.currentThread().getName() + "出库" + port + " ");
pk.show();
pk.notifyAll();
}
}
}
}
一切正常
改进1:
由于上述代码把停车逻辑全部卸载线程执行体中,看着有些庞大,可以将停车逻辑写到Park的方法中,供run执行体调用即可。
Park
public class Park {
// private boolean[] carport=new boolean[3];
private boolean[] carport={true,true,true};
// public Park(){
// for(int i=0;i<this.carport.length;i++){
// this.carport[i]=true;
// }
// }
private int GetEmptyport(){
for(int i=0;i<carport.length;i++){
if(carport[i]){
return i;
}
}
return -1;
}
//入库,返回入库车位
public synchronized int InPort(){
while (GetEmptyport()==-1){
mywait();
}
int port=GetEmptyport();
carport[port]=false;
System.out.print("入库车位:"+(port+1)+" ");;
show();
return port;
}
// 出库:输入出库车位
public synchronized void OutPort(int port){
carport[port]=true;
System.out.print("出库车位:"+(port+1)+" ");
show();
notifyAll();
}
private void mywait(){
try{
wait();
}
catch (Exception ex){
ex.printStackTrace();
}
}
private void show(){
String s="";
for(boolean b:carport){
s=s+b+',';
}
System.out.println(s);
}
}
CarThread
public class CarThread extends Thread{
private Park park;
public CarThread(Park park){
this.park=park;
}
@Override
public void run() {
for(int i=0;i<10;i++){
int port=park.InPort();
try {
Thread.sleep(1000);
}
catch (Exception ex){
ex.printStackTrace();
}
park.OutPort(port);
}
}
}
Main
public class Main {
public static void main(String[] args){
Park cp=new Park();
new CarThread(cp).start();
new CarThread(cp).start();
new CarThread(cp).start();
new CarThread(cp).start();
new CarThread(cp).start();
}
}
改进2:
以上两种都是把整个停车场当做共享资源,因此,同一时刻只能有一个车辆线程操作park,但其实一个停车场有很多车位,其实可以同时操作,这里借鉴一下大佬的思路,把共享资源细化到每个车位,就不写代码了:贴个链接https://blog.csdn.net/qq_33256688/article/details/74781752