线程安全(一)synchronize什么是线程同步?什么是线程安全?什么是线程锁?synchronized怎么用?如何理解wait()和sleep()的区别?超详细例程讲解

____________.                    ___.        .__     __. .____________. .________    .___      ___.
|████████████|                   /    \      |   \   |  | |████████████| |   ___   \   \  \    /  /
     |  |                       /  /\  \     |    \  |  |      |  |      |  |    )  |   \  \  /  /
     |  |                      /  /__\  \    |  .   \|  |      |  |      |  |___/   /    \  \/  /
     |  |                     /  ______  \   |  |\   `  |      |  |      |   ___  <       \    /
     |  |     .____________. |  |      |  |  |  |  \    |      |  |      |  |   \  \       |  |
     |__|     |████████████| /__/      \__\  |__|    \__|      |__|      |__|    \__\      |__|

前言

想写这篇文章的缘由呢,是因为我自己在了解线程锁,线程同步的这些概念,以及线程同步的这些方法。我在查的很多博客要么重复一样的东西,要么一些很不生动的理论,还有一些是直接抛一个案例。不能说不对,只是作为一个小白的时候,很难理解。所以我想尝试从一个小白的角度去介绍一下我暂时所了解的这部分内容。其实这写内容都是相互关联的,有可能看了第一点,不明白,但是看完后面的点,串起来之后才是明白到的
注意: 如果你是做卷子答题,那还是不要cv这里的内容,因为这里只是在逼逼,或者举例说明,尝试从简单的例子去介绍这样一个听起来有点复杂的东西。

一、什么是【线程同步】?

我第一次看到这几个字,我的理解是几个线程在跑, 同步 同步不就是让他们跑的一样快。很遗憾,事实不是这样。
【解释】当多个线程在跑 ,并且这些线程用到同一个资源时,需要保证资源的安全。举个很简单的例子 银行存款有一百万【共同资源】,现在有十个人【多条线程】 同时来取钱,每个人都想取十万。每个人都直接修改这个【共同资源】的数据,一瞬间全部完全取款。结果银行存款【共同资源】剩下100-10=90万(因为瞬间还没等别人处理完,都拿到这个资源,并且去修改,所以每个人拿到的原始值都是100,算出的结果都是剩下90),每个人也都拿到钱,凭空生出了九十万,这明显是不对的,这是不安全的。
在这里插入图片描述
所以需要有很多手段去保证这个【共同资源】的安全,让他们一个一个使用这个资源,第一个取完剩下九十万,第二个取完剩下八十万…最终全部取万剩下零元。
在这里插入图片描述
那么保证资源安全的这些手段,称之为线程同步

二、什么是线程锁?synchronized

线程锁,它其实是为了保证【共享资源】安全,而出现的一个概念。我们要保证资源的安全,所以要把资源加锁,有人在用的时候锁死,不用的时候,开锁
好比如路边只有一个公共厕所,此时有5个人都要上厕所【共享资源】,有人在上厕所的时候就得把门锁死,要是不锁门,其他四个人也冲进去,多不安全,特别是漂亮的小女子,妈呀,我这口水,那场面多刺激。用完了得开锁,不把锁开起来后面几个人难道还得拉裤子?
java中如何加锁?
加锁的位置有好几个,也有的不同的关键字,这里先介绍一下synchronized 关键字有无作用在方法上的效果的一些比较。

2.1 没有加锁时

首先我们要测试没有使用锁时,同时运行同一个对象的普通方法效果如何。

/**
 * 没有使用锁时,同时运行同一个对象的普通方法效果如何
 */
public class TsetSyn1 {
    public static void main(String[] args) {
        TsetSyn1 ts = new TsetSyn1();
        new Thread(new Runnable() {
            @Override
            public void run() {
                ts.test1();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                ts.test1();
            }
        }).start();
    }
    /**
     * 没有加锁的普通方法
     */
    public void test1() {
        System.out.println("普通方法执行");
        try {
            Thread.sleep(2000);//延时使得方法没有那么快运行结束
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("普通方法结束");
    }
}

可以看到执行结果

普通方法执行
普通方法执行
普通方法结束
普通方法结束

由此可见,两个线程的方法是同时在跑的。虽然运行的是同一个方法,但是第二个线程运行方法是没有等待第一个线程运行完方法就开始运行了。

2.2 锁加在普通方法上时,使用同一个对象调用。

我们尝试把 synchronized 关键字加在普通方法上,使用同一个对象调用。

/**
 * 把 synchronized 关键字加在普通方法上,使用同一个对象调用。
 */
public class TestSyn2 {
    public static void main(String[] args) {
        TestSyn2 ts = new TestSyn2();
        new Thread(new Runnable() {
            @Override
            public void run() {
                ts.test2();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                ts.test2();
            }
        }).start();
    }
    /**
     * 加锁的普通方法
     */
    public synchronized void test2() {
        System.out.println("普通加锁方法执行");
        try {
            Thread.sleep(2000);//延时使得方法没有那么快运行结束
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("普通加锁方法结束");
    }
}

可以看到执行结果

普通加锁方法执行
普通加锁方法结束
普通加锁方法执行
普通加锁方法结束

此时我们可以看到加锁之后,同一个对象中,枷锁的方式,两个线程同时要使用时,需要等前一个线程使用结束后才可以执行这个方法。

2.3 锁加在普通方法上时,使用两个不同的对象调用。

/**
 * 把 synchronized 关键字加在普通方法上,使用两个不同的对象调用。
 */
public class TestSyn3 {
    public static void main(String[] args) {
        TestSyn3 ts = new TestSyn3();
        TestSyn3 ts2 = new TestSyn3();
        new Thread(new Runnable() {
            @Override
            public void run() {
                ts.test2();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                ts2.test2();
            }
        }).start();
    }
    /**
     * 加锁的普通方法
     */
    public synchronized void test2() {
        System.out.println("普通加锁方法执行");
        try {
            Thread.sleep(2000);//延时使得方法没有那么快运行结束
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("普通加锁方法结束");
    }
}

可以看到执行结果

普通加锁方法执行
普通加锁方法执行
普通加锁方法结束
普通加锁方法结束

可以看到,此时的结果和使用一个对象调用方法,且没有加锁运行的结果是一样的。

2.4 锁加在静态方法上时,使用两个不同的对象调用。

/**
 * 把 synchronized 关键字加在static方法上,使用两个不同的对象调用。
 */
public class TestSyn4 {
    public static void main(String[] args) {
        TestSyn4 ts = new TestSyn4();
        TestSyn4 ts2 = new TestSyn4();
        new Thread(new Runnable() {
            @Override
            public void run() {
                ts.test2();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                ts2.test2();
            }
        }).start();
    }
    /**
     * 加锁的普通方法
     */
    public static synchronized void test2() {
        System.out.println("静态加锁方法执行");
        try {
            Thread.sleep(2000);//延时使得方法没有那么快运行结束
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("静态加锁方法结束");
    }
}

可以看到执行结果

静态加锁方法执行
静态加锁方法结束
静态加锁方法执行
静态加锁方法结束

此时我们又可以看到,调用这个方法又需要等待了。这是因为,静态方法它是属于类本身的。所有对象共用这个方法,而不是每个对象各自拥有的。因此将它锁住,即使是其他对象,也需要等待它被释放锁的时候才可以使用。
至于其他类锁什么的,还有其他关键字,这边暂不介绍,后面有时间再来补充修改一下,这边目的是认识一下锁的概念,让我们更好地阅读下文。

三、释放锁是什么样的?sleep()和wait()的区别?

我们在搜的很多文章也都会说道sleep和wait都是让程序睡一会儿,两者区别呢是前者不会释放锁,后者会释放锁。那么什么是释放锁呢。我们在前面其实也看到了没有释放锁的样子,因为我们前面用的都是sleep。
接下来我就写一个例程来对比试验
如何看出区别,在结果中对结果和类的内容进行讲解

3.1 测试类TestSleepWait

/**
 * 测试类
 */
public class TestSleepWait {
    public static void main(String[] args) {
        Person testP = new Person(); //这个类里面有是个带锁的方法,目的是多线程时,只有一个线程能够使用
        OneThread t1 = new OneThread(testP);//创建的这两个线程争夺这个资源,
        OneThread t2 = new OneThread(testP);
        t1.start();
        t2.start();
    }
}

3.2 线程类OneThread

/**
 * 线程中调用person类的synchronized修饰过的方法
 */
public class OneThread extends Thread  {
    private Person curPerson = null;
    public OneThread(Person person){
        this.curPerson = person;
    }

    @Override
    public void run(){
        System.out.println(Thread.currentThread().getName() + " call start");
        curPerson.display();
        System.out.println(Thread.currentThread().getName() + " call end");
    }
}

3.3 重点区别类,带锁的方法Person

/**
 * 方法逻辑,展示两种方法的区别
 */
public class Person {
    public synchronized void display(){
        for(int i=1;i<=5;i++){
            System.out.println(Thread.currentThread().getName() + " " + i+"time["+ System.currentTimeMillis()/1000+"]startsleep");
            try{
                Thread.sleep(2000);   // sleep是Thread类静态方法,不会释放对象锁
            }catch(Exception ex){

            }
            System.out.println(Thread.currentThread().getName() + " " + i+"time["+ System.currentTimeMillis()/1000+"]endsleep");
            if(i==3){
                System.out.println(Thread.currentThread().getName() + " " + i+"time["+ System.currentTimeMillis()/1000+"]startwait-----------");
                try{
                    this.wait(2000); // wait是Object类实例方法,会释放对象锁
                }catch(Exception ex){

                }
                System.out.println(Thread.currentThread().getName() + " " + i+"time["+ System.currentTimeMillis()/1000+"]endwait-----------");
            }
        }
    }
}

可以看到执行结果

Thread-0 call start
Thread-0 1time[1610893403]startsleep
Thread-1 call start
Thread-0 1time[1610893405]endsleep
Thread-0 2time[1610893405]startsleep
Thread-0 2time[1610893407]endsleep
Thread-0 3time[1610893407]startsleep
Thread-0 3time[1610893409]endsleep
Thread-0 3time[1610893409]startwait-----------
Thread-1 1time[1610893409]startsleep
Thread-1 1time[1610893411]endsleep
Thread-1 2time[1610893411]startsleep
Thread-1 2time[1610893413]endsleep
Thread-1 3time[1610893413]startsleep
Thread-1 3time[1610893415]endsleep
Thread-1 3time[1610893415]startwait-----------
Thread-0 3time[1610893415]endwait-----------
Thread-0 4time[1610893415]startsleep
Thread-0 4time[1610893417]endsleep
Thread-0 5time[1610893417]startsleep
Thread-0 5time[1610893419]endsleep
Thread-0 call end
Thread-1 3time[1610893419]endwait-----------
Thread-1 4time[1610893419]startsleep
Thread-1 4time[1610893421]endsleep
Thread-1 5time[1610893421]startsleep
Thread-1 5time[1610893423]endsleep
Thread-1 call end

解析

我截取前面8行的部分,可以看到,两个线程都启动了,但是由于两个线程要跑的都是person类中的display方法,且这个方法用synchronized修饰过,我们在main方法中传的是同一个person对象给两个线程,因此这个display方法同时只能有一个线程使用
从前面的部分,我们很明显看到,线程0和线程1都启动了,但是只有线程0一直在sleep,每次sleep2秒,这就说明调用sleep的时候这个方法没有被让出来,只不过整个程序都停在那边等。线程0依旧在使用display方法,线程1在等待display方法被释放锁

Thread-0 call start
Thread-0 1time[1610893403]startsleep
Thread-1 call start
Thread-0 1time[1610893405]endsleep
Thread-0 2time[1610893405]startsleep
Thread-0 2time[1610893407]endsleep
Thread-0 3time[1610893407]startsleep
Thread-0 3time[1610893409]endsleep

接着我们可以看到线程0调用了wait()方法,紧接着就轮到了线程1开始调用sleep方法,同样没有释放出display这里就很明显的说明了,wait方法会释放锁,其他线程就可以来争抢这个资源了,剩下的部分一次类推。

Thread-0 3time[1610893409]startwait-----------
Thread-1 1time[1610893409]startsleep
Thread-1 1time[1610893411]endsleep
Thread-1 2time[1610893411]startsleep
Thread-1 2time[1610893413]endsleep
Thread-1 3time[1610893413]startsleep
Thread-1 3time[1610893415]endsleep

四、工程

本章例程

虽然synchronized已经能狗达到锁的功能,但是它有一些缺陷,因此有了Lock。下一章节中介绍了lock的内容。
线程安全(二)Lock 什么是Lock线程锁?与synchronized区别在哪?Lock锁是如何实现等待通知的?如何实现线程顺序执行

  • 36
    点赞
  • 87
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值