声明:该专栏本人重新过一遍java知识点时候的笔记汇总,主要是每天的知识点+题解,算是让自己巩固复习,也希望能给初学的朋友们一点帮助,大佬们不喜勿喷(抱拳了老铁!)
往期回顾
Java学习day23:线程构造方法、常用方法(例题+知识点详解)-CSDN博客
Java学习day22:进程和线程、并发并行、线程创建方式(知识点详解)-CSDN博客
Java学习day21:System类、Runtime类、Date类、Calendar类(知识点详解)-CSDN博客
Java学习day24:线程的同步和锁
一、同步和锁
1.为什么要进行线程的同步?
Java是允许多线程(多个线程),当多个线程操作同一个资源(咋操作)的时候,会导致得到或者打印的数据不准确。从而发生冲突。咋解决?加同步锁。
2.多线程操作导致数据不准确
比如,当三个线程美团、淘票票、猫眼同时卖票, 美团、淘票票这个两个线程,都去卖同一场次的票,结果美团卖出去一张1排1列的票
结果淘票票也卖出去了1排1列的票 你感觉合适吗?不合适的,分享同一个资源的时候,要保证分享资源的数据,合法性!!!否则就会出现数据的混乱,就是下面的这种结果!!!
示例:
class MySync implements Runnable {int ticket = 50;@Overridepublic void run() {//while (true) {//死循环if (ticket > 0) {System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket + "票");ticket--;} else {System.out.println("买完了");break;//终止循环!!!}}}
}
public class Demo1 {public static void main(String[] args) {MySync mySync = new MySync();//这三个线程Thread thread1 = new Thread(mySync, "线程1");thread1.start();Thread thread2 = new Thread(mySync, "线程2");thread2.start();Thread thread3 = new Thread(mySync, "线程3");thread3.start();}
}
运行结果:
这里就是,三个线程同时分享同一个资源,也就是卖同一组票,此时理想的情况应该是:先线程1进入到ticket=50,循环循环结束以后此时tiket=49了,循环第二次的时候线程2抢到资源了 此时ticket=49循环 打印49 tiket-- ticket=48了,循环第三次的时候 线程2 抢到资源了, 此时ticket=48......就这样循环下去。
而实际上会出现什么问题呢?
两个线程都进入到了循环了,此时两个线程的ticket都是50,但是两个线程都会往下执行,可能的状态就是,线程3抢占到cpu资源,循环一遍,ticket为49,然后线程3再次抢占到cpu资源,继续执行一遍循环,一直到线程3执行了4次循环,线程1才终于抢占到cpu资源,执行循环,但是由于线程1是在ticket为50的时候进入循环的,只是一直在等待,此时线程1输出的就是ticket=50
好好理解这个原理,线程的抢占式执行带来的结果,但是抢占式能够将cpu最大化的利用,此时怎么在继续抢占式的同时避免数据的混乱,这就引入了锁的概念。
示例:
//最理想的状态!!!
//先线程1进入到ticket=50,循环 循环结束以后 此时
//tiket=49了
//循环第二次的时候 线程2抢到资源了 此时ticket=49
//循环 打印49 tiket-- ticket=48了
//循环第三次的时候 线程2 抢到资源了, 此时ticket=48
//打印卖第48张票。ticket-- tiket=47
//线程1又抢到循环
//现在的情况是:有可能两个线程同时进入到while循环
//
class MySync implements Runnable {int ticket = 50;@Overridepublic void run() {//while (true) {//死循环//两个线程都进入到了循环了//此时两个线程所持有的ticket 都是50//但是两个线程都要往下执行//有可能线程1 先执行了sout(50) 线程2在等待哦!!!//线程1执行了--操作并出了循环 线程1ticket = 49//线程1又抢到循环了 sout(49) tiket--//再进入倒这个循环,有可能线程2抢到这个执行权//线程2要往下执行输出语句 ticket=50 打印50if (ticket > 0) {//线程具有抢占式的运行//咱们有没有可能,线程3进入到if语句//此时线程1也进入到if语句了//线程3去打印 卖出了50张票//在线程1里面 ticket=50//线程3又抢到ticket-- 又进入到循环了 ticket = 49//线程3又抢到了ticket-- 又进入倒循环 ticket=48//线程1又抢到资源要执行,执行输出语句 tiekct=50System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket + "票");ticket--;} else {System.out.println("买完了");break;//终止循环!!!}}}
}
public class Demo1 {public static void main(String[] args) {MySync mySync = new MySync();//这三个线程Thread thread1 = new Thread(mySync, "线程1");thread1.start();Thread thread2 = new Thread(mySync, "线程2");thread2.start();Thread thread3 = new Thread(mySync, "线程3");thread3.start();}
}
3.解决方法
3.1.同步方法:使用一个关键字synchronized修饰方法。
因为Java对象都有一个内置的锁对象。当使用这个关键字修饰方法的时候,这个方法就会被锁保护起来,被锁锁住,当一个线程进来以后,会立马锁住当前的方法。意味着只有一个线程进来,其他线程都在外面等着。
语法格式:
public synchronized void run () {
}
我们把run方法加上锁,但是你会发现,最后所有的票都是一个线程买完的,原因很简单,一个线程进去了其他的进不去,进去的那个会一直占有资源直到结束,才会释放其占有的资源,也就意味着会把票卖完,这就不符合生活场景,比如三个平台卖票的,结果有一个平台抢到后就不松手,自己一个人卖完了所有。
3.2同步代码块
就是用了synchronized 关键字修饰一个语句块。被修饰的语句块会被加锁。从而实现同步。
语法格式:
synchronized (this) {
被加锁的代码块
}
这个时候我们再想,锁哪一部分的代码块,肯定不能锁while循环,不然放进去一个线程,这个线程要把while循环执行完才出来。
示例:
class MySync2 implements Runnable {int ticket = 500;//对这个run方法加了锁 就意味着只有一个线程进入到run方法中//其他线程都在run方法外面等待@Overridepublic void run() {//能不能对循环加锁?不能 因为循环加锁以后,还是一个线程循环完,没有任何意义while (true) {//死循环//if语句加了锁以后//就意味着只有一个线程进入到if语句//假如线程1进入if语句了,线程2和线程3就会等待//线程1打印50 并-- ticket变量为49//线程2抢到了49 sout(49) tiket-- 48//其他线程再抢!!!//核心业务 加了锁,只让一个线程进入,操作完以后。锁释放掉//然后这三个线程再抢。还只能进一个,再操作核心业务synchronized (this) {//只能让一个线程进入操作,其他线程在外面等待排队if (ticket > 0) {System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket + "票");ticket--;} else {System.out.println("买完了");break;//终止循环!!!}}}}
}
public class Demo3 {public static void main(String[] args) {MySync2 mySync = new MySync2();//这三个线程Thread thread1 = new Thread(mySync, "线程1");thread1.start();Thread thread2 = new Thread(mySync, "线程2");thread2.start();Thread thread3 = new Thread(mySync, "线程3");thread3.start();}
}
所以在这个案例里,真正应该锁的,是while循环里面的if语句,三个线程都能进while循环,但是只有一个线程能进if语句执行卖票操作,执行完就要释放资源,然后三个线程又抢,看哪个能进去。
所以很重要的一点,需要准确知道把锁加在哪里。
总而言之,加锁的目的为了保证数据的准确性。
案例:
卖电影票:
三个线程:
淘票票
美团
猫眼
100张票
参考:
class SaleTicket implements Runnable {//声明一个变量票//静态的变量和对象没有关系了private static int ticket = 100;@Overridepublic void run() {while (true) {//美团 猫眼 淘票票synchronized (this) {if (ticket > 0) {System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket + "票"); ticket--;} else {System.out.println("卖完了");break;}}}}
}
public class Demo4 {public static void main(String[] args) {SaleTicket saleTicket = new SaleTicket();new Thread(saleTicket, "淘票票").start();new Thread(saleTicket, "美团").start();new Thread(saleTicket, "猫眼").start();}
}
线程就是这样,抢占式运行导致不可控制,但是可以加锁。让他可控制。
以上,就是今天的所有知识点了。锁的问题,是Java中非常重要的核心,大家一定要自己去百度一些资料,增长自己的见识 ,比如lock(); unlock();等等,大家得多花点时间,静下心看代码,写代码,多理解。
加油吧,预祝大家变得更强!