通过上一小节的学习,了解到同步代码块可以有效解决线程的安全问题,当把共享资源的操作放在 synchronized 定义的区时,便为这些操作加了同步锁。在方法面前同样可以使用 synchronized 关键字来修饰,被修饰的方法为同步方法,它能实现与同步代码块相同的功能,具体语法格式如下。
synchronized 返回值类型 方法名([参数 1,...]){}
被 synchronized 修饰的方法在某一时刻只允许一个线程访问,访问该方法的其他线程都会发生阻塞,直到当前线程访问完毕后,其他线程才有机会执行该方法。
接下来通过一个案例来学习同步方法的使用,如下所示。
public class example12 {
public static void main(String[] args) {
//创建线程的任务类对象并开启线程
TicketWinodw task = new TicketWinodw();
new Thread(task, "窗口1").start();
new Thread(task, "窗口2").start();
new Thread(task, "窗口3").start();
new Thread(task, "窗口4").start();
}
}
class TicketWinodw implements Runnable {
private int tickets = 1000;
@Override
public void run() {
while (true) {
sendTicket();
}
}
//定义售票的方法
public synchronized void sendTicket() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + " --- 卖出的票" + tickets--);
} else {
System.exit(0);
}
}
}
运行结果如下所示。
上述代码块中,将售票代码抽取为售票方法 sendTicket(),并用 synchronized 关键字把 sendTicket()方法修饰为同步方法,然后在 run()方法中调用该方法。上图所示的运行结果同样没有出现 0 号和负数号的票,说明同步方法实现了和同步代码块一样的效果。
Think
同步代码块的锁是自己定义的任意类型的对象,那么同步方法是否也存在锁?如果有,它的锁是什么呢?答案是肯定的,同步方法也有锁,它的锁就是**当前调用该方法的对象,也就是 this 指向的对象。这样做的好处是,同步方法被所有线程锁共享,方法所在的对象相当于所有线程来说是唯一的,从而保证了锁的唯一性。当一个线程执行该方法时,其他的线程就不能进入该方法中,直到这个线程执行完该线程为止。**从而达到了线程同步的效果。
有时候需要同步的方法是静态方法,静态方法不需要创建对象就可以直接用 类名.方法名()
的方式调用。如果不创建对象,静态同步方法的锁就不会是 this 指向的对象,那么静态同步方法的锁是什么?Java 中静态方法的锁是该方法所在类的 class 对象,该对象在装载该类时自动创建,该对象可以直接用 类名.class
的方式获取。
用同步代码块和同步方法解决多线程问题有好处也有弊端。同步解决了多个线程同时访问共享数据时的数据安全问题,只要加上同一个锁,在同一时间内只能有一条线程执行。但是线程在执行同步代码时每次都会判断锁的状态,非常消耗资源,效率较低。