一、线程安全问题:

并发编程的原则:设计并发编程的目的是为了使程序获得更高的执行效率,但绝不能出现数据一致性(数据准确)问题,如果并发程序连最基本的执行结果准确性都无法保证,那并发编程就没有任何意义。

为什么会出现数据不正确:

  如果一个资源(变量,对象,文件,数据库)可以同时被很多线程使用就会出现数据不一致问题,也就是我们说的线程安全问题。这样的资源被称为共享资源或临界区。

  举个例子:

    一个共享变量m,现在有两个线程同时对它进行累加操作,各执行10000次,那么我么期待的结果是20000,但实际上并不是这样的。看代码:

package com.jalja.base.threadTest;

public class SynchronizedTest implements Runnable{
private static volatile int m=0;
public static void main(String[] args) {
Runnable run=new SynchronizedTest();
Thread thread1=new Thread(run);
Thread thread2=new Thread(run);
thread1.start();
thread2.start();
try {
//join() 使main线程等待这连个线程执行结束后继续执行下面的代码
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m的最终结果:"+m);
}
public void run() {
for(int i=0;i<10000;i++){
m++;
}
}
}

无论运行多少次 m总是小于20000。为什么会出现这样的结果呢?当线程thread1在将m++的结果写入内存之前,线程thread2已经从内存中读取了m的值,并在这个值(过时值)上进行++操作,最后将m=1写入内存中(可能就覆盖了thread1计算的m=1的值,也可能是出现thread1覆盖了thread2的值)。出现这样的结果是必然的。
如何控制多线程操作共享数据引起的数据准确性问题呢?使用“序列化访问临界资源”的方案,即在同一时刻,只能有一个线程访问临界资源,也称作同步互斥访问,也就是保证我们的共享资源每次只能被一个线程使用,一旦该资源被线程使用,其他线程将不得拥有使用权。在Java中,提供了两种方式来实现同步互斥访问:synchronized和Lock。

二、互斥访问之synchronized(同步方法或者同步块)

  互斥锁:顾名思义,就是互斥访问目的的锁。

  举个简单的例子:如果对临界资源加上互斥锁,当一个线程在访问该临界资源时,其他线程便只能等待。

  在Java中,每一个对象都拥有一个锁标记(monitor),也称为监视器,多线程同时访问某个对象时,只有拥有该对象锁的线程才能访问。

  在Java中,可以使用synchronized关键字来标记一个需要同步的方法或者同步代码块,当某个线程调用该对象的synchronized方法或者访问synchronized代码块时,这个线程便获得了该对象的锁,其他线程暂时无法访问这个方法,只有等待这个方法执行完毕或者代码块执行完毕,这个线程才会释放该对象的锁,其他线程才能执行这个方法或者代码块。通过这种方式达到我们上面提到的在同一时刻,只能有一个线程访问临界资源。

  synchronized用法:

  1、同步代码块

synchronized(synObject) {

    }

用法:将synchronized作用于一个给定的对象或类的一个属性,所以每当有线程执行这段代码块,该线程会先请求获取对象synObject的锁,如果该锁已被其他线程占有,那么新的线程只能等待,从而使得其他线程无法同时访问该代码块。

package com.jalja.base.threadTest;

public class SynchronizedTest implements Runnable{
private static volatile int m=0;
public static void main(String[] args) {
Runnable run=new SynchronizedTest();
Thread thread1=new Thread(run);
Thread thread2=new Thread(run);
thread1.start();
thread2.start();
try {
//join() 使main线程等待这连个线程执行结束后继续执行下面的代码
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m的最终结果:"+m);
}
public void run() {
synchronized (this) {
for(int i=0;i<10000;i++){
m++;
}
}
}
}

该代码是使用当前对象作为互斥锁,下面我们使用类的一个属性作为互斥锁。

package com.jalja.base.threadTest;

public class SynchronizedTest implements Runnable{
private static volatile int m=0;
private Object object=new Object();
public static void main(String[] args) {
Runnable run=new SynchronizedTest();
Thread thread1=new Thread(run);
Thread thread2=new Thread(run);
thread1.start();
thread2.start();
try {
//join() 使main线程等待这连个线程执行结束后继续执行下面的代码
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m的最终结果:"+m);
}
public void run() {
synchronized (object) {
for(int i=0;i<10000;i++){
m++;
}
}
}
}

1、同步方法

package com.jalja.base.threadTest;

public class SynchronizedTest implements Runnable{
private static int m=0;
public static void main(String[] args) {
Runnable run=new SynchronizedTest();
Thread thread1=new Thread(run);
Thread thread2=new Thread(run);
thread1.start();
thread2.start();
try {
//join() 使main线程等待这连个线程执行结束后继续执行下面的代码
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m的最终结果:"+m);
}
public synchronized void run() {
for(int i=0;i<10000;i++){
m++;
}
}
}

这段代码中,synchronzied作用于一个实例方法,就是说当线程在进入run()方法前,必须获取当前对象实例锁,本例中对象实例锁就是run。在这里提醒大家认真看这三段代码中main函数的实现,在这里我们使用Runnable创建两个线程,并且这两个线程都指向同一个Runnable接口实例,这样才能保证两个线程在工作中,使用同一个对象锁,从而保证线程安全。

一种错误的理解:

package com.jalja.base.threadTest;

public class SynchronizedTest implements Runnable{
private static int m=0;
public static void main(String[] args) {
Thread thread1=new Thread(new SynchronizedTest());
Thread thread2=new Thread(new SynchronizedTest());
thread1.start();
thread2.start();
try {
//join() 使main线程等待这连个线程执行结束后继续执行下面的代码
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m的最终结果:"+m);
}
public synchronized void run() {
for(int i=0;i<10000;i++){
m++;
}
}
}

这段代码的运行结果是错误的,请看main函数的实现方式,使用Runnable创建两个线程,但是两个线程拥有各自的Runnable实例,所以当thread1线程进入同步方法时加的是自己的对象实例锁,而thread2在进入同步方法时关注的是自己的实例锁,两个线程拥有不同的对象实例锁,因此无法达到互斥的要求。

略作改动:

package com.jalja.base.threadTest;

public class SynchronizedTest implements Runnable{
private static int m=0;
public static void main(String[] args) {
Thread thread1=new Thread(new SynchronizedTest());
Thread thread2=new Thread(new SynchronizedTest());
thread1.start();
thread2.start();
try {
//join() 使main线程等待这连个线程执行结束后继续执行下面的代码
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m的最终结果:"+m);
}
public void run() {
for(int i=0;i<10000;i++){
count();
}
}
public static synchronized void count(){
m++;
}
}

  这样处理结果就是我么想要的了,在这里我们将处理业务的代码封装成一个静态的同步方法,那现在访问该同步方法需要的是当前类的锁,而类在内存中只有一份,所以无论如何,他们使用的都是同一个锁(class级别的锁)。

三、synchronize的实现原理

深入分析Synchronized原理

java 线程安全 synchronized的更多相关文章

  1. java线程安全— synchronized和volatile

    java线程安全— synchronized和volatile package threadsafe; public class TranditionalThreadSynchronized { pu ...

  2. java 线程及synchronized关键字

         从本篇开始,我们将会逐渐总结关于java并发这一块的内容,也可以理解为是我的笔记,主要来自于一些博客和java书籍中的内容,所有的内容都是来自于他们之中并且加上了我自己的理解和认识.     ...

  3. Java线程同步synchronized的理解

    JVM中(留神:马上讲到的这两个存储区只在JVM内部与物理存储区无关)存在一个主内存(Main Memory),Java中所有的变量存储在主内存中,所有实例和实例的字段都在此区域,对于所有的线程是共享 ...

  4. java线程之——synchronized的注意细节

    我在学习synchronized的时候,十分好奇当一个线程进入了一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法? 然后就做了个实验(实验代码最后贴出),最后得到了如下 ...

  5. 关于java线程锁synchronized修饰普通方法与静态方法的区别

    最近研究线程方面问题,关于这个synchronized锁修饰的问题,先是修饰普通方法,然后通过两个线程,各自执行自己对象的锁,发现方法执行互不影响,代码如下: private static int n ...

  6. java线程总结--synchronized关键字,原理以及相关的锁

    在多线程编程中,synchronized关键字非常常见,当我们需要进行“同步”操作时,我们很多时候需要该该关键字对代码块或者方法进行锁定.被synchronized锁定的代码块,只能同时有一条线程访问 ...

  7. Java 线程 — synchronized、volatile、锁

    线程同步基础 synchronized 和volatile是Java线程同步的基础. synchronized 将临界区的内容上锁,同一时刻只有一个进程能访问该临界区代码 使用的是内置锁,锁一个时刻只 ...

  8. Java线程(二):线程同步synchronized和volatile

    上篇通过一个简单的例子说明了线程安全与不安全,在例子中不安全的情况下输出的结果恰好是逐个递增的(其实是巧合,多运行几次,会产生不同的输出结果),为什么会产生这样的结果呢,因为建立的Count对象是线程 ...

  9. JAVA 线程状态以及synchronized,wait,sleep,yield,notify,notifyAll

    java线程存在以下几种状态: 1: 创建状态(New):线程被new出来,还未调用start 2: 就绪状态(Runnable):又称为可执行状态,调用线程的start方法后,线程处于就绪状态,,线 ...

随机推荐

  1. iOS中AutoLayer自动布局流程及相关方法

    关于UIView的Layer,IOS提供了三个方法: 1.layoutSubviews 在iOS5.1和之前的版本,此方法的缺省实现不会做任何事情(实现为空),iOS5.1之后(iOS6开始)的版本, ...

  2. RCurl网络数据抓取

    观察基础信息(服务器信息和提交给服务器的信息) d=debugGatherer()xpath="http://123.sogou.com/"url=getURL(xpath,deb ...

  3. 解决ie6 fixed 定位以及抖动问题

    像你所遇到的问题一样, IE6浏览器有太多的bug让制作网页的人头疼.这篇文章介绍的是介绍的是如何解决IE6不支持position:fixed;属性的办法.如果我们需要做某个元素始终位于浏览器的底部, ...

  4. 自己动手做logo

    本文主要记录用 coreDraw 和ps 做公司logo . 我修改的logo.效果还不错. 1 矢量图 和位图的区别 http://jingyan.baidu.com/article/54b6b9c ...

  5. 百度star编程赛-练习1

    ztr loves math http://acm.hdu.edu.cn/showproblem.php?pid=5675 显然:4 ^ n * (a + b) * (a -b) #include & ...

  6. 填补Resources和WWW加载本地资源的坑

    总的来说Resources和WWW加载本地资源坑比较多,大多与路径有关. 下面代码构成了一个路径的预读模块: 此模块主要解决的坑是:Resources或WWW加载本地的文件夹中的多个文件时,无法获取文 ...

  7. 第5章 搭建S3C6410开发板的测试环境

    1.使用Eboot擦除NandFlash的方法如下: 第一步:准备工作 用串口线或USB转串口线连接开发板和PC,并启动minicom 第二步:进入Eboot状态 打开OK6410开发板的电源开关,过 ...

  8. 用手机自带uc浏览器查看静态页面,css样式不显示

    问题描述: 这个问题是一个同事在写手机页面用UC浏览器测试以后遇到的,其他浏览器静态页面显示正常,唯独UC浏览器不显示页面样式. 我测试过代码是没有问题的,因为临时没有找到安卓手机,就猜想PC端的应该 ...

  9. MySQL数据库权限操作指南

    -- 创建用户 CREATE USER 'dongrichtest' IDENTIFIED BY 'dongrichtest'; -- 新增后删除需要刷新权限 FLUSH PRIVILEGES; -- ...

  10. (原创)MongoDB之NoSQL简介

    Nosql简介1.1系统对数据的需求        Nosql[Nosql主要用途大数据处理]的全称为”not only sql”,为非关系型数据库[非关系型数据库就是关系型数据库的所有特点都没有了, ...