您当前的位置: 首页 > 

止步前行

暂无认证

  • 3浏览

    0关注

    247博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

多线程中的synchronized

止步前行 发布时间:2018-11-09 14:53:06 ,浏览量:3

一、引言

我们知道,当我们在编写多线程的代码时,对于那些线程共享资源,如果有多个线程需要同时访问共享资源时,就会有线程安全问题,这也是多线程带来的困扰。下面就来分析一下,如果解决多线程间访问共享资源。

二、线程安全问题

众所周知,在单线程中,不会出现线程安全问题。而在多线程的代码中,有可能会出现有多个线程同时访问同一个 共享、可变资源 的情况,这种资源可以是:一个变量、一个对象、一个文件等。对于共享资源,这里要特别注意两点:

a.	共享: 意味着该资源可以由多个线程同时访问;
b.	可变: 意味着该资源可以在其生命周期内被修改;

所以,当多个线程同时访问这种资源的时候,就会存在一个问题:由于每个线程执行的过程是不可控的,所以需要采用同步机制来协同对 对象可变状态的访问。

举个数据脏读 的例子:

	//资源类
	class PublicVar {

	    public String username = "A";
	    public String password = "AA";
	
	    //同步实例方法
	    public synchronized void setValue(String username, String password) {

	        try {
	            this.username = username;
	            Thread.sleep(5000);
	            this.password = password;
	
	            System.out.println("method=setValue " +"\t" + "threadName="
	                    + Thread.currentThread().getName() + "\t" + "username="
	                    + username + ", password=" + password);
	        } catch (InterruptedException e) {
	            e.printStackTrace();
	        }
	    }
	
	    //非同步实例方法
	    public void getValue() {
	        System.out.println("method=getValue " + "\t" +  "threadName="
	                + Thread.currentThread().getName()+ "\t" + " username=" + username
	                + ", password=" + password);
	    }
	}


	//线程类
	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");
	    }
	}


	//测试类
	public class Test {
	
	    public static void main(String[] args) {
	        try {
	            //临界资源
	            PublicVar publicVarRef = new PublicVar();
	
	            //创建并启动线程
	            ThreadA thread = new ThreadA(publicVarRef);
	            thread.start();		//启动子线程,为属性赋值,赋值过程中,会休眠5秒
	
	            Thread.sleep(200);// 主线程休眠 打印结果受此值大小影响
	
	            //在主线程中调用
	            publicVarRef.getValue();
	
	        } catch (InterruptedException e) {
	            e.printStackTrace();
	        }
	    }
	}

	/* Output ( 数据交叉 ): 
	        method=getValue     threadName=main         username=B, password=AA
	        method=setValue     threadName=Thread-0     username=B, password=BB
	 *///:~

由程序输出可知,虽然在写操作时进行了同步操作,但在读操作上仍然有可能出现一些意想不到的情况,例如上面所示的 脏读。发生 脏读 的情况是在执行读操作时,相应的数据已被其他线程 部分修改 过,导致 数据交叉 的现象产生。

这其实就是一个线程安全问题,即多个线程同时访问一个资源时,会导致程序运行结果并不是想看到的结果。这里面,这个资源被称为:临界资源。也就是说,当多个线程同时访问临界资源(一个对象,对象中的属性,一个文件,一个数据库等)时,就可能会产生线程安全问题。

但是,当多个线程执行一个方法时,该方法内部的局部变量并不是临界资源,因为这些局部变量是在每个线程的私有栈中,因此不具有共享性,不会导致线程安全问题。

三、 如何解决线程安全问题

实际上,所有的并发模式在解决线程安全问题时,采用的方案都是 序列化访问临界资源 。即在同一时刻,只能有一个线程访问临界资源,也称作 同步互斥访问。

换句话说,就是在访问临界资源的代码前面加上一个锁,当访问完临界资源后释放锁,让其他线程继续访问。

在 Java 中,提供了两种方式来实现同步互斥访问:synchronized 和 Lock。本文主要讲述 synchronized 的使用方法,Lock方法将在后续文章中分析。

四、synchronized 同步方法 或者 同步块

在了解 synchronized 关键字的使用方法之前,先来看一个概念:互斥锁,即 能到达到互斥访问目的的锁。举个简单的例子,如果对临界资源加上互斥锁,当一个线程在访问该临界资源时,其他线程便只能等待。

在 Java 中,可以使用 synchronized 关键字来标记一个 方法 或者 代码块,当某个线程调用该对象的synchronized方法或者访问synchronized代码块时,这个线程便获得了该对象的锁,其他线程暂时无法访问这个方法,只有等待这个方法执行完毕或者代码块执行完毕,这个线程才会释放该对象的锁,其他线程才能执行这个方法或者代码块。

下面这段代码中,两个线程分别调用insertData对象插入数据:

1、synchronized 方法
	public class Test {
	
	    public static void main(String[] args)  {

	        final InsertData insertData = new InsertData();
	        // 启动线程 1  
	        new Thread() {
	            public void run() {
	                insertData.insert(Thread.currentThread());
	            };
	        }.start();
	
	        // 启动线程 2
	        new Thread() {
	            public void run() {
	                insertData.insert(Thread.currentThread());
	            };
	        }.start();
	    }  
	}

	class InsertData {
	
	    // 共享、可变资源
	    private ArrayList arrayList = new ArrayList();
	
	    //对共享可变资源的访问
	    public void insert(Thread thread){
	        for(int i=0;i            
关注
打赏
1657848381
查看更多评论
0.0413s