Java学习day24:线程的同步和锁(例题+知识点详解)

声明:该专栏本人重新过一遍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;
    @Override
    public 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();
    }
}

运行结果:

Java学习day24:线程的同步和锁(例题+知识点详解)_第1张图片

这里就是,三个线程同时分享同一个资源,也就是卖同一组票,此时理想的情况应该是:先线程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;
    @Override
    public void run() {
        //
        while (true) {//死循环
            //两个线程都进入到了循环了
            //此时两个线程所持有的ticket 都是50
            //但是两个线程都要往下执行
            //有可能线程1 先执行了sout(50)  线程2在等待哦!!!
            //线程1执行了--操作并出了循环 线程1ticket = 49
            //线程1又抢到循环了 sout(49) tiket--
            //再进入倒这个循环,有可能线程2抢到这个执行权
            //线程2要往下执行输出语句  ticket=50 打印50
            if (ticket > 0) {
                //线程具有抢占式的运行
                //咱们有没有可能,线程3进入到if语句
                //此时线程1也进入到if语句了
                //线程3去打印 卖出了50张票
                //在线程1里面  ticket=50
                //线程3又抢到ticket-- 又进入到循环了  ticket = 49
                //线程3又抢到了ticket-- 又进入倒循环 ticket=48
                //线程1又抢到资源要执行,执行输出语句  tiekct=50
                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();


    }
}

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方法外面等待
    @Override
    public  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;
    @Override
    public 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();等等,大家得多花点时间,静下心看代码,写代码,多理解。

加油吧,预祝大家变得更强!

你可能感兴趣的:(java从0到1,java,学习,开发语言,java-ee,intellij-idea)