当多线程访问共享数据时,就可能会出现线程安全问题
使用 synchronized 是解决线程安全的其中一种方式
1.synchronized 同步代码块语法
synchronized(锁对象){
可能出现线程安全问题的代码(即访问了共享数据的代码);
}
注意事项:
- 同步代码块的 “锁对象” 可以是任意类型的对象
- 必须保证多个线程使用的是同一个锁对象
- 锁对象的作用:只让一个线程在同步代码块中执行
2. synchronized 方法
作用和代码块差不多,写法是成员方法的语法, 在返回值修饰符前加 synchronized
可以理解为,它是一个用 this 作为锁对象的同步代码块,方便重复调用
访问修饰符 synchronized 返回值修饰符 方法名(参数列表){
//方法体;
}
3. synchronized 静态方法
作用和代码块差不多,写法是静态方法的语法, 在返回值修饰符前加 synchronized
可以理解为, 它是使用 本类的class属性, 即class文件对象作为锁对象的同步代码块
访问修饰符 static synchronized 返回值修饰符 方法名(参数列表){
//方法体;
}
4.例子(仅使用同步代码块示例,因为后两者实质相同,只是语法稍有差异)
创建一个 RunnableImpl 类,共有100张票,不断售卖直到票数等于0
创建一个 main 方法,让 3 个线程同时卖票,此时也是线程安全的
如果去掉 synchronized 代码块,那么就会出现卖不存在的票,重复卖票的情况
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private int ticket = 100;
//创建一个锁对象
Object obj = new Object();
//设置线程任务:卖票
@Override
public void run() {
//使用死循环,让卖票操作重复执行
while(true){
synchronized(obj) {
//先判断票是否存在
if(ticket>0){
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
public static void main(String[] args) {
//创建Runnable接口的实现类对象
RunnableImpl run = new RunnableImpl();
//创建Thread类对象,构造方法中传递Runnable接口的实现类对象
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
//调用start方法开启多线程
t0.start();
t1.start();
t2.start();
}
}