文档章节

JAVA多线程问题【2】 synchronized锁机制

南栀安
 南栀安
发布于 2017/09/05 18:12
字数 1513
阅读 2
收藏 0
点赞 0
评论 0

    虽然在工作过程中没有涉及到多线程的逻辑,所以此文参照五月的仓颉的博客在此整理JAVA多线程的知识点。(原文地址:http://www.cnblogs.com/xrq730/p/4851350.html

多线程的安全问题:

脏读

一个常见的概念。在多线程中,难免会出现在多个线程中对同一个对象的实例变量进行并发访问的情况,如果不做正确的同步处理,那么产生的后果就是"脏读",也就是取到的数据其实是被更改过的。

比如最常见的好几个窗口卖火车票的问题,比如三个窗口同时销售10张火车票,火车票数应该根据实际的卖票数进行减数。

可以使用同步锁来作处理。

一、使用synchronized块

package epm.am.demo.utils;
 
public class Station extends Thread {
    //通过构造方法给线程名字赋值
    public Station(String name) {
         super(name);// 给线程名字赋值
    }
    //为了保持票数的一致,票数要静态
    static int tick = 20;
    //创建一个静态钥匙
    static Object ob = "aa";//值是任意的
    //重写run方法,实现买票操作
    @Override
    public void run() {
        while (tick > 0) {
            synchronized (ob) {// 这个很重要,必须使用一个锁,
                // 进去的人会把钥匙拿在手上,出来后才把钥匙拿让出来
                if (tick > 0) {
                    System.out.println(getName() + "卖出了第" + tick + "张票");
                    tick--;
                } else {
                    System.out.println("票卖完了");
                }
            }
            try {
                 sleep(1000);//休息一秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    /**
     * java多线程同步锁的使用
     * 示例:三个售票窗口同时出售10张票
     * */
    public static void main(String[] args) {
        //实例化站台对象,并为每一个站台取名字
         Station station1=new Station("窗口1");
         Station station2=new Station("窗口2");
         Station station3=new Station("窗口3");
     
        // 让每一个站台对象各自开始工作
         station1.start();
         station2.start();
         station3.start();
    }
}

console_show:

窗口1卖出了第20张票
窗口2卖出了第19张票
窗口3卖出了第18张票
窗口3卖出了第17张票
窗口1卖出了第16张票
窗口2卖出了第15张票
窗口1卖出了第14张票
窗口3卖出了第13张票
窗口2卖出了第12张票
窗口1卖出了第11张票
窗口2卖出了第10张票
窗口3卖出了第9张票
窗口1卖出了第8张票
窗口2卖出了第7张票
窗口3卖出了第6张票
窗口1卖出了第5张票
窗口3卖出了第4张票
窗口2卖出了第3张票
窗口1卖出了第2张票
窗口2卖出了第1张票
票卖完了

二、使用synchronized方法

package epm.am.demo.utils;

public class ThreadDomain13 {
	 private int num = 0;
	
	public  void addNum(String userName)
    {	
		System.out.println("userName:"+userName);
        try
        {
            if ("a".equals(userName))
            {
                num = 100;
                System.out.println("a顾客已经付钱了!");
                Thread.sleep(2000);
            }
            else
            {
                num = 200;
                System.out.println("b顾客已经付钱了!");
            }
            System.out.println(userName + " 顾客num = " + num + "元");
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
	
	public static void main(String[] args)
	{
	    ThreadDomain13 td = new ThreadDomain13();
	    MyThread13_0 mt0 = new MyThread13_0(td);
	    MyThread13_1 mt1 = new MyThread13_1(td);
	    mt0.start();
	    mt1.start();
	}
}
package epm.am.demo.utils;

public class MyThread13_0 extends Thread {
	 private ThreadDomain13 td;
	    
	    public MyThread13_0(ThreadDomain13 td)
	    {
	    	System.out.println("建立对象A");
	        this.td = td;
	    }
	    
	    public void run()
	    {
	    	System.out.println("调用runA");
	        td.addNum("a");
	    }
}
package epm.am.demo.utils;

public class MyThread13_1 extends Thread {
	 private ThreadDomain13 td;
	    
	    public MyThread13_1(ThreadDomain13 td)
	    {
	        this.td = td;
	    }
	    
	    public void run()
	    {
	        td.addNum("b");
	    }
}

console_show: 
建立对象A
调用runA
userName:a
userName:b
a顾客已经付钱了!
b顾客已经付钱了!
b 顾客num = 200元
a 顾客num = 200元

由上述的结果可以看出:本来按照逻辑应该打印出a顾客付钱100元,但是实际结果并不是这样,这是线程安全问题。

1. mt0先运行,再赋值“a”,之后开始sleep;

2. mt0线程sleep的2秒时间里,mt1线程已经开始运行了,赋值b,把num赋值为200,并打印出b用了‘200’元;

3. mt0 sleep完毕后,num已经被赋值为200,所以打印出‘a 顾客num = 200元’。

为了解决此问题,可以在方法中加sychronized同步锁。

代码如下:

package epm.am.demo.utils;

public class ThreadDomain13 {
	 private int num = 0;
	
	public synchronized  void addNum(String userName)
    {	
		System.out.println("userName:"+userName);
        try
        {
            if ("a".equals(userName))
            {
                num = 100;
                System.out.println("a顾客已经付钱了!");
                Thread.sleep(2000);
            }
            else
            {
                num = 200;
                System.out.println("b顾客已经付钱了!");
            }
            System.out.println(userName + " 顾客num = " + num + "元");
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
	
	public static void main(String[] args)
	{
	    ThreadDomain13 td = new ThreadDomain13();
	    MyThread13_0 mt0 = new MyThread13_0(td);
	    MyThread13_1 mt1 = new MyThread13_1(td);
	    mt0.start();
	    mt1.start();
	}
}

console_show:

情况一:

建立对象A
调用runA
userName:a
a顾客已经付钱了!
a 顾客num = 100元
userName:b
b顾客已经付钱了!
b 顾客num = 200元

情况二:

建立对象A
userName:b
b顾客已经付钱了!
b 顾客num = 200元
调用runA
userName:a
a顾客已经付钱了!
a 顾客num = 100元

多个对象多个锁

public static void main(String[] args)
	{
	    //ThreadDomain13 td = new ThreadDomain13();
	    ThreadDomain13 td0 = new ThreadDomain13();
	    ThreadDomain13 td1 = new ThreadDomain13();
	    MyThread13_0 mt0 = new MyThread13_0(td0);
	    MyThread13_1 mt1 = new MyThread13_1(td1);
	    mt0.start();
	    mt1.start();
	}
}

把上述main函数中的方法改成多个线程对象。

那么输出为

建立对象A
调用runA
userName:a
userName:b
a顾客已经付钱了!
b顾客已经付钱了!
b 顾客num = 200元
a 顾客num = 100元

原因其实很简单,

这里有一个重要的概念。关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法(函数)当作锁,哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所属对象的锁,其他线程都只能呈等待状态。但是这有个前提:既然锁叫做对象锁,那么势必和对象相关,所以多个线程访问的必须是同一个对象

如果多个线程访问的是多个对象,那么Java虚拟机就会创建多个锁,就像上面的例子一样,创建了两个ThreadDomain13对象,就产生了2个锁。既然两个线程持有的是不同的锁,自然不会受到"等待释放锁"这一行为的制约,可以分别运行addNum(String userName)中的代码。

© 著作权归作者所有

共有 人打赏支持
南栀安
粉丝 1
博文 20
码字总数 5177
作品 0
绍兴
Java多线程学习(二)synchronized关键字(2)

系列文章传送门: Java多线程学习(一)Java多线程入门 Java多线程学习(二)synchronized关键字(1) java多线程学习(二)synchronized关键字(2) Java多线程学习(三)volatile关键字 Ja...

一只蜗牛呀
04/16
0
0
synchronized与ThreadLocal

synchronized是实现java的同步机制。同步机制是为了实现同步多线程对相同资源的并发访问控制。保证多线程之间的通信。 同步的主要目的是保证多线程间的数据共享。同步会带来巨大的性能开销,...

bigYuan
2013/07/18
0
2
Java多线程学习(四)等待/通知(wait/notify)机制

系列文章传送门: Java多线程学习(一)Java多线程入门 Java多线程学习(二)synchronized关键字(1) java多线程学习(二)synchronized关键字(2) Java多线程学习(三)volatile关键字 Ja...

一只蜗牛呀
04/16
0
0
Java多线程下 ThreadLocal 的应用实例

ThreadLocal很容易让人望文生义,想当然地认为是一个“本地线程” 。其实,ThreadLocal并不是一个 Thread,而是 Thread 的局部变量,也许把它命名为 ThreadLocalVariable更容易让人理解一些。...

空云万里晴
2014/01/06
0
0
java.lang.ThreadLocal类研究

java.lang.ThreadLocal类研究 1、概述 ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是thread local variable(线程局部变量)。也许把它命名为...

SDK4
2011/09/17
0
2
关于Java里面多线程同步的一些知识

# 关于Java里面多线程同步的一些知识 对于任何Java开发者来说多线程和同步是一个非常重要的话题。比较好的掌握同步和线程安全相关的知识将使得我们则更加有优势,同时这些知识并不是非常容易...

欧阳海阳
07/13
0
0
深入研究java.lang.ThreadLocal类

一、概述 ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局...

FoxHu
2012/05/08
0
0
Java 8新特性探究(十)StampedLock将是解决同步问题的新宠

Java8就像一个宝藏,一个小的API改进,也足与写一篇文章,比如同步,一直是多线程并发编程的一个老话题,相信没有人喜欢同步的代码,这会降低应用的吞吐量等性能指标,最坏的时候会挂起死机,...

OSC闲人
2014/05/13
0
30
Java 编程之美:并发编程基础晋级篇

本文来自作者 加多 在 GitChat 上分享 「Java 并发编程之美:并发编程基础晋级篇」 编辑 | Mc Jin 借用 Java 并发编程实践中的话,编写正确的程序并不容易,而编写正常的并发程序就更难了! ...

gitchat
04/18
0
0
java ThreadLocal

JDKAPI 解释: 该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其get或set方法)的每个线程都有自己的局部变量,它独立于初始化变量的副本...

smallsun512
2013/06/26
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

shell及python脚本方式登录服务器

一、问题 在工作过程中,经常会遇见需要登录服务器,并且因为安全的原因,需要使用交互的方式登录,而且shell、python在工作中也经常用到,并且可以提供交互的功能。都是利用了expect、spawn...

yangjianzhou
9分钟前
0
0
upstream sent too big header while reading...

nginx 报错:1736 upstream sent too big header while reading response header from upstream 1. 一般处理 location ~ \.php$ { #增加下面两句 fastcgi_buffer_size 128k; ......

dubox
20分钟前
0
0
Python解析配置文件模块:ConfigPhaser

import configparser as pa# [SectionA]# a = aa# b = bb# c = cc# [SectionB]# optionint = 1# optionfloat = 1.1# optionstring = string#https://www.cnblogs.com/a......

易野
26分钟前
0
0
Java基础——面向对象

声明:本栏目所使用的素材都是凯哥学堂VIP学员所写,学员有权匿名,对文章有最终解释权;凯哥学堂旨在促进VIP学员互相学习的基础上公开笔记。 Object的方法: clone() Object 克隆 to Strin...

凯哥学堂
28分钟前
0
0
rabbitmq学习记录(八)消息发布确认机制

RabbitMQ服务器崩了导致的消息数据丢失,已经持久化的消息数据我们可以通过消息持久化来预防。但是,如果消息从生产者发送到vhosts过程中出现了问题,持久化消息数据的方案就无效了。 Rabbit...

人觉非常君
33分钟前
0
0
毕业5年,我是怎么成为年薪30W的运维工程师

#转载# 我在大学读的是计算机专业,但大学毕业之后,进入到一家私企进行工作,工作的内容类似于网管,会经常的去修电脑,去做水晶头等内容。刚开始工作,也没想太多,最想的是丰富自己的工作...

Py爱好
40分钟前
1
0
大数据基础知识,大数据学习,涉及的知识点

一、什么是大数据 一种规模大到在获取、存储、管理、分析方面大大超出了传统数据库软件工具能力范围的数据集合,具有海量的数据规模、快速的数据流 转、多样的数据类型和价值密度低四大特征。...

董黎明
55分钟前
0
0
Linux CentOS 7上安装极点五笔

话说几天前在新买的惠普笔记本上成功地安装了Linux CentOS 7操作系统、Nvidia Quandro P600驱动程序及X Window,并在VMware下安装Red Hat教学环境,彻底跳出Windows的苦海,但仍然有一件事不...

大别阿郎
今天
17
0
2018年7月20日集群课程

一、集群介绍 集群,简单地说是指一组(若干个)相互独立的计算机,利用高速通信网络组成一个较大的计算机服务系统,每个集群节点(即集群中的每台计算机)都是运行各自服务的独立服务器。 ...

人在艹木中
今天
0
0
spark开发机中调试snappy

目的 在Idea中的点击运行,使spark可以直接读取snappy 自己编译hadoop,以支持snappy的压缩。 自己编译的目的就是要得到支持snappy文件读写的动态链接库。如果可以在网上下载,可以跳过自行编...

benny周
今天
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部