最近在重新学习多线程的相关知识,发现多线程是真的有意思,完全打破以前的那种只有1条执行流的执行方式, 而是多线程一起跑起来,并发的进行,所以你会看到他的输出会完全打破你之前的按序输出, 他 根据底层OS的处理器进行随机调度某一个线程进行执行;因为时间很短,所以你会感觉像并发进行;
关于这方面的知识,就牵扯到:多个线程之间的通信方式,怎么控制多个线程来执行达到你想要的结果;以及他们之间的同步问题;这2方面是关键;
学习多线程思维模式也打破了,从空间维度类比有种从1维过渡到2维、3维的感觉;
下面通过几道题目实践感受下,多线程的并发、以及他们之间的通信,同步。
1.此题目可以用同步代码块,也可以用同步方法。
/**
*1. 写2个线程,其中一个线程打印1\~52,另一个线程打印A\~Z,
*打印顺序应该是12A34B56C…5152Z。
*该题需要用到多线程通信的知识。
* 不理解的话可以Debug下一步一步的走走看
* @author lwx
*
*/
public class PrintThread {
public static void main(String[] args) {
Object obj = new Object();
new NumberThread(obj).start();
new ZimuThread(obj).start();
}
}
class NumberThread extends Thread {
private Object obj;
public NumberThread(Object obj) {
this.obj = obj;
}
public void run() {
//加锁
synchronized (obj) {
for (int i = 1; i <= 52; i++) {
System.out.print(i);
if (i % 2 == 0) {
obj.notifyAll();
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
class ZimuThread extends Thread {
private Object obj;
public ZimuThread(Object obj) {
this.obj = obj;
}
public void run() {
synchronized (obj) {
for (int i = 0; i < 26; i++) {
System.out.print((char) (i + 'A'));
// 唤醒其他线程并且让线程等待!
obj.notifyAll();
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
总结:
由于线程A和线程B持有同一个MyObject类的对象object,尽管这两个线程需要调用不同的方法,但是它们是同步执行的,比如:线程B需要等待线程A执行完了methodA()方法之后,它才能执行methodB()方法。这样,线程A和线程B就实现了 通信。
这种方式,本质上就是“共享内存”式的通信。多个线程需要访问同一个共享变量,谁拿到了锁(获得了访问权限),谁就可以执行。
运行结果:
12A34B56C78D910E1112F1314G1516H1718I1920J2122K2324L2526M2728N2930O3132P3334Q3536R3738S3940T4142U4344V4546W4748X4950Y5152Z
1.创建一个包含这两种方法的类
public class PrintStore {
//通过一个标识flag来确保打印字母的线程一定要等到打印数字线程执行后才得到执行机会。
private boolean flag = false;
public synchronized void printNumber() {
// System.out.printf("打印数字线程启动,此时flag为 %s \n", flag);
for (int i = 1; i < 53; i++) {
System.out.print(i);
if (i % 2 == 0) {
flag = true;
notifyAll();
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public synchronized void printLetter() {
// System.out.printf("打印字母线程启动,此时flag为 %s \n", flag);
char[] zimu = new char[26];
for (int i = 0; i < 26; i++) {
zimu[i] = (char) (i + 65);
}
for (char c : zimu) {
if (flag) {
System.out.print(c);
flag = false;
notify();
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
2.写两个线程类
class PrintNumber implements Runnable {
private PrintStore ps;
public PrintNumber(PrintStore ps) {
this.ps=ps;
}
@Override
public void run() {
ps.printNumber();
}
}
class PrintLetter implements Runnable {
private PrintStore ps;
public PrintLetter(PrintStore ps) {
this.ps=ps;
}
@Override
public void run() {
ps.printLetter();
}
}
3.主方法测试
public class HomeWork1 {
public static void main(String[] args) {
PrintStore ps=new PrintStore();
PrintNumber pn=new PrintNumber(ps);
PrintLetter pl=new PrintLetter(ps);
new Thread(pn).start();
new Thread(pl).start();
}
}
总结:
写这个程序过程中遇到了不少问题,一开始我创建线程时,没有用到共享资源,导致两个线程都是独立的,然后发现线程A的wait()方法和notify()方法,只能暂停和启动A线程本身,并不会通知B线程启动。后来仔细研究了一下这里的问题:
Object类的wait(),notify(),notifyAll()方法必须由同步监视器对象来调用。
* 对于使用synchronized修饰的同步方法,因为该类的默认实例(this)就是同步监视器,所以可以在同步方法中直接调用这三个方法;
* 对于使用synchronized修饰的同步代码块,同步监视器是synchronized后括号里的对象,所以必须使用该对象调用。
这三个方法,都只能对同一个同步监视器上的线程有效,所以我最开始的那种方式,其实存在着两个同步监视器,线程A和线程B各自有各自的同步监视器,所以A不能成功通知到B。后来参考这个:
深入理解Java多线程之线程间的通信方式
发现两个线程之间如果想要成功的进行通信,必须要创建一个线程之间的共享资源,然后把这个共享资源当做同步监视器。
1.停车场:含有属性:车位状态 同步的方法:停车、开车
/**
* 2.假设车库有3个车位(可以通过boolean[]数组来表示车库)可以停车,
* 写一个程序模拟多个用户开车离开,停车入库的效果。注意:车位有车时不能停车。
* @author lwx
*
*/
public class Park {
private int state = 3;//车场中剩余的车位数
public synchronized void CarIn(int i){
try{
if(state == 0){
System.out.println("目前空余车位为:"+ state+"请等待");
wait();
}
System.out.println(i+"号车停车成功");
state--;
System.out.println("目前剩余车位: " + state);
notifyAll();
}catch (InterruptedException e){
e.printStackTrace();
}
}
public synchronized void CarOut(int i){
try{
if(state ==3){
wait();
}
System.out.println(i +"号车已驶出");
state++;
System.out.println("目前剩余车位: " + state);
notify();
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
2.停车线程:模拟用户停车20次
class CarInThread extends Thread{
Park park;
public CarInThread(Park park){
this.park = park;
}
public void run(){
for(int i=1;i<20;i++){
park.CarIn(i);
}
}
}
3.开车线程:模拟用户开车20次
class CarOutThread extends Thread{
Park park;
public CarOutThread(Park park) {
this.park=park;
}
@Override
public void run() {
super.run();
for(int i=1;i<20;i++){
park.CarOut(i);
}
}
}
4.测试:
public class Test {
public static void main(String[] args){
Park park =new Park();
CarInThread cInThread=new CarInThread(park);
CarOutThread cOutThread=new CarOutThread(park);
new Thread(cOutThread).start();
new Thread(cInThread).start();
}
}
运行结果:
0号车停车成功
目前剩余车位: 2
1号车停车成功
目前剩余车位: 1
2号车停车成功
目前剩余车位: 0
目前空余车位为:0请等待
0号车已驶出
目前剩余车位: 1
1号车已驶出
目前剩余车位: 2
2号车已驶出
目前剩余车位: 3
3号车停车成功
目前剩余车位: 2
4号车停车成功
目前剩余车位: 1
5号车停车成功
目前剩余车位: 0
目前空余车位为:0请等待
3号车已驶出
目前剩余车位: 1
4号车已驶出
目前剩余车位: 2
5号车已驶出
目前剩余车位: 3
6号车停车成功
目前剩余车位: 2
7号车停车成功
目前剩余车位: 1
8号车停车成功
目前剩余车位: 0
目前空余车位为:0请等待
6号车已驶出
目前剩余车位: 1
7号车已驶出
目前剩余车位: 2
8号车已驶出
目前剩余车位: 3
9号车停车成功
目前剩余车位: 2
9号车已驶出
目前剩余车位: 3
总结:
此题目也可以用对象锁+condition实现通信;
也可以用队列来做,基本都是这样子,注意要实现同步+通信