一 基本概念

多个线程并发读写同一个临界资源时会发生线程并发安全问题
当多个线程同时访问同一种共享资源时,可能会造成数据的覆盖等不一致性问题,此时就需要对线程之间进行通信和协调,该机制就叫做线程的同步机制

异步操作:多线程并发的操作,各自独立运行
同步操作:多线程串行的操作,先后执行的顺序。

二 实现方式

使用synchronized关键字,就可以把有线程安全问题的代码锁起来,这样就可以达到同一时刻 仅有一个线程访问这段代码的目的。

把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可

实现方式一:

使用同步代码块的方式实现部分代码的锁定,格式如下

synchronized(类类型的引用){
编写所有需要锁定的代码
}

类类型的引用:类的对象类型的成员变量

实现方式二:

使用同步方法的方式实现所有代码的锁定。直接使用synchronized关键字来修饰整个方法即可,格式如下:
同步方法
修饰符synchronized 返回值类型 方法名(方法参数){方法体;}
同步方法的锁对象是 this

同步静态方法
修饰符 static synchronized 返回值类型 方法名(方法参数) {方法体;}
同步静态方法的锁对象是 类名.class

三 案例演示:

某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票

同步代码块:

 

public class Tickets implements Runnable{
int ticket = 100;
@Override
public void run() {
    while (true) {
        //谁先抢到就进行加锁(锁的是这个资源对象)  ->锁的就是当前的这张票
        synchronized (this) {           //使用同步代码块保证票的唯一性
           // 判断,票数大于0就卖票,并告知是哪个窗口卖的
            if (ticket > 0) {
                try {
         //让当前线程休眠,让其他线程抢到CPU执行权。
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "还剩下:" + ticket + "张票");
                //卖了票之后,总票数要减1
                ticket--;
            } else {
                //票卖没了,线程停止
                break;
            }
        }
    }
}
}}
同步方法:
public class SellTicket implements Runnable {

    int ticket = 100;
    boolean flag = true;

    @Override
    public void run() {
        while (flag) {
            //调用卖票的方法(同步方法)
              sell();
        }
    }

    public synchronized void sell() {
        if (ticket > 0) {
        System.out.println(Thread.currentThread().getName() + "还剩下:" + ticket + "张票");
           ticket--;
         try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }   
        } else {
           flag = false;
         }  
    }
}
测试类:
public class SellTicketTest {
public static void main(String[] args) {
   //高并发,一个“cpu”(一个资源)
   SellTicket ticket=new SellTicket();
    //一个资源,三个线程线程使用,三个窗口共卖100张票
    Thread t1=new Thread(ticket,"窗口1");
    Thread t2=new Thread(ticket,"窗口2");
    Thread t3=new Thread(ticket,"窗口3");
    t1.start();
    t2.start();
    t3.start();
}

}

四 同步的好处和弊端

  • 好处:解决了多线程的数据安全问题
  • 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

五 线程安全类和不安全类:

当多个线程访问某个方法时,不管这些线程如何交替执行,这个类的结果行为都是我们设想的正确行为,我们就可以说这个类是线程安全类。

线程安全表示它是唯一的,例如Vector & ArrayList:

Vector的方法都是同步的,是线程安全的,即某一时刻只有一个线程能够写Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,访问它比访问ArrrayList慢。ArrayList的方法不是同步的。

注意事项:

使用synchronized保证线程同步应当注意
①多个需要同步的线程在访问同步块时,看到的应该是同一个锁对象引用。
②在使用同步块时,应当减少同步范围以提高并发的执行效率。
(同步方法的加锁范围小,颗粒度更小,定位更细;同步代码块加锁范围大)