我是一个从汽车行业转行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、将方法改成静态,则无论如何同步效果都有效,因为锁的是类对象,内存中仅有一份。