多线程并发及synchronized案例演示-java基础篇

我是一个从汽车行业转行IT的项目经理,我是Edward。今天跟大家聊聊多线程并发以及synchronized案例演示

sleep

  • static void sleep(long ms)
  • 使运行该方法的线程阻塞指定毫秒,超时后线程会自动回到RUNNABLE状态等待再次获取
  • 时间片并发运行。

用黄宏著名小品《装修》演示一下,sleep及其interrupt的效果。

/**
 * 小品《装修》
* @author EP
* @date 2020年3月29日  
* @version 1.0
 */

public class WallFinishing {

	public static void main(String[] args) {
		//林永健
		Thread lin = new Thread() {
			@Override
			public void run() {
				System.out.println("林:刚做完美容,好好睡一觉~");
				try {
					Thread.sleep(10000);
					System.out.println("林:睡醒了~");
				} catch (InterruptedException e) {
					System.out.println("林:干嘛呢!干嘛呢!都破了相了!");
				}
			}
		};
		
		//黄宏
		Thread huang = new Thread() {
			@Override
			public void run() {
				System.out.println("开始砸墙!");
				for (int i = 0; i < 5; i++) {
					System.out.println("黄:80!");
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				System.out.println("轰~");
				System.out.println("黄:完事了!");
				//lin.interrupt();
				
			}
		};
		
		lin.start();
		huang.start();

	}

}

DaemonThread

  • 守护线程是通过普通线程调用setDaemon(true)方法设置成的。
  • 因此默认创建的线程都是普通线程。所以守护线程创建时就是普通线程。因此使用上也是一样的。
  • 但是守护线程有一点与普通线程不同,就是进程结束,当一个进程结束时,所有正在运行的
  • 守护线程都会被强制停止。
  • 进程的结束:当该进程中的所有普通线程都结束时,进程就会结束。
  • GC就是放在守护线程上,适用于结束时间点不重要,进程结束就结束的线程。

用Jack和Rose的著名桥段来演示一下:

/**
 * 泰坦尼克号
* @author EP
* @date 2020年3月29日  
* @version 1.0
 */

public class JackAndRose {

	public static void main(String[] args) {
		//Rose
		Thread rose = new Thread() {
			@Override
			public void run() {
				for (int i = 0; i < 3; i++) {
					System.out.println("Rose:Let me go~");
					try {
						Thread.sleep(500);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					
				}
				System.out.println("Rose:Ahahaha~");
				System.out.println("Splash~");
				
			}
		};
		
		//Jack
		Thread jack = new Thread() {
			@Override
			public void run() {
				while (true) {
					System.out.println("Jack:You jump,I jump~");
					try {
						Thread.sleep(500);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
		};
		
		rose.start();
		jack.setDaemon(true);
		jack.start();
		System.out.println("主线程执行完毕~");

	}

}


join

  • join方法是用来协调线程之间的同步运行
  • 当一个线程调用了另一个线程的join方法后,该线程就处于了阻塞状态,直到另一个
  • 线程(join方法所属的线程)结束后才会解除阻塞。
  • 同步运行:多个线程运行是有序进行的。
  • 异步运行:多个线程各干各的,互不影响。正常情况下多个线程都是异步运行的。

案例,协调文字信息待图片下载完成一起显示:

/**
 * join方法是用来协调线程之间的同步运行
 * 
 * 当一个线程调用了另一个线程的join方法后,该线程就处于了阻塞状态,直到另一个
 * 线程(join方法所属的线程)结束后才会解除阻塞。
 * 
 * 同步运行:多个线程运行是有序进行的。
 * 异步运行:多个线程各干各的,互不影响。正常情况下多个线程都是异步运行的。
* @author EP
* @date 2020年3月28日  
* @version 1.0
 */

public class JoinDemo {
	//表示图片是否下载完毕
	public static boolean isFinish = false;

	public static void main(String[] args) {
		//boolean isFinish = false; 
		//Local variable isFinish defined in an enclosing scope must be final or effectively final
		/*
		 * java有一个语法要求:
		 * 当一个方法的局部内部类当中引用了这个方法的其他局部变量时,该变量必须
		 * 声明为final的。JDK8之后,可以不写final,但是该特性依然存在。
		 * 这实际上是java虚拟机的内存分配导致的问题。
		 */
		
		Thread download = new Thread() {
			@Override
			public void run() {
				System.out.println("download:开始下载图片...");
				for (int i = 1; i <=100; i++) {
					System.out.println("down:"+i+"%");
					try {
						Thread.sleep(50);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				System.out.println("download:图片下载完毕!");
				isFinish = true;	//图片下载完毕
			}
		};
		
		Thread show = new Thread() {
			@Override
			public void run() {
				System.out.println("show:开始显示文字");
				try {
					Thread.sleep(3000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("show:文字显示完毕!");
				
				System.out.println("show:开始显示图片");
				/*
				 * 这里应当先等待download执行完毕后再继续
				 */
				System.out.println("show:等待download执行完毕...");
				try {
					download.join();
				} catch (InterruptedException e) {   //同sleep(),也可以强制调用Interrupt来中断阻塞,会报错
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("show:等待完毕,开始后续工作!");
				
				
				
				
				if (!isFinish) {
					/*
					 * 当一个线程中抛出一个异常到run方法之外,意味着该线程
					 * 就结束了!
					 */
					//图片没有下载完毕!
					throw new RuntimeException("图片加载失败了!");
				}
				System.out.println("show:显示图片完毕!");
			}
		};
		
		
		download.start();
		show.start();


	}

}

synchronized

  • 多线程并发安全问题
  • 当多个线程并发操作同一临界资源时,由于线程切换时机不确定,导致操作该资源的顺序
  • 出现混乱,导致操作结果出现混乱。严重时可能导致系统瘫痪。
  • 临界资源:同一时间只能被单线程执行的资源。

用银行柜台和ATM取款的案例说明:

/**
 * 柜台和ATM同时取款
* @author EP
* @date 2020年3月29日  
* @version 1.0
 */

public class GetMoney {

	public static void main(String[] args) {
		Bank bank = new Bank();
		
		//柜台
		Thread counter = new Thread() {
			@Override
			public void run() {
				while (true) {
					int money = bank.getMoney();
					System.out.println(getName()+":"+money);	
				}
			}
		};
		
		//ATM
		Thread atm = new Thread() {
			@Override
			public void run() {
				while (true) {
					int money = bank.getMoney();
					System.out.println(getName()+":"+money);
					
				}
			}
		};
		
		counter.start();
		atm.start();
		

	}
	

}

class Bank{
	private int money = 10000;
	
	public synchronized int getMoney() {
		if (money==0) {
			throw new RuntimeException("钱取完了!");
		}
		Thread.yield();  //模拟当前线程恰好让出时间片的场景
		return money-=1000;
	}
}

synchronized代码块及其应用

  • 同步块
  • 语法:
  • synchronized(同步监视器对象){
  •  需要多线程同步运行的代码片段
    
  • }
  • 使用同步块可以更准确的锁定需要同步运行的代码片段,当有效的缩小同步范围时
  • 是可以在保证并发安全的前提下提高并发效率的。

案例用的是优衣库的著名事件:

/**
 * 优衣库
* @author EP
* @date 2020年3月29日  
* @version 1.0
 */

public class ClothesShop {
	public void buy() {
		Thread t = Thread.currentThread();
		
		System.out.println(t.getName()+"正在挑衣服...");
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		synchronized (this) {
			System.out.println(t.getName()+"正在试衣服,,,");
			try {
				Thread.sleep(5000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
		}
		System.out.println(t.getName()+"结账离开了~");
	}

	public static void main(String[] args) {
		ClothesShop clothesShop = new ClothesShop();
		Thread t1 = new Thread() {
			@Override
			public void run() {
				clothesShop.buy();
			}
		};
		
		Thread t2 = new Thread() {
			@Override
			public void run() {
				clothesShop.buy();
			}
		};
		
		
		t1.start();
		t2.start();

	}

}

这个案例有一些说明的地方:
1、如果把挑衣服的流程也synchronized了,则两个部分都会同步,而且两个部分会互斥,多个线程不能同时执行它们。
2、如果new出来两个shop,则同步效果失效,因为锁的new出来的对象,仅对当前对象有效。
3、将方法改成静态,则无论如何同步效果都有效,因为锁的是类对象,内存中仅有一份。

原创文章 46 获赞 7 访问量 2082

猜你喜欢

转载自blog.csdn.net/EdwardWH/article/details/105175446