对共享可变数据的同步访问
2010-02-08 17:20
197 查看
一般认为同步就是一般的互斥行文。不是很全面。
同步的意义:同步不仅可以阻止一个线程看到对象处于不一致的状态中,它还可以保证通过一系列看似顺序执行的状态转变序列,对象从一种一致的状态变迁到另外一种一致的状态。每一个线程进入到一个被同步的方法或是代码块的时候,它会看到由同一个锁控制的以前所有状态转变的结果。当线程退出了这个被同步的区域之后,任何线程在进入到由这同一把锁同步的区域时,它就可以看到由前面那个线程带来的状态转变(如果有状态转变的话)。也就是代码是固定的,变量的地址,或是值变量也是固定的,不同的线程会造成数据被修改的情况,第2个线程进入就会看到被修改的内存值域。
JAVA语言保证读或者写一个变量是原子的,除非这个变量类型为long或是double.也就是说,读入一个非long或double类型的变量,可以保证返回值一定是某个线程保存在该变量中的,即使多个线程在没有同步的情况下并发修改这个变量,也是如此。
有些人说,为了提高性能,在读或写原子数据的时候,你应该避免使用同步。其实这个建议是非常危险而且是错误的。虽然原子性保证了一个线程在读取原子数据的时候,不会看到一个随机的数值,但是它并不保证一个线程写入的值对于另外一个线程是可见的:为了在线程之间可靠的通信,以及为了互斥访问,同步是需要的。
以下代码就可以看出来:
/**
*
*/
package thread;
/**
* 单例的Test类,保证唯一性。
* @author Administrator
*
*/
public class Test {
private int flag=0;
private static Test temp=null;
public synchronized void method(){
flag=flag+1;
System.out.println(flag);
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static Test getInstance(){
if(temp==null){
temp=new Test();
return temp;
}
return temp;
}
}
这个类是执行一个方法method,该方法要被多次调用。
/**
*
*/
package thread;
/**
* 实例一个单例的test对象启动线程,调用test.method同步方法。
* @author Administrator
*
*/
public class ThreadTemp extends Thread{
public void run(){
Test test=Test.getInstance();
test.method();
}
}
这个类是启动线程的,他使用的单例,让这个test对象成为唯一的共享对象,包括对象的值域。
/**
*
*/
package thread;
/**
* 开启两个线程去访问单例的test,造成域被改变。
* @author Administrator
*
*/
public class ThreadFactory {
public static void main(String[] args) {
Thread t1=new ThreadTemp();
t1.start();
Thread t2=new ThreadTemp();
t2.start();
}
}
开始访问可以看到第2个线程进入的时候值域flag已经是1了。
所以打印出来是1,2.
所以JAVA线程的特性我们应该引起注意。这个是线程安全的,当你把method方法的synchronized取消,然后在主函数调用的地方改成循环实例线程,并且把sleep也去掉。你将看到奇怪的现象。这里就是竞争造成的。
记住一句话:无论任何时候当多个线程共享可变数据的时候,每个读或写数据的线程必须获得一把锁,而这个锁只能是对象锁。如果没有同会察到。而且这样的错误,是很难调试和重现的,因为线程调试是很麻烦的。他们是高度依赖JVM和操作系统硬件平台。
同步的意义:同步不仅可以阻止一个线程看到对象处于不一致的状态中,它还可以保证通过一系列看似顺序执行的状态转变序列,对象从一种一致的状态变迁到另外一种一致的状态。每一个线程进入到一个被同步的方法或是代码块的时候,它会看到由同一个锁控制的以前所有状态转变的结果。当线程退出了这个被同步的区域之后,任何线程在进入到由这同一把锁同步的区域时,它就可以看到由前面那个线程带来的状态转变(如果有状态转变的话)。也就是代码是固定的,变量的地址,或是值变量也是固定的,不同的线程会造成数据被修改的情况,第2个线程进入就会看到被修改的内存值域。
JAVA语言保证读或者写一个变量是原子的,除非这个变量类型为long或是double.也就是说,读入一个非long或double类型的变量,可以保证返回值一定是某个线程保存在该变量中的,即使多个线程在没有同步的情况下并发修改这个变量,也是如此。
有些人说,为了提高性能,在读或写原子数据的时候,你应该避免使用同步。其实这个建议是非常危险而且是错误的。虽然原子性保证了一个线程在读取原子数据的时候,不会看到一个随机的数值,但是它并不保证一个线程写入的值对于另外一个线程是可见的:为了在线程之间可靠的通信,以及为了互斥访问,同步是需要的。
以下代码就可以看出来:
/**
*
*/
package thread;
/**
* 单例的Test类,保证唯一性。
* @author Administrator
*
*/
public class Test {
private int flag=0;
private static Test temp=null;
public synchronized void method(){
flag=flag+1;
System.out.println(flag);
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static Test getInstance(){
if(temp==null){
temp=new Test();
return temp;
}
return temp;
}
}
这个类是执行一个方法method,该方法要被多次调用。
/**
*
*/
package thread;
/**
* 实例一个单例的test对象启动线程,调用test.method同步方法。
* @author Administrator
*
*/
public class ThreadTemp extends Thread{
public void run(){
Test test=Test.getInstance();
test.method();
}
}
这个类是启动线程的,他使用的单例,让这个test对象成为唯一的共享对象,包括对象的值域。
/**
*
*/
package thread;
/**
* 开启两个线程去访问单例的test,造成域被改变。
* @author Administrator
*
*/
public class ThreadFactory {
public static void main(String[] args) {
Thread t1=new ThreadTemp();
t1.start();
Thread t2=new ThreadTemp();
t2.start();
}
}
开始访问可以看到第2个线程进入的时候值域flag已经是1了。
所以打印出来是1,2.
所以JAVA线程的特性我们应该引起注意。这个是线程安全的,当你把method方法的synchronized取消,然后在主函数调用的地方改成循环实例线程,并且把sleep也去掉。你将看到奇怪的现象。这里就是竞争造成的。
记住一句话:无论任何时候当多个线程共享可变数据的时候,每个读或写数据的线程必须获得一把锁,而这个锁只能是对象锁。如果没有同会察到。而且这样的错误,是很难调试和重现的,因为线程调试是很麻烦的。他们是高度依赖JVM和操作系统硬件平台。
相关文章推荐
- java,线程操作,同步访问共享的可变数据
- 【Effective Java】11、同步访问共享的可变数据
- 并发-同步访问共享的可变数据
- Effective Java 读书笔记——66:同步访问共享的可变数据
- effective java同步访问共享的可变数据
- 代码笔记 | 多线程使用queue模块同步访问共享数据
- 多线程访问共享数据同步原因
- 同步访问共享数据【Effective java第10章】
- 共享数据的访问,其实就是协调同步
- 使用C# lock同时访问共享数据
- Android4.0 以后 的网络访问和 数据同步问题
- 使用C# lock同时访问共享数据
- 多个线程访问共享对象和数据的方式
- 共享一个通用的数据访问类
- android开发 如何通过web服务器访问MYSQL数据库并且使其数据同步到android SQLite数据库?
- oracle_fdw的使用:从PostgreSQL中访问Oracle数据库,实现数据库数据的同步
- java中多个线程访问共享数据的方式有哪些
- 使用HTML5 跨域共享特性解决AJAX跨域数据同步问题
- 多线程之3-------多个线程访问共享数据的方式