线程同步机制
1、在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性
2、也可以这样理解 :线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作
同步具体方法-Synchronized
1、同步代码块
synchronized(对象){ //得到对象的锁,才能操作同步代码
//需要被同步代码
}
2、synchronized还可以放在方法声明中,表示整个方法-为同步方法
public synchronized void m(String name){
//需要被同步的代码
}
3、如何 理解
就好像某小伙伴上厕所前先把门头上(上锁),完事后再出来 (解锁),那么其它小伙伴就可以使用厕所了
4、使用synchronized解决售票问题
/**
* @ClassName SellTicket
* @Description 使用多线程模拟三个窗口同时售票100张
* @Author 小黄debug
* @Date 2022/3/20 16:58
* @Version 1.0
**/
public class SellTicket {
public static void main(String[] args) {
// SellTicket01 sellTicket01 = new SellTicket01();
// SellTicket01 sellTicket02 = new SellTicket01();
// SellTicket01 sellTicket03 = new SellTicket01();
//
// //出现票数超卖
// sellTicket01.start();
// sellTicket02.start();
// sellTicket03.start();
//使用接口方式查看是否存在超卖
// SellTicker02 sellTicker02 = new SellTicker02();
// new Thread(sellTicker02).start();
// new Thread(sellTicker02).start();
// new Thread(sellTicker02).start();
SellTicker03 sellTicker03 = new SellTicker03();
new Thread(sellTicker03).start();
new Thread(sellTicker03).start();
new Thread(sellTicker03).start();
}
}
//实现接口方式,使用synchronized实现线程同步
class SellTicker03 implements Runnable{
//为什么这里不用静态,是因为SellTicker02只new了一次,所有的都ticketNum都指向这里
private int ticketNum = 100; //让多个线程共享ticketNum
private boolean loop = true;//控制 run方法的变量
Object object = new Object();
//同步方法(静态的)的锁为当前类本身
//1、public synchronized static void m1(){}锁是加在SellTicket03.class
//2、如果在静态方法中,实现一个同步代码块
public synchronized static void m1(){
}
public static void m2(){
synchronized (SellTicker03.class){
}
}
//说明
//1、public synchronized void sell(){}就是一个同步方法
//2、这时锁在this对象
//3、也可以在代码块上写synchronized,同步代码块,互斥锁还是在this对象
public /*synchronized*/ void sell(){ //同步方法,在同一时刻,只能有一个线程来执行run方法
synchronized(/*this*/object) {
if (ticketNum <= 0) {
System.out.println(Thread.currentThread().getName() + "售票结束 。。");
loop = false;
return;
}
System.out.println("窗口" + Thread.currentThread().getName()
+ "售出一张票,剩余票数" + (--ticketNum));
}
}
@Override
public void run() {
while(loop){
sell(); //sell方法是一个同步方法
try { //将休眠放出来,否则会一直一个线程卖票
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class SellTicket01 extends Thread{
private static int ticketNum = 100; //让多个线程共享ticketNum
@Override
public void run() {
while(true){
if(ticketNum <= 0){
System.out.println("售票结束 。。");
break;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口"+Thread.currentThread().getName()
+"售出一张票,剩余票数"+(--ticketNum));
}
}
}
class SellTicker02 implements Runnable{
//为什么这里不用静态,是因为SellTicker02只new了一次,所有的都ticketNum都指向这里
private int ticketNum = 100; //让多个线程共享ticketNum
@Override
public void run() {
while(true){
if(ticketNum <= 0){
System.out.println("售票结束 。。");
break;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口"+Thread.currentThread().getName()
+"售出一张票,剩余票数"+(--ticketNum));
}
}
}
互斥锁:synchronized又被称为互斥锁
基本介绍
1、在Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
2、每个对象都对应于一个可称为”互斥锁"的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象
3、关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问
4、同步的局限性:导致程序的执行效率要降低
5、同步方法(非静态的)的锁可以是this,是可以是其他对象(要求是同一对象)
6、同步方法(静态的)的锁为当前类本身
注意事项和细节
1、同步方法如果没有使用static修饰:默认锁对象为this
2、如果方法使用static修饰,默认锁对象:当前类.class
3、实现落地步骤:
需要先分析上锁的代码
选择同步代码块或同步方法
要求多个线程的锁对象为同一个即可!
线程的死锁
多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生
应用案例
妈妈:你先完成作业,才让你玩手机
小明:你先让我玩手机,我才完成作业
/**
* @ClassName DeadLock_
* @Description 模拟线程死锁
* @Author 小黄debug
* @Date 2022/3/20 22:50
* @Version 1.0
**/
public class DeadLock_ {
public static void main(String[] args) {
//模拟一个死锁现象
DeadLockDemo A = new DeadLockDemo(true);
DeadLockDemo B = new DeadLockDemo(false);
A.setName("A线程");
B.setName("B线程");
A.start();
B.start();
}
}
class DeadLockDemo extends Thread{
static Object o1 = new Object();
static Object o2 = new Object();
boolean flag;
public DeadLockDemo(boolean flag){
this.flag = flag;
}
@Override
public void run() {
//下面业务逻辑分析
//1。如果flag为T,线程A就会得到/持有o1对象锁,然后尝试去获取o2对象锁
//2。如果线程A得不到o2对象锁,就会Blockecd
//3。如果flag为F,线程B就会得到/持有o2对象锁,然后尝试去获取o1对象锁
//4。如果线程B得不到o1对象锁,就会Blocked
if(flag){
synchronized (o1){
System.out.println(Thread.currentThread().getName()+"进入了1");
synchronized (o2){
System.out.println(Thread.currentThread().getName()+"进入了2");
}
}
}else{
synchronized (o2){
System.out.println(Thread.currentThread().getName()+"进入了3");
synchronized ((o1)){
System.out.println(Thread.currentThread().getName()+"进入了4");
}
}
}
}
}
如何避免,一个线程不要去拿两个锁
释放锁
下面操作会释放锁
1、当前线程的同步方法、同步代码块执行结束
案例:上厕所,完整出来
2、当前线程在同步代码块、同步方法中遇到break、return.
案例:没有正常的完整,经理叫他修改bug,不得已出来
3、当前线程在同步代码块、同步方法中出现了未处理的Erro或Exception,导致异常结束
案例:没有正常的完事,发现忘带纸,不得已出来
4、当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁
案例:没有正常完整,觉得需要酝酿下,所以出来 等会再进去
下面操作不会释放锁
1、线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁
案例:上厕所,太困了,在才坑位上眯了一会
2、线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁
提示:应尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用