问题描述:1个父线程,并发生成N(N作为参数传入)个子线程。设定全局变量 g_sum=0,每个子进程在 0至200中随机选择一个数;

若子线程的进程ID是奇数则g_sum=g_sum-随机数,
若子线程的进程ID是偶数则g_sum=g_sum+随机数,

每个子线程对g_sum操作完成后都要通知父线程,父线程收到通知后输出“子线程{进程ID}对g_sum进行了[加/减]{随机数}操作,当前g_sum={新值}”,父进程将每次g_sum的新值进行记录,并对已记录的数组进行升序排列,每次重新排列后进行一次输出。

想法一 回调方式

  • ThreadTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class ThreadTest {
public static int g_sum = 0;
public static List<Integer> g_sumList = new ArrayList<Integer>();
public static Object o= new Object();

@SuppressWarnings("unchecked")
public ThreadTest() {
super();
this.g_sumList.add(g_sum);
}

/**
*子线程回调函数
* @param id 进程id
* @param num 加或减的数值
* @param flag 加或减的标志
*/
public static void callBack(int id, int num, int flag) {
String mark = flag == 1 ? "加" : "减";
System.out.println("子线程" + id + ":对g_num进行了" + mark + "" + num + "操作,当前g_sum=" + g_sum);
//将值记录到列表并排序,输出
g_sumList.add(g_sum);
Collections.sort(g_sumList);
SimpleDateFormat sdf = new SimpleDateFormat(); // 格式化时间
sdf.applyPattern("yyyy-MM-dd HH:mm:ss a"); // a为am/pm的标记
Date date = new Date(); // 获取当前时间
System.out.println(sdf.format(date)+" g_sum历史值升序为:");
System.out.println(g_sumList);
System.out.println();
}

//创建N个子线程
public void generateThreads(int N) {
int randomNum;
for (int id = 1; id <= N; id++) {
randomNum = (int) (Math.random() * 200);
//通过自定义的Runnable类创建线程
MyRunnable myRunnable = new MyRunnable(id, randomNum);
Thread t=new Thread(myRunnable);
t.start();
}

}

public static void main(String[] args) {
ThreadTest test = new ThreadTest();
test.generateThreads(10);
}
}

MyRunnable.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MyRunnable implements Runnable {
private int id;
private int randomNum;

public MyRunnable(int id, int randomNum) {
super();
this.id = id;
this.randomNum = randomNum;
}
@Override
public void run() {
synchronized (ThreadTest.o) { //加同步锁,防止多个线程对g_sum操作造成脏读
if (id % 2 == 0) {
ThreadTest.g_sum += randomNum;
ThreadTest.callBack(id, randomNum, 1);
} else {
ThreadTest.g_sum -= randomNum;
ThreadTest.callBack(id, randomNum, 0);
}
}

}

}
  • 运行结果

image-20210623000217372

子线程和父线程之间其实并没有进行通信,打印信息的静态回调方法callBack()虽然是在主类中定义,但是却是在子线程中调用的,而不是子线程将信息传递并通知给主线程,然后主线程打印。

想法二 消息队列

  • ThreadTest.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
     public class ThreadTest {
    public static int g_sum = 0;
    public static Deque<Message> messageDeque = new ArrayDeque();
    public List<Integer> g_sumList = new ArrayList<Integer>();
    public static Object lock= new Object();

    public ThreadTest() {
    super();
    this.g_sumList.add(g_sum);
    }


    //N个子线程并发创建执行
    public void generateThreads(int N) {
    int randomNum;
    for (int id = 1; id <= N; id++) {
    randomNum = (int) (Math.random() * 200);
    //通过自定义的Runnable类创建线程
    MyRunnable myRunnable = new MyRunnable(id, randomNum);
    Thread t=new Thread(myRunnable);
    t.start();
    }
    }

    public static void main(String[] args) throws InterruptedException {
    System.out.print("请输入问题规模:");
    Scanner in = new Scanner(System.in);
    int N = in.nextInt();
    ThreadTest test = new ThreadTest();
    synchronized (lock) { //加同步锁
    test.generateThreads(N); //在主线程中并发创建N个进程
    while(true) {
    if(messageDeque.isEmpty()) {
    lock.wait(); //消息队列为空时,主线程阻塞等待,等待lock解锁
    }
    while(!messageDeque.isEmpty()) {
    //主线程从消息队列中取出消息并打印
    Message mess = messageDeque.pop();
    System.out.println("子线程"+mess.tid+"进行了"+mess.op+""+mess.randomNum+"操作,当前g_sum="+mess.g_sum);
    //将g_sum的历史值记录到列表并排序
    test.g_sumList.add(mess.g_sum);
    Collections.sort(test.g_sumList);
    //打印g_sum的历史值列表
    System.out.println(mess.opeTime+" g_sum历史值升序为:");
    System.out.println(test.g_sumList);
    System.out.println();
    }
    }
    }
    }
    }
  • MyRunnable.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public class MyRunnable implements Runnable {
private int tid;
private int randomNum;

public MyRunnable(int id, int randomNum) {
super();
this.tid = id;
this.randomNum = randomNum;
}
@Override
public void run() {
synchronized (ThreadTest.lock) { //加同步锁,防止多个线程同时(并发)对g_sum操作造成脏读
SimpleDateFormat sdf = new SimpleDateFormat(); // 格式化时间
sdf.applyPattern("yyyy-MM-dd HH:mm:ss a"); // a为am/pm的标记
String opeTime = sdf.format(new Date()); // 获取当前时间

if (tid % 2 == 0) {
ThreadTest.g_sum += randomNum;

//创建一个消息,并添加到消息队列
Message message = new Message(tid, "加", randomNum,ThreadTest.g_sum,opeTime);
ThreadTest.messageDeque.add(message);

//解锁,唤醒主线程,可以从消息队列取消息啦
ThreadTest.lock.notifyAll();
} else {
ThreadTest.g_sum -= randomNum;

Message message = new Message(tid, "减", randomNum,ThreadTest.g_sum,opeTime);
ThreadTest.messageDeque.add(message);

ThreadTest.lock.notifyAll();
}
try {
int sleepTime = (int)(Math.random() * 500); //让线程随机休眠一定时间
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

}

//子线程传递给父线程的消息类
public class Message{
public int tid; //子线程id
public String op; //加或减操作
public int randomNum; //随机数
public int g_sum; //操作后的g_sum
public String opeTime; //操作时的时间

public Message(int tid, String op, int randomNum,int g_sum ,String time) {
super();
this.tid = tid;
this.op = op;
this.randomNum = randomNum;
this.g_sum = g_sum;
this.opeTime=time;
}

}

}
  • 运行结果

image-20210623081306586

主线程并发创建N个子线程后,转为阻塞等待状态,等待lock解锁获得执行权且消息队列非空,然后一次性打印消息队列中已经存放的消息。

子线程在进行“加减”操作完成后,创建一个消息(包含线程id、随机数、操作类型、操作结果、操作时间信息),并把这个消息添加到消息队列(一个静态全局的队列),然后Notifiy(通知)唤醒主线程,通知它消息队列有消息可以打印了。