当我们在多条线程共同操作共享数据会出现线程不安全的情况。
1.引入synchronized
关键字 synchronized
1.可以保证在同一个时刻,只有一个线程可以执行某个方法或者某个代码块。
2.可保证一个线程的变化(主要是共享数据的变化)被其他线程所看到(保证可见性)
3.synchronized锁的是1.对象2.class
2.synchronized的三种应用方式
- 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁
- 修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
- 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得 给定对象的锁。
例1:
//synchronized 锁的对象是方法的调用者(phone)
两个方法用的同一把锁谁先拿到谁执行。所以执行的结果必是
先发短信后是打电话
import java.util.concurrent.TimeUnit;
public class Test1 {
public static void main(String[] args) {
Phone phone =new Phone();
new Thread(()->{
phone.sendSms();
},"A").start();
new Thread(()->{
phone.call();
},"B").start();
}
}
class Phone{
//synchronized 锁的对象是方法的调用者(phone)
//两个方法用的同一把锁谁先拿到谁执行
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
例2:模拟简单的买票问题给 消售方法加上synchronized
线程操纵资源类
public class SaleTicketDemo01 {
//并发,多线程操作同一个资源类,把资源类丢入线程
//lambda表达式(参数)->{代码}
public static void main(String[] args) {
Ticket ticket =new Ticket();
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"C").start();
}
}
//资源类
class Ticket{
//属性 方法
private int number =30;
//买票的方式
//synchronized本质是:队列,锁 //锁的是 1 对象 2 Class
public synchronized void sale(){
if (number>0){
System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余:"+number);
}
}
}
synchronized的缺点:
1.无法判断获取锁的状态。
2.虽然会自动释放锁,但如果如果锁的那个方法执行时间较长就会一直占用着不去释放,不能让使用同一把锁的方法继续执行,影响程序的运行如(例1)。(会锁住某一段程序,别的程序如果需要调用的话就必须等待,减少了速度、效率)。
3.使用synchronized,当多个线程尝试获取锁时,未获取到锁的线程会不断的尝试获取锁,而不会发生中断,这样会造成性能消耗。
4.有可能产生死锁,导致程序中断。