首页 技术 正文
技术 2022年11月19日
0 收藏 611 点赞 3,038 浏览 5592 个字

   一、介绍       

线程的同步:一般的并发指的就是多个线程访问同一份资源。多个线程同时访问(修改)同一份资源的话,就会有可能造成资源数据有误。

如果多个线程访问多个不同资源,就不会造成线程同步。

如果要解决这个问题,就需要对线程使用同步存取。java中提供了一个synchronized关键字来对方法或者某个块加锁。从而达到锁定某个区域,不可

同时修改以免数据有误的情况。

synchronized关键字可以锁定的部分:

1、锁定方法:在方法上加入synchronized关键字就表明在使用该方法的时候需要获取相应的锁。

2、锁定块:锁定块的参数需要是对象,不可是基本类型数据

synchronized(引用类型变量 | this | 对象.class){

//逻辑代码

}

上图表示非同步线程和同步线程的比较,可以看出非同步的时候,线程1和线程2都是在同一个时间段访问同一个transter方法,而使用了同步之后,线程2如果想调用transter方法就必须等待线程1调用完成后才可执行。

 二、实例       

这里以12306抢票代码为例来说明线程同步的synchronized关键字的使用。

1、未使用synchronized锁的情况

首先来看未使用synchronized的情况会是什么样?

抢票的线程代码:

123456789101112131415161718192021222324 class Web12306 implements Runnable{    private int num=10;//总共10张票    private boolean flag = true;    @Override    public void run() {        while(flag){            //黄牛抢到了3    农民工抢到了1 黄牛抢到了0  程序员抢到了-1            test1();//线程不安全,数据不准确:结果有-1值        }    }    //1、线程不安全    public void test1(){        if (num<=0) {            flag = false;            return;//跳出循环,结束        }        try {            Thread.sleep(500);//模拟延时        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println(Thread.currentThread().getName()+"抢到了"+num--);    }}

测试线程代码:

12345678910111213 public class SynDemo1 {    public static void main(String[] args) {        //真实角色        Web12306 web = new Web12306();        //代理角色        Thread proxy1 = new Thread(web,"黄牛");        Thread proxy2 = new Thread(web,"程序员");        Thread proxy3 = new Thread(web,"农民工");        proxy1.start();        proxy2.start();        proxy3.start();    }}

测试结果如下:可以看出最后的结果会出现0和-1这样错误的数据。

123456789101112 黄牛抢到了10农民工抢到了8程序员抢到了9黄牛抢到了7农民工抢到了6程序员抢到了5黄牛抢到了4农民工抢到了3程序员抢到了2黄牛抢到了1农民工抢到了0程序员抢到了-1

为什么会出现这样的数据呢?

因为现在三个线程都启动了,都是在运行状态中访问test1方法,修改其中的num值。因为他们三个会同时都会进入该方法的情况,所以修改的数据也会出现当:黄牛抢走了1,这时候农民工和程序员还在test1方法里,他俩也会对num进行–操作。所以,最后的结果就是0和-1

2、使用synchronized关键字锁定方法:

线程修改抢票代码,在test1方法上加入synchronized关键字,使该方法锁定。调用时需要先获取锁(线程安全)

1234567891011121314151617181920212223 class Web12306 implements Runnable{    private int num=10;//总共10张票    private boolean flag = true;    @Override    public void run() {        while(flag){            test2();//线程安全,数据准确        }    }    //2、方法锁:加上synchronized表示线程安全的    public synchronized void test2(){        if (num<=0) {            flag = false;            return;//跳出循环,结束        }        try {            Thread.sleep(500);//模拟延时        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println(Thread.currentThread().getName()+"抢到了"+num--);    }}

继续使用上面的main方法测试,测试结果如下:抢票结果正确没问题。

12345678910 黄牛抢到了10黄牛抢到了9黄牛抢到了8黄牛抢到了7黄牛抢到了6黄牛抢到了5黄牛抢到了4黄牛抢到了3黄牛抢到了2农民工抢到了1

3、使用synchronized锁定代码块:锁定当前对象

继续修改抢票代码,在方法内部使用synchronized锁定块

12345678910111213141516171819202122232425 class Web12306 implements Runnable{    private int num=10;//总共10张票    private boolean flag = true;    @Override    public void run() {        while(flag){            test3();//线程安全,数据准确        }    }    //3、锁定块:当前对象也就是Web12306    public void test3(){        synchronized(this){//锁定当前对象            if (num<=0) {                flag = false;                return;//跳出循环,结束            }            try {                Thread.sleep(500);//模拟延时            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println(Thread.currentThread().getName()+"抢到了"+num--);        }    }}

继续使用上面的main方法测试,测试结果如下:抢票结果正确没问题。

12345678910 黄牛抢到了10黄牛抢到了9黄牛抢到了8黄牛抢到了7黄牛抢到了6黄牛抢到了5黄牛抢到了4黄牛抢到了3黄牛抢到了2农民工抢到了1

4、使用synchronized锁定代码块:锁定部分代码块

可以看出test3方法是使用synchronized关键字锁定了整个方法区域。那如果就只锁定一部分呢?这里假如只锁定if(num<=0)这个判断部分

12345678910111213141516171819202122232425 class Web12306 implements Runnable{    private int num=10;//总共10张票    private boolean flag = true;    @Override    public void run() {        while(flag){            test4();//线程不安全,数据不准确:出现-1 【锁定范围不正确】        }    }    //4、使用synchronized锁定部分资源    public void test4(){        synchronized(this){            if (num<=0) {                flag = false;                return;//跳出循环,结束            }        }//只锁定到此        try {            Thread.sleep(500);//模拟延时        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println(Thread.currentThread().getName()+"抢到了"+num--);    }}

使用main方法测试结果如下:可以看出最后的结果同样也会出现0和-1这样错误的数据。

123456789101112 黄牛抢到了10农民工抢到了8程序员抢到了9黄牛抢到了7农民工抢到了6程序员抢到了5黄牛抢到了4农民工抢到了3程序员抢到了2黄牛抢到了1农民工抢到了0程序员抢到了-1

分析下为什么会出现这样的结果?我们知道test4中只锁定了if这部分。假设现在程序num现在等于1

1.此时线程A,B,C三个线程都会进入到12行,if判断的部分。A先进来拿到了锁,判断此时num=1 。然后释放锁走到18行,try的部分

2.线程A在18行try部分并没有对num–操作。此时线程B也进入到了12行拿到了锁。也到了18行。现在18行是A,B两个线程。A往下执行拿走了num

等线程B再去拿num的时候,num已经等于0了。

3.同理,C再去拿num的时候num已经是0-1 = -1了。

5、使用synchronized锁定部分资源:只锁定num变量

由于synchronized的参数需要是对象,所以把基本类型包装成引用类型

12345678910111213141516171819202122232425 class Web12306 implements Runnable{    private int num=10;//总共10张票    private boolean flag = true;    @Override    public void run() {        while(flag){            test5();//线程不安全,数据不准确:出现重复数据【锁定范围不正确】        }    }    //5、使用synchronized锁定部分资源:锁定num变量    public void test5(){        synchronized((Integer)num){            if (num<=0) {                flag = false;                return;//跳出循环,结束            }        }        try {            Thread.sleep(500);//模拟延时        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println(Thread.currentThread().getName()+"抢到了"+num--);    }}

使用main方法测试结果如下:可以看出最后的结果会出现重复数据(两个6)锁定资源不正确也是线程不安全的

12345678910111213 黄牛抢到了10农民工抢到了9程序员抢到了8农民工抢到了7黄牛抢到了6程序员抢到了6黄牛抢到了5农民工抢到了4程序员抢到了3黄牛抢到了2程序员抢到了1农民工抢到了0黄牛抢到了-1

 三、总结       

1、synchronized关键字表示锁,可以加在方法上或者一个代码块中

synchronized(引用类型变量 | this | 对象.class){

//需要锁的区域

}

2、不加synchronized关键字的方法是线程不安全的

加了synchronized表示线程安全,线程安全的话会降低效率。因为共享的资源被加了锁,会有锁等待时间

3、在加synchronized代码块的时候需要注意,注意锁的范围。

范围太大—–>会降低效率。范围太小——>线程不安全

来自为知笔记(Wiz)

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,085
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,560
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,409
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,182
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,819
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,902