一、前期基础知识储备
(1)线程同步的定义:多线程之间的同步。
(2)多线程同步原因:一个多线程的程序如果是通过Runnable接口实现的,则意味着类中的属性将被多个线程共享,由此引出资源的同步问题,即当多个线程要操作同一资源时,有可能出现错误。
(3)实现多线程同步的方式——引入同步机制:在线程使用一个资源时为其加锁,这样其他的线程便不能访问那个资源了,直到解锁后才可以访问。——这样做的结果,所有线程间会有资源竞争,但是所有竞争的资源是同步的,刷新的,动态的,不会因为线程间的竞争,导致资源“过度消耗”或者“虚拟消耗”。
上代码,具体展示“过度消耗/虚拟消耗”问题:
public class TestTicketRunnable{
public static void main(String[] a){
TicketThread tThread = new TicketThread();
new Thread(tThread).start();
new Thread(tThread).start();
new Thread(tThread).start();
}
};
class TicketThread implements Runnable {
private int ticket = 5;
public void run(){
for (int i = 0; i < 5; i++){
if (ticket > 0){
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票:ticket = " + ticket--);
}
}
}
};
运行结果:Thread-0卖票:ticket = 5
Thread-2卖票:ticket = 5 //虚拟消耗
Thread-1卖票:ticket = 4
Thread-1卖票:ticket = 2
Thread-2卖票:ticket = 3
Thread-0卖票:ticket = 3 //虚拟消耗
Thread-0卖票:ticket = -1 //过度消耗
Thread-1卖票:ticket = 1
Thread-2卖票:ticket = 0
如上所见,票一共5张,三个线程调用买票,线程1网上卖了售票第1张,紧接着线程2线下也卖了“第一张”出现了“虚拟消耗”的问题;线程3黄牛党卖了最后1张票,线程1网上又卖了最后1张,出现了“过度消耗”的问题,这两种问题都是实际生活中不可能发生的,但是在这个3个线程执行中却出现了,那一定是有问题的,问题的根源就在于,三个渠道的“售票员”不在一个频道上办事,或者说没有相互之间同步所共享的资源,导致这一问题的根本原因,就是相互之间实现方式不同步。
二、使用synchronized实现同步机制
synchronized关键字:Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。它包括两种用法:synchronized 方法和 synchronized 块。
即实现线程间同步的方式有两种:
①使用synchronized同步代码块;
②使用synchronized关键字创建synchronized()方法
下面分别进行解析,对上面售票的代码进行改造:
①代码——使用synchronized同步代码块
class TicketThread implements Runnable {
private int ticket = 5;
public void run(){
for (int i = 0; i < 5; i++){
synchronized(this){
if (ticket > 0){
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票:ticket = " + ticket--);
}
}
}
}
}
②代码——使用synchronized关键字创建synchronized()方法
class TicketThreadMethod implements Runnable {
private int ticket = 5;
public void run(){
for (int i = 0; i < 5; i++){
this.sale();
}
}
public synchronized void sale(){
if (ticket > 0){
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票:ticket = " + ticket--);
}
}
}
三、synchronized方法和synchronized同步代码块的区别:
- synchronized同步代码块只是锁定了该代码块,代码块外面的代码还是可以被访问的。
- synchronized方法是粗粒度的并发控制,某一个时刻只能有一个线程执行该synchronized方法。
- synchronized同步代码块是细粒度的并发控制,只会将块中的代码同步,代码块之外的代码可以被其他线程同时访问。