“非线程安全”其实会在多个线程对同一个对象中的实例变量进行并发访问的时候产生,产生的后果是脏读,也就是取到的数据是被更改过的。而“线程安全”就是以获得的实例变量的值是经过同步处理的,不会出现脏读的现象。

1、方法内的变量是线程安全的

“非线程安全”问题存在于“实例变量中”,如果是方法内的私有变量,则不存在“非线程安全”问题,所得结果也就是“线程安全”了。

package selfThread;

/**
* Created by liping.sang on 2017/1/17 0017.
*/
public class HasSelfPrivateNum {
public void addI(String username){
try{
int num=0;
if(username.equals("a")){
num=100;
System.out.println("a set over");
Thread.sleep(2000);
}else {
num=200;
System.out.println("b set over");
}
System.out.println(username + " num = "+num);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
package selfThread;

/**
* Created by liping.sang on 2017/1/17 0017.
*/
public class ThreadA extends Thread {
private HasSelfPrivateNum numRef; public ThreadA( HasSelfPrivateNum numRef) {
super();
this.numRef = numRef;
}
public void run(){
super.run();
numRef.addI("a");
}
}
package selfThread;

/**
* Created by Administrator on 2017/1/17 0017.
*/
public class ThreadB extends Thread{
private HasSelfPrivateNum numRef; public ThreadB(HasSelfPrivateNum numRef) {
super();
this.numRef = numRef;
}
public void run(){
super.run();
numRef.addI("b");
}
}
package selfThread;

/**
* Created by liping.sang on 2017/1/17 0017.
*/
public class Run {
public static void main(String [] args){
HasSelfPrivateNum numRef = new HasSelfPrivateNum();
ThreadA athread = new ThreadA(numRef);
athread.start();
ThreadB bthread = new ThreadB(numRef);
bthread.start();
}
}
b set over
b num = 200
a set over
a num = 100

可见,方法中的变量不存在非线性安全的问题,永远都是非线程安全的,这是方法内部的变量是私有的特性造成的。

2、实例变量非线程安全

如果多个线程共同访问一个对象中的实例变量,则有可能出现“非线程安全”问题。

package service;

public class HasSelfPrivateNum {

    private int num = 0;

    synchronized public void addI(String username) {
try {
if (username.equals("a")) {
num = 100;
System.out.println("a set over!");
Thread.sleep(2000);
} else {
num = 200;
System.out.println("b set over!");
}
System.out.println(username + " num=" + num);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} }
package extthread;

import service.HasSelfPrivateNum;

public class ThreadA extends Thread {

    private HasSelfPrivateNum numRef;

    public ThreadA(HasSelfPrivateNum numRef) {
super();
this.numRef = numRef;
} @Override
public void run() {
super.run();
numRef.addI("a");
} }
package extthread;

import service.HasSelfPrivateNum;

public class ThreadB extends Thread {

    private HasSelfPrivateNum numRef;

    public ThreadB(HasSelfPrivateNum numRef) {
super();
this.numRef = numRef;
} @Override
public void run() {
super.run();
numRef.addI("b");
} }
package test;

import service.HasSelfPrivateNum;
import extthread.ThreadA;
import extthread.ThreadB; public class Run { public static void main(String[] args) { HasSelfPrivateNum numRef = new HasSelfPrivateNum(); ThreadA athread = new ThreadA(numRef);
athread.start(); ThreadB bthread = new ThreadB(numRef);
bthread.start(); } }
a set over !
b set over !
b num = 200
a num =200

此时是两个线程同时访问一个没有同步的方法,如果两个线程同时操作对象中的实例变量,则有可能出现“非线程安全”问题,

只要在public void  addI(String username)方法前加关键字synchronized即可 。

在两个线程访问同一个对象中的同步方法时一定是线程安全的。

3、多个对象多个锁

package service;

public class HasSelfPrivateNum {

    private int num = 0;

    synchronized public void addI(String username) {
try {
if (username.equals("a")) {
num = 100;
System.out.println("a set over!");
Thread.sleep(2000);
} else {
num = 200;
System.out.println("b set over!");
}
System.out.println(username + " num=" + num);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} }
package extthread;

import service.HasSelfPrivateNum;

public class ThreadA extends Thread {

    private HasSelfPrivateNum numRef;

    public ThreadA(HasSelfPrivateNum numRef) {
super();
this.numRef = numRef;
} @Override
public void run() {
super.run();
numRef.addI("a");
} }
package extthread;

import service.HasSelfPrivateNum;

public class ThreadB extends Thread {

    private HasSelfPrivateNum numRef;

    public ThreadB(HasSelfPrivateNum numRef) {
super();
this.numRef = numRef;
} @Override
public void run() {
super.run();
numRef.addI("b");
} }
package test;

import service.HasSelfPrivateNum;
import extthread.ThreadA;
import extthread.ThreadB; public class Run { public static void main(String[] args) { HasSelfPrivateNum numRef1 = new HasSelfPrivateNum();
HasSelfPrivateNum numRef2 = new HasSelfPrivateNum(); ThreadA athread = new ThreadA(numRef1);
athread.start(); ThreadB bthread = new ThreadB(numRef2);
bthread.start(); } }
a set over !
b set over !
b num =200
a num =100

两个线程分别访问同一个类的两个不同实例的相同名称的同步方法 ,效果却是以异步的方式运行,上面,创建了两个业务对象,在系统中产生了两个锁,所以运行结果是异步的,结果是先打印b在打印a,

虽然在HasSelfPrivateNum中使用了synchronized关键字但却是异步的交叉的,这是因为关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法(函数)当做锁,所以在示例中,哪个线程先止血带synchronized关键字的方法,哪个线程就持有该方法所属对象的锁Lock,那么其他线程只能呈等待状态,前提是多个线程访问的是同一个对象,但如果多个线程访问的是多个对象,则JVM会创建多个锁。上面示例中创建了两个类的对象,所以就会产生2个锁。

4、synchronized方法与锁对象

package extobject;

public class MyObject {

    synchronized public void methodA() {
try {
System.out.println("begin methodA threadName="
+ Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("end");
} catch (InterruptedException e) {
e.printStackTrace();
}
} }
package extthread;

import extobject.MyObject;

public class ThreadA extends Thread {

    private MyObject object;

    public ThreadA(MyObject object) {
super();
this.object = object;
} @Override
public void run() {
super.run();
object.methodA();
} }
package extthread;

import extobject.MyObject;

public class ThreadB extends Thread {

    private MyObject object;

    public ThreadB(MyObject object) {
super();
this.object = object;
} @Override
public void run() {
super.run();
object.methodA();
}
}
package test.run;

import extobject.MyObject;
import extthread.ThreadA;
import extthread.ThreadB; public class Run { public static void main(String[] args) {
MyObject object = new MyObject();
ThreadA a = new ThreadA(object);
a.setName("A");
ThreadB b = new ThreadB(object);
b.setName("B"); a.start();
b.start();
} }

此时结果为:

begin methodA  threadName = A
begin methodB threadName = B
end
end

如果修改MyObject

package extobject;

public class MyObject {

    synchronized public void methodA() {
try {
System.out.println("begin methodA threadName="
+ Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public void methodB() {
try {
System.out.println("begin methodB threadName="
+ Thread.currentThread().getName()+"begin time=" +System.currentTimeMills());
Thread.sleep(5000);
System.out.println("end");
} catch (InterruptedException e) {
e.printStackTrace();
}
} }

在ThreadB中修改run方法中修改为Object.methodB();

1)A线程先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法

2)A线程先持有object对象的Lock锁,B线程如果在这时调用object对象中的synchronized类型的方法则需等待,也就是同步。

5、脏读

在上面的例子中为了避免数据出现交叉的情况,使用synchronized关键字来同步,虽然在赋值时进行了同步,但是在取值时可能出现一些想不到的意外,这种情况就是脏读,发送脏读的情况是在读取实例变量时,此值已经被其他线程更改过了。

package entity;

public class PublicVar {

    public String username = "A";
public String password = "AA"; synchronized public void setValue(String username, String password) {
try {
this.username = username;
Thread.sleep(5000);
this.password = password; System.out.println("setValue method thread name="
+ Thread.currentThread().getName() + " username="
+ username + " password=" + password);
} catch (InterruptedException e) {
e.printStackTrace();
}
} synchronized public void getValue() {
System.out.println("getValue method thread name="
+ Thread.currentThread().getName() + " username=" + username
+ " password=" + password);
}
}
package extthread;

import entity.PublicVar;

public class ThreadA extends Thread {

    private PublicVar publicVar;

    public ThreadA(PublicVar publicVar) {
super();
this.publicVar = publicVar;
} @Override
public void run() {
super.run();
publicVar.setValue("B", "BB");
}
}
package test;

import entity.PublicVar;
import extthread.ThreadA; public class Test { public static void main(String[] args) {
try {
PublicVar publicVarRef = new PublicVar();
ThreadA thread = new ThreadA(publicVarRef);
thread.start(); Thread.sleep(200);//打印结果受此值的影响 publicVarRef.getValue();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} }
}
脏读结果:
getValue method thread name=main username=B password=AA
getValue method thread name=main username=B password=BB

出现脏读是因为public  void getValue()方法不是同步的,所以可以在任意的时候进行调用,解决办法方然就是加上同步synchronized关键字。

通过上述我们可以知道脏读是通过synchronized关键字解决的,而且还要知道:当A线程进入anyObject对象加入synchronized关键字的X方法时,A线程就获得了X方法锁,更精确地讲,是获得了对象的锁,所以在其他线程必须等A线程执行网之后才能调用X方法,但B线程可以随意调用其他非synchronized同步方法。

当A线程调用anyObject对象加入synchronized关键字修饰的X方法时,A线程就获得了X方法所在的对象的锁,所以其他线程必须等A线程执行完毕后才可以调用X方法,而B线程如果调用声明了synchronized关键字的非X方法时,必须等A线程将X方法执行完。也就是释放对象锁后才可以调用,这时A线程已经执行了一个完整的 任务,也就是说username和password这两个实例变量已经同时被赋值,不存在脏读的基本环境。

脏读一定会出现操作实例变量的情况下,这就是不同线程“争抢”实例变量的结果。

6、synchronized锁重入

关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次得到该对象的锁的,这也证明在一个synchronized方法/块的内部调用奔雷的其他synchronized方法/块时,是永远可以得到锁的。

package myservice;

public class Service {

    synchronized public void service1() {
System.out.println("service1");
service2();
} synchronized public void service2() {
System.out.println("service2");
service3();
} synchronized public void service3() {
System.out.println("service3");
} }
package extthread;

import myservice.Service;

public class MyThread extends Thread {
@Override
public void run() {
Service service = new Service();
service.service1();
} }
package test;

import extthread.MyThread;

public class Run {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
}
}
结果:
service1
service2
service3

可重入锁的概念是:自己可以再次获得自己的内部锁。比如有1条线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象锁的时候还是可以获取的,如果不可重入的话,会造成死锁。

可重入锁也支持在父子类继承的环境中。

package extthread;

import myservice.Main;
import myservice.Sub; public class MyThread extends Thread {
@Override
public void run() {
Sub sub = new Sub();
sub.operateISubMethod();
} }
package myservice;

public class Main {

    public int i = 10;

    synchronized public void operateIMainMethod() {
try {
i--;
System.out.println("main print i=" + i);
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} }
package myservice;

public class Sub extends Main {

    synchronized public void operateISubMethod() {
try {
while (i > 0) {
i--;
System.out.println("sub print i=" + i);
Thread.sleep(100);
this.operateIMainMethod();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
} }
package test;

import extthread.MyThread;

public class Run {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
}
}
结果:
sub print i=9
main print i=8
sub print i=7
main print i=6
sub print i=5
main print i=4
sub print i=3
main print i=2
sub print i=1
main print i=0

7、出现异常时,锁自动释放

synchronized同步方法的更多相关文章

  1. synchronized同步方法《二》

    1.synchronized方法和锁对象 (1).验证线程锁的是对象 代码如下: 1.1创建一个MyObject类: package edu.ymm.about_thread4; public cla ...

  2. 四、java多线程核心技术——synchronized同步方法与synchronized同步快

    一.synchronized同步方法 论:"线程安全"与"非线程安全"是多线程的经典问题.synchronized()方法就是解决非线程安全的. 1.方法内的变 ...

  3. java多线程(二)——锁机制synchronized(同步方法)

    synchronized Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码.当两个并发线程访问同一个对象object中 ...

  4. 深入理解使用synchronized同步方法和同步代码块的区别

    一.代码块和方法之间的区别 首先需要知道代码块和方法有什么区别: 构造器和方法块,构造器可以重载也就是说明在创建对象时可以按照不同的构造器来创建,那么构造器是属于对象,而代码块呢他是给所有的对象初始化 ...

  5. synchronized同步方法《一》

    1.方法内的变量为线程安全 "非线程安全"问题存在于"实例变量"中,如果是方法内部的私有变量,则不存在"非线程安全"问题,所得结果也就是&q ...

  6. 二十二 synchronized同步方法

    一 Synchronized锁: 1 synchronized取得的锁都是对象锁,而不是把一段代码或方法加锁. synchronized是给该方法的实例对象加锁.如果多个线程访问的是同一个对象  的s ...

  7. synchronized同步方法和同步代码块的区别

    同步方法默认使用this或者当前类做为锁. 同步代码块可以选择以什么来加锁,比同步方法更精确,我们可以选择只有会在同步发生同步问题的代码加锁,而并不是整个方法. 同步方法使用synchronized修 ...

  8. 58、synchronized同步方法

    线程安全问题 先看下面代码出现的问题: 定义一个Task类,里面有一个成员变量和一个有boolean类型参数的方法,方法内部会根据传入参数修改成员变量的值. package com.sutaoyu.T ...

  9. java synchronized静态同步方法与非静态同步方法,同步语句块

    摘自:http://topmanopensource.iteye.com/blog/1738178 进行多线程编程,同步控制是非常重要的,而同步控制就涉及到了锁. 对代码进行同步控制我们可以选择同步方 ...

随机推荐

  1. 利用JDK动态代理机制实现简单拦截器

    利用JDK动态代理机制实现简单的多层拦截器 首先JDK动态代理是基于接口实现的,所以我们先定义一个接口 public interface Executer { public Object execut ...

  2. Android——电脑蓝屏重启后,studio无法认出Android环境 setup JDK(缓存!缓存!缓存)

    电脑蓝屏重启后,studio无法认出Android环境 setup JDK 问题重现:因为工作问题,需要用到模拟器,然后创建了模拟器后开启了漫长的等待之旅,两三分钟之后win8蓝屏,重启,再次打开,依 ...

  3. libtommath.a: could not read symbols: Bad value 编译错误

    最近做个项目需要RSA,便调用了tommath,平时开发环境都在32位的系统上,编译运行一切都没问题,但当把程序换到一台64位系统上编译时出现: /usr/bin/ld: /usr/lib/gcc/x ...

  4. tar -cvzf a.tar.gz a --remove-files,tar命令执行原理

    tar -cvzf  a.tar.gz a --remove-files [root@nfs01 backup]# tar -zcvf  88.tar.gz    --remove-files  /b ...

  5. TextBox控件的DataBindings属性

    DataBindings属性是很多控件都有的属性,作用有2方面.一方面是用于与数据库的数据进行绑定,进行数据显示.另一方面用于与控件或类的对象进行数据绑定.这里主要关注后者.主要用法是将某个对象的某个 ...

  6. 几种常见的DIV边框样式

    <html> <head> <title>边框样式</title> </head> <body> <p style=bor ...

  7. 什么是事务(transaction)?它有什么好处

    为了完成对数据的操作,企业应用经常要求并发访问在多个构件之间共享的数据.这些应用在下列条件下应该维护数据的完整性(由应用的商务规则来定义): 分布式访问一个单独的数据资源,以及从一个单独的应用构件访问 ...

  8. par函数usr参数-控制坐标系的范围

    在R语言中,会根据数据的范围自动计算x轴和y轴的范围,举个例子 比如绘制一个1到5的散点图:代码示例: plot(1:5, 1:5) 生成的图片如下: 从图片中我们可以看到,x轴的起始位置比1要小,终 ...

  9. 蔡勒(Zeller)公式

    来源好搜百科:http://baike.haosou.com/doc/1048888-1109421.html 蔡勒(Zeller)公式,是一个计算星期的公式,随便给一个日期,就能用这个公式推算出是星 ...

  10. Android学习笔记——Menu(一)

    背景: Android3.0(API level 11)开始,Android设备不再需要专门的菜单键. 随着这种变化,Android app应该取消对传统6项菜单的依赖.取而代之的是提供anction ...