线程同步:多个线程操作同一个资源
所以线程同步也有另外一种广为人知的叫法:并发!!!!!!!!!
并发:同一个对象被多个线程同时操作
我们首先要知道:每一个对象都有一把锁
举个例子说明:现实生活中,我们会遇到”同一个资源,多个人都想使用”的问题,比如,食堂排队打饭,每个人都想吃饭,最天然的解决办法就是,排队.一个个来
在实际的线程编程之中我们会遇到,多个线程访问同一个对象,并且某些线程还想修改这个对象这时候我们就需要线程同步.线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池 形成队列,等待前面线程使用完毕,下一个线程再使用
由上面所述,并发要形成的条件就是队列+锁
那么如何让并发开启呢?只需要在某个线程访问时加入锁机制synchronized(关键字),当一个线程获得对象的排它锁,独占资源,其他线程必须等待使用后释放锁即可。但存在以下问题
1.一个线程持有锁会导致其他所有需要此锁的线程挂起
2.在多线程竞争下,加锁,释放锁会导致比较多的上下文切换 和 调度延时,引起性能问题
3.如果一个优先级高的线程等待一个优先级低的线程释放锁 会导致优先级倒置,引起性能问题
简而言之就是:线程用了锁(synchronized)就会牺牲掉业务执行的性能,但保证了数据的稳定运输不紊乱。与之相反的是:如果有很多线程,但不使用锁(synchronized),虽然有了性能,但是数据会发生紊乱,不安全。
下面举例说明不安全的情况
6.1创建 一个卖票的类实现Runnable接口——>既然卖票就需要有“票”这个变量——>既然是有动作发生那么我们需要标志位变量让其能停止——>可拆写一个卖票的方法,然后在run()方法里面运行它
implements Runnable{
// Scanner in = new Scanner(System.in);
//票,先写变狼
private int ticket =10;
boolean flag = true; // 外部停止方式
@Override
public void run() {
while (flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
void buy() throws InterruptedException {
if (ticket <= 0) {
flag = false;
return;
}
//模拟延时
Thread.sleep(100);
//买票,
System.out.println(Thread.currentThread().getName() + "拿到第" + ticket-- + "票");
}
}
6.2 创建一个main类,引入实现Runnable接口的类的对象,并创建几个线程,模拟多人购票的场景,里面里面传入name变量是因为使用了:Thread.currentThread().getName()
//不安全的买票
public class UnsafeBuyTicket {
public static void main(String[] args) {
//先把子线程的对象new 出来
BuyTicket station = new BuyTicket();
//然后写子线程的启动
new Thread(station,"小谢").start(); //第一条子线程
new Thread(station,"蜡笔").start(); //第二条子线程
new Thread(station,"小新").start(); //第三条子线程
}
}
结果分析
因为没有给被多个线程执行的资源(在这个代码里面就是“票”)加锁,所以出现了有人买到第0票的结果和多人买到同一张票的结果
6.3修改,在run()方法之中加入标识符 synchronized
class BuyTicket implements Runnable {
private int ticket = 10;
boolean flag = true;
@Override
public synchronized void run() { //这里加入synchronized
while (flag) {
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
synchronized void buy() throws InterruptedException {
if (ticket <= 0) {
flag = false;
return;
}
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "拿到第" + ticket-- + "票");
}
}
第二个结果分析
由于对执行操作的方法添加了锁的标识符,所以结果没有溢出,但却造成了死锁的问题。