一、介绍       

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

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

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

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

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

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

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

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

//逻辑代码

}

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

 二、实例       

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

1、未使用synchronized锁的情况

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

抢票的线程代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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--);
    }
}

测试线程代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
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这样错误的数据。

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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方法测试,测试结果如下:抢票结果正确没问题。

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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方法测试,测试结果如下:抢票结果正确没问题。

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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这样错误的数据。

1
2
3
4
5
6
7
8
9
10
11
12
黄牛抢到了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的参数需要是对象,所以把基本类型包装成引用类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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)锁定资源不正确也是线程不安全的

1
2
3
4
5
6
7
8
9
10
11
12
13
黄牛抢到了10
农民工抢到了9
程序员抢到了8
农民工抢到了7
黄牛抢到了6
程序员抢到了6
黄牛抢到了5
农民工抢到了4
程序员抢到了3
黄牛抢到了2
程序员抢到了1
农民工抢到了0
黄牛抢到了-1
 三、总结       

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

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

//需要锁的区域

}

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

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

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

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

线程的同步之Synchronized的使用的更多相关文章

  1. 线程的同步之Synchronized在单例模式中的应用

    synchronized在单例模式中的使用 在单例模式中有一种懒汉式的单例,就是类初始化的时候不创建对象.等第一次获取的时候再创建对象.这种单例在单线程下是没有问题的获取的也都是同一个对象.但是如果放 ...

  2. JAVA之旅(十三)——线程的安全性,synchronized关键字,多线程同步代码块,同步函数,同步函数的锁是this

    JAVA之旅(十三)--线程的安全性,synchronized关键字,多线程同步代码块,同步函数,同步函数的锁是this 我们继续上个篇幅接着讲线程的知识点 一.线程的安全性 当我们开启四个窗口(线程 ...

  3. Java基础-多线程-③线程同步之synchronized

    使用线程同步解决多线程安全问题 上一篇 Java基础-多线程-②多线程的安全问题 中我们说到多线程可能引发的安全问题,原因在于多个线程共享了数据,且一个线程在操作(多为写操作)数据的过程中,另一个线程 ...

  4. Java:多线程,线程同步,synchronized关键字的用法(同步代码块、非静态同步方法、静态同步方法)

    关于线程的同步,可以使用synchronized关键字,或者是使用JDK 5中提供的java.util.concurrent.lock包中的Lock对象.本文探讨synchronized关键字. sy ...

  5. Java多线程之线程同步【synchronized、Lock、volatitle】

    线程同步 线程同步:当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态,实现线程同步的方法有很多. ...

  6. Java线程:线程的同步-同步方法

    Java线程:线程的同步-同步方法   线程的同步是保证多线程安全访问竞争资源的一种手段. 线程的同步是Java多线程编程的难点,往往开发者搞不清楚什么是竞争资源.什么时候需要考虑同步,怎么同步等等问 ...

  7. Java线程:线程的同步与锁

    一.同步问题提出 线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏. 例如:两个线程ThreadA.ThreadB都操作同一个对象Foo对象,并修改Foo对象上的数据. public ...

  8. java线程(2)--同步和锁

    参考转载:http://rainyear.iteye.com/blog/1734311 http://turandot.iteye.com/blog/1704027 http://www.cnblog ...

  9. java 线程数据同步

    java 线程数据同步 由买票实例 //java线程实例 //线程数据同步 //卖票问题 //避免重复卖票 //线程 class xc1 implements Runnable{ //定义为静态,可以 ...

随机推荐

  1. web前端几个小知识点笔记

    1.css实现宽度是百分比的盒子为正方形 <div style="width:50%;padding-bottom:50%;height:0px;background:#ccc;&qu ...

  2. #C++初学记录(初识汉诺塔)

    汉诺塔 题目 用1,2,...,n表示n个盘子,称为1号盘,2号盘,....号数大盘子就大.经典的汉诺塔问 题经常作为一个递归的经典例题存在.可能有人并不知道汉诺塔问题的典故.汉诺塔来源于 印度传说的 ...

  3. js 打印软件 Lodop

    官网首页:http://www.c-lodop.com/index.html 下载页面里有使用手册可下载.

  4. 20145314郑凯杰《网络对抗技术》实验1 逆向及Bof基础实践

    20145314郑凯杰<网络对抗技术>实验1 逆向及Bof基础实践 1.1 实践目标 本次实践的对象是一个名为pwn1的linux可执行文件. 该程序正常执行流程是:main调用foo函数 ...

  5. 20145221 《Java程序设计》实验报告一:Java开发环境的熟悉(Windows+IDEA)

    20145221 <Java程序设计>实验报告一:Java开发环境的熟悉(Windows+IDEA) 实验要求 使用JDK编译.运行简单的Java程序: 使用IDEA 编辑.编译.运行.调 ...

  6. 20172305 2018-2019-1 《Java软件结构与数据结构》第二周学习总结

    20172305 2018-2019-1 <Java软件结构与数据结构>第二周学习总结 教材学习内容总结 本周内容主要为书第三章和第四章的内容: 第三章(以数组来替代栈的作用) 集合(聚集 ...

  7. VS+SVN版本控制

    1.下载VisualSVN工具并安装 https://www.visualsvn.com/visualsvn/download/ 2.将代码传到svn服务器中 3.查看项目svn地址,直接项目右键属性 ...

  8. Matlab绘图基础——利用axes(坐标系图形对象)绘制重叠图像 及 一图多轴(一幅图绘制多个坐标轴)

    描述 axes在当前窗口中创建一个包含默认属性坐标系 axes('PropertyName',propertyvalue,...)创建坐标系时,同时指定它的一些属性,没有指定的使用DefaultAxe ...

  9. [bug report] 当springboot报错 找不到类 javax.xml.bind.JAXBException

    <!--以下四个依赖均是javax.xml.bind.JAXBException的依赖 在java6/7/8默认支持,java9不再支持--> <dependency> < ...

  10. POJ 3468 A Simple Problem with Integers(线段树:区间更新)

    http://poj.org/problem?id=3468 题意: 给出一串数,每次在一个区间内增加c,查询[a,b]时输出a.b之间的总和. 思路: 总结一下懒惰标记的用法吧. 比如要对一个区间范 ...