一、介绍       

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

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

如果要解决这个问题,就需要对线程使用同步存取。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. mysql 数据操作 单表查询 简单查询 避免重复DISTINCT

    创建数据库company create database company charset=utf8; use company; company.employee 员工id id int 姓名 emp_ ...

  2. rest-framework框架的基本组件

    快速实例 Quickstart 大致步骤 (1)创建表,数据迁移 (2)创建表序列化类BookSerializer class BookSerializer(serializers.Hyperlink ...

  3. 自定义 Repository 方法

    为某一个 Repository 上添加自定义方法 步骤: 定义一个接口: 声明要添加的, 并自实现的方法 提供该接口的实现类: 类名需在要声明的 Repository 后添加 Impl, 并实现方法 ...

  4. (16)Cocos2d-x 多分辨率适配完全解析

    Overview 从Cocos2d-x 2.0.4开始,Cocos2d-x提出了自己的多分辨率支持方案,废弃了之前的retina相关设置接口,提出了design resolution概念. 3.0中有 ...

  5. cocos代码研究(21)Widget子类Text,TextAtlas,TextBMFont学习笔记

    理论基础 Text类又称ttf格式文本,可以用ttf文件或者系统自带字体,支持文字多,但是ttf文件格式体积大,渲染速度慢: TextBMFont类又称fnt格式文本,纹理创建,根据纹理上有的文字来显 ...

  6. jQuery中的prop和attr区别

    最近在做一个项目用jq时发现一个问题  在谷歌中可以正常出效果  但是在火狐中就是不行 就是这个prop和attr   之前用的是attr方法   但是在火狐中不出效果  于是特意看了两者的区别 主要 ...

  7. Python Missing parentheses in call to 'print'

    原来是因为Python2.X和Python3.X不兼容. 我安装的是Python3.X,但是我试图运行的却是Python2.X 的代码. 所以上面的语法在python3中是错误的.在python3中, ...

  8. Solr DIH query 工作流

    本文地址 http://www.cnblogs.com/jasonxuli/p/6491270.html DataImportHandler (DIH) 支持全量数据导入和增量数据导入,主要有四个qu ...

  9. 20145319 《网络对抗》逆向与Bof基础

    20145319 逆向与Bof实验 1 实验内容 本次实验以可执行文件pwn1为例,将对pwn1进行反汇编的基础上进行功能上的解读,并进行缓冲区溢出攻击 可执行文件pwn1的正常流程是主函数调用foo ...

  10. linux下 ip指令

    目录 Network ip command Command :ip 简介 内容 Network ip command Command :ip 简介 ip 是個指令喔!並不是那個 TCP/IP 的 IP ...