java同步:小结

java中的同步控制有几种方式:
1)synchronized关键字:同步方法、同步块。
2)object的wait和notify方法
3)java的lock,锁机制

4)一些常用的线程安全的容器,及使用注意事项。如:ConcurrentHashMap,Blockingqueue及其子类等。

Synchronized关键字

synchronized有两种使用方式:一种是synchronized方法、一种是synchronized块。但无论哪一种方式,都是同一个核心思想:Synchronized锁是针对对象实例的,也就是jvm一个对象实例对应的一块内存地址。

因此:一个包含有同步块或同步方法类,如果有多个实例,这多个实例无同步关系。

Synchronized方法

一个类中包含多个同步方法A、B、C,对于同一对象实例O而言,多个同步方法是互斥的。如:线程1调用方法A时,线程2想调用方法B或C,需要等待线程1执行完方法A,释放对象O的锁。

在线程类中创建同步方法,证实不同的线程之间,同步方法无效,因为每个线程运行时,都是不同的实例。(由此可见,一般不赞成线程里面创建静态方法。)

如果将同步方法改成静态同步方法,那么无论普通对象还是线程对象,所有线程都会对此同步方法的调用同步。因为此同步静态方法在内存中的地址唯一,我们为此段内存加了锁。

测试示例

一个对象的多个同步方法互斥

 假设有一个账户类,省略属性,有存钱、取钱两个方法。这两个方法需要互斥:不能同时存钱、不能同时取钱、存钱同时不能取钱、取钱同时不能存钱。

1)下面是一个AccountObj对象,将deposit()和withdraw()方法都加上synchronized关键字。

public class AccountObj {
	//省略属性...
	public synchronized void deposit(int amt){
		System.out.println("despsit start-----");
		try {
			Thread.sleep(5*1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("despsit end-----");
	}
	public synchronized void withdraw(int amt){
		System.out.println("withdraw start#####");
		try {
			Thread.sleep(5*1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("withdraw end#####");
	}
}

2)客户端调用代码:

public class Client {
	public static void main(String[] args){
		final AccountObj aco = new AccountObj();
		new Thread(){	//线程1,调用deposit()方法
			public void run(){
				aco.deposit(10);
			}
		}.start();
		new Thread(){	//线程2,调用withdraw()方法
			public void run(){
				aco.withdraw(10);
			}
		}.start();
	}
}

3)运行结果:

despsit start-----
despsit end-----
withdraw start#####
withdraw end#####

4)说明:在AccountObj的两个同步方法,线程1调用deposit()后进入休眠,此时线程2想调用withdraw()方法,但必须在deposit()执行完后,才能获得访问。
简单说就是,一个对象任何时刻只能有一个线程执行其中一个同步方法。如果对象中既有同步方法,也有非同步方法,非同步方法的调用完全不受影响。

一个类的多个实例,同步方法无关

 上面的实验测试了一个对象的多个同步方法在多线程中依然是同步的。那么如果一个类的多个对象,其同步方法还能同步吗?

1)实时上面的例子,修改测试客户端如下:

public class Client2 {
	public static void main(String[] args) {
		//包含同步方法的实例1
		final AccountObj ao1 = new AccountObj();
		//包含同步方法的实例2
		final AccountObj ao2 = new AccountObj();
		new Thread(){	//线程1,调用实例1的存钱方法
			public void run(){
				ao1.deposit(10);
			}
		}.start();
		new Thread(){	//线程2,调用实例2的存钱方法
			public void run(){
				ao2.deposit(10);
			}
		}.start();
	}
}
2)测试输出:
despsit start-----
despsit start-----
despsit end-----
despsit end-----

3)说明:线程1调用实例1的存钱方法进入休眠,与线程2调用实例2的存钱方法依然能进入执行,二者之间并未发生同步。
也就是说,线程同步功能只发生在同一个对象之内,如果是不同的实例对象,它们之间的同步方法互不影响。

账号存取钱的例子

 上面的测试结果就很好的解释了一个账户对存钱和取钱的基本控制。

账户类AccountObj,可以有多个实例,每个实例之间的数据无关,同步无关。

同步控制,只需要加载特定的对象之上。并且,存钱的同时,在其他地方发生了取钱,也必须存取平衡。一个类的存取方法都加上同步关键字就能保证:存存同步、取取同步、存取同步。

1)下面修改AccountObj类,测试此特性:

public class AccountObj {
	private int amount;
	public AccountObj(int amount){	//账号初始值
		this.amount = amount;
	}
	public synchronized void deposit(int amt){
		int tmp = amount;
		tmp += amt;
		try {
			Thread.sleep(5);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		amount = tmp;
	}
	public synchronized void withdraw(int amt){
		int tmp = amount;
		tmp -= amt;
		try {
			Thread.sleep(5);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		amount = tmp;
	}
	public int getAcount(){
		return amount;
	}
}

3)修改客户端测试代码:

public class Client {
	//模拟存取次数1000次
	private static int THREAD_MOCK_NUM = 1000;
	static Thread[] threadsHold = new Thread[THREAD_MOCK_NUM];
	public static void main(String[] args){
		//初始化账号有1000块钱
		final AccountObj aco = new AccountObj(1000);
		for(int i=0;i
3)测试结果:多次测试,收支平衡,输出结果恒为:账号余额:1000

但如果将AccountObj存取方法的synchronized关键字去掉,则每次输出的结果就不一定了。
(对于在一个线程类中创建同步方法是无用之举,应为不会各个线程之间不会发生同步。对于这句话就不贴代码举例了)

 

Synchronized块

同步块的语法:synchronized(obj){//java代码块}。

解释:synchronized块在执行前,先要获得对象obj的对象锁,如果obj对象锁可用,则当前线程独占obj锁,直到synchronized块执行完毕,释放锁,则其他线程可争用此锁,以执行同步块。

注意,这里的obj是实例对象,也就是说:

1)如果两个线程,加锁的obj,是同一个实例对象,则同步块可以进行同步;两个线程的加锁obj是两个不同的实例,那么同步块无效的,因为线程获得的是不同对象上的锁,并不会互斥。因为,同步是针对对象。

2)对于所有的同步块的使用,只要注意锁机制是发生在一个相同的对象实例上即可。无论同步的是对象,this,还是Object.class,只要理解了实例的概念就能很好的控制。如果对一个单例对象加锁,那么所有线程在此同步块访问时都会同步,与Object.class一样,因为每个class在内存中也只有一个对应的Class对象。

3)当我们在使用同步块时,注意力应该放在同步对象obj上。比如3个不同的同步块X、Y、Z,针对同一个对象同步,实际上这三个同步块的线程争用时是互斥的。而不仅仅是X-X争用同步、Y-Y争用同步、Z-Z争用同步等。

 测试示例

同步块的使用重点是同步对象obj

 1)假设我们有一个类:ObjectDemo.java。

2)有一个线程例子:

package com.thread.syncBlock;

public class ThreadDemo implements Runnable {
	private String method;

	public ThreadDemo(String method){
		this.method = method;
	}
	
	@Override
	public void run() {
		if("1".equals(method)){
			processSync1();
		}
		if("2".equals(method)){
			processSync2();
		}
	}
	
	private void processSync1(){
		synchronized(ObjectDemo.class){	//对一个加载类加锁,同步块X
			System.out.println(Thread.currentThread().getName()+"--进入方法1");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"--退出方法1");
		}
	}
	private void processSync2(){
		synchronized(ObjectDemo.class){	//对同一个加载类加锁,同步块Y
			System.out.println(Thread.currentThread().getName()+"--进入方法2");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"--退出方法2");
		}
	}

}

3)写一个测试类:

package com.thread.syncBlock;

public class SyncBlockTest {

	public static void main(String[] args) {
		Thread t1 = new Thread(new ThreadDemo("1"));
		t1.start();

		Thread t2 = new Thread(new ThreadDemo("2"));
		t2.start();
	}
}

4)输出结果:

Thread-0--进入方法1
Thread-0--退出方法1
Thread-1--进入方法2
Thread-1--退出方法2

5)结果说明:虽然同步块X、Y执行的区域完全不同,但二者都争用同一把锁,ObjectDemo.class。X-Y同步块之间也能发生同步。所以把握好同步对象是关键。

同步方法与同步块的联系

同步方法执行时,是在对象实例本身之上加锁;而同步块我们可以自定义需要加锁的对象,如果用this关键字:synchronized(this){..},那么锁也是加在自身实例之上。我们需要注意控制的是同步块中的对象,确实是符合业务逻辑的同步对象。

换句话说:静态同步方法、静态同步变量、synchronized(AnyObject.class)、synchronized(“”)等都有同样的效果,对所有调用此段代码的所有线程实现同步。

object的wait和notify方法

wait和notify方法的调用需要在同步关键字synchronized中使用,如:shnchronized(obj){obj.wait;}和synchronized(obj){obj.notify}。

object的wait和notify方法可以用来协调控制,多线程对同一对象的操作问题。

使用场景:我的逻辑需要A线程获取一个值对象Obj,但这个值需要调用B线程生产,但因为网络原因、操作时长不定,我们需要线程A、B之间对值对象Obj存取同步,那么wait、notify就很合适。

测试示例

1)一个普通java对象:

package com.thread.waitNotify;

public class ObjectInfo {

	private String name;
	private String desc;
	public ObjectInfo(){}
	public ObjectInfo(String name,String desc){
		this.name = name;
		this.desc = desc;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getDesc() {
		return desc;
	}
	public void setDesc(String desc) {
		this.desc = desc;
	}
	public String toString(){
		return "{name:"+name+";desc:"+desc+"}";
	}
}

2)一个需要获取值的线程,取到值之前先休眠:

package com.thread.waitNotify;

public class ThreadWait implements Runnable {

	private Object lock;
	public ThreadWait(Object lock){
		this.lock = lock;
	}
	@Override
	public void run() {
		synchronized(lock){
			System.out.println(WaitNotifyTest.getNowTime()+"\t"+Thread.currentThread().getName()+"执行下发,等待返回……");
			try {
				lock.wait();3)
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(WaitNotifyTest.getNowTime()+"\t"+Thread.currentThread().getName()+"跳出wait----");
			ObjectInfo oi = (ObjectInfo)lock;
			System.out.println(oi.toString());
		}
	}
}
3)一个处理返回值的线程,返回值处理成功后,唤醒等待线程:

package com.thread.waitNotify;

import com.thread.Util;

public class ThreadNotify implements Runnable {
	
	public Object lock;
	public ThreadNotify(Object lock){
		this.lock = lock;
	}
	public void run() {
		synchronized(lock){
			System.out.println(Util.getNowTime()+"\t"+Thread.currentThread().getName()+"进入信息回执处理");
			try {
				Thread.sleep(10*1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			ObjectInfo oi = (ObjectInfo)lock;
			oi.setName("测试");
			oi.setDesc("回执");
			lock.notify();
		}
	}
}
4)客户端代码:

package com.thread.waitNotify;

import java.text.SimpleDateFormat;
import java.util.Date;

public class WaitNotifyTest {

	public static void main(String[] args) {
		ObjectInfo info = new ObjectInfo();
		Thread tw = new Thread(new ThreadWait(info));	//tw线程获取值时,进入wait等待
		tw.start();

		Thread tn = new Thread(new ThreadNotify(info));	//tn线程获取返回值,唤醒wait线程
		tn.start();
	}
	
	public static String getNowTime(){
		Date date = new Date();
		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd HH:mm:ss");
		return sdf.format(date);
	}
}
5)测试结果输出:

20150323 14:06:33	Thread-0执行下发,等待返回……
20150323 14:06:33	Thread-1进入信息回执处理
20150323 14:06:43	Thread-0跳出wait----
{name:测试;desc:回执}

6)结果说明:

当thread-0(tw)进入休眠后,thread-1(tn)将对象info赋值,然后唤醒thread-0,thread-0进行往下执行,输出了info的信息。

结果也同时说明,当调用了obj.wait()方法,obj的锁能够被释放,为其他线程所用。

wait与sleep的区别

调用sleep,线程是阻塞的,如果其获得了对象锁,这个锁是不释放的,其他竞争线程不能获得该锁;
调用wait,线程也会被阻塞,区别是当前线程会释放自己持有的线程锁,其他线程可以获得对象锁执行操作。上面的代码示例就是很好的说明。

wait、notify与synchronized区别

synchronized关键字主要用于控制线程访问某一对象同步,协调资源竞争。如果是同步问题,则用synchronized关键字。
wait、notify则是用于控制线程执行的顺序,通过此方式实现线程见的通信也是可以的。如果是线程执行顺序、或数据交换的问题,就用wait、notify。

java锁机制-lock

java的锁机制中常用的锁有两个:ReentrantLock和ReentrantReadWriteLock。

ReentrantLock

ReentrantLock是一个可重用互斥锁,此对象实例可以通过lock()和unlock()方法获取和释放锁。
其机制与synchronized关键字一样,在一段代码开始前调用lock()获取锁,在一段代码执行结束调用unlock()以释放锁。不一样的是synchronized是自动释放锁,而ReentrantLock需要手动释放,所以一般在finally{}代码块中释放锁;且ReentrantLock有公平锁(new ReentrantLock())、非公平锁(new ReentrantLock(true))两种,synchronized与非公平锁的竞争类似,而公平锁可以控制获取锁的顺序,让等待锁时间最长的线程优先获取锁,并执行。
那么对于公平锁和非公平锁,应该用那个呢?一般而言用非公平锁即可,因为公平锁看似很好,其实开销很大,并发执行吞吐量小,速度很慢。如果真要用,需要慎重考虑。

ReentrantReadWriteLock

ReentrantReadWriteLock用意:一般一个数据持有对象obj,一些线程可能会调用obj的set方法,来写入数据;而另一些线程可能会调用obj的get方法,来访问数据。为了保证数据的正确性,我们就需要对set、get方法加同步。如果加了synchronized修改方法,那么任何一个同步方法被调用时,都会持有此对象锁,导致其他同步方法等待,并发效果很不理想。我们的需求是:对obj的读读不做控制,但对写写和读写要进行同步控制,以保证数据访问的同步和高效。
举个例子:写代码是遇到一个情况,一个系统在启动时,将数据库中一张数据字典表的值保存在了缓存中,以后系统中任何用到此数据字典表,都从缓存中获取。需求是,在系统运行时,如果人为修改了字典表数据,我们要调用一下刷新方法,将数据库改动更新到缓存中。此时我们需要保证的就是,我在刷新时,可能会有线程在读内存的数据,我要对写和读进行同步控制。

刷新测试示例

此示例是对上面的一张数据字典表的内存刷新模拟。需要这么几个对象:1)内存容器类;2)刷新线程;3)读取线程;4)测试类。下面是代码:
1)内存容器类:
package com.thread.lock.rwlock;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
//内存容器类
public class Config {
	
	private static final ReadWriteLock lock = new ReentrantReadWriteLock();
	private static final Lock rlock = lock.readLock();	//从读写锁获取读锁
	private static final Lock wlock = lock.writeLock();	//从读写锁获取写锁
	
	//数据库字典表缓存模拟容器
	private static Map cache = new HashMap();

	//读操作1
	public static Map getMap(){
		try{
			rlock.lock();				//加读锁
			return cache;			
		}finally{
			rlock.unlock();				//读锁释放,注意释放锁放在finally块中
		}
	}
	//读操作2
	public static String getValueByKey(String key){
		try{
			rlock.lock();
			return cache.get(key);			
		}finally{
			rlock.unlock();
		}
	}
	//写操作,刷新
	public static void refreah(){
		wlock.lock();					//加写锁
		System.out.println(Test.getNowTime()+Thread.currentThread().getName()+"--写线程执行开始");
		try{
			//刷新模拟
			cache.clear();
			try {
				Thread.sleep(5*1000);	//模拟数据库操作耗时
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			for(int i=1;i<20;i++){
				cache.put(String.valueOf(i), String.valueOf(Math.random()));
			}
		}finally{
			System.out.println(Test.getNowTime()+Thread.currentThread().getName()+"--写线程执行结束");
			wlock.unlock();				//释放写锁,在finally块中执行
		}
	}
}

2)刷新线程
package com.thread.lock.rwlock;

public class WriteThread implements Runnable {

	@Override
	public void run() {
		Config.refreah();
	}
}

3)读取线程
package com.thread.lock.rwlock;

public class ReadThread implements Runnable {

	@Override
	public void run() {
		System.out.println(Test.getNowTime()+Thread.currentThread().getName()+"++当前缓存中特定key的值为:key=12 value="+Config.getValueByKey("12"));
	}
}

4)客户端模拟
package com.thread.lock.rwlock;

import java.text.SimpleDateFormat;
import java.util.Date;

public class Test {

	public static void main(String[] args) {
		Config.refreah();	//先将数据加载到缓存
		
		Thread rt1 = new Thread(new ReadThread());	//第一次取值
		rt1.start();
		
		try {
			Thread.sleep(10);	//给rt1留点锁争用时间,免得wt1比rt1获取锁的时间还快
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		Thread wt1 = new Thread(new WriteThread());	//第一次刷新
		wt1.start();

		try {
			Thread.sleep(100);	//确保wt1获取到锁,然后rt2来读数据,会等待读锁的释放才能取值
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		Thread rt2 = new Thread(new ReadThread());	//第二次取值
		rt2.start();
	}

	public static String getNowTime(){
		Date date = new Date();
		SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
		return sdf.format(date)+" ";
	}
}

5)输出结果:
14:24:42 main--写线程执行开始
14:24:47 main--写线程执行结束
14:24:47 Thread-0++当前缓存中特定key的值为:key=12 value=0.7770040453144604	//原数据
14:24:47 Thread-1--写线程执行开始
14:24:52 Thread-1--写线程执行结束
14:24:47 Thread-2++当前缓存中特定key的值为:key=12 value=0.8918793843441799	//新数据,此数据已被刷新
6)测试说明
在线程thread-1开始刷新数据时,线程thread-2要读取数据,但是刷新为结束,thread-2取不到值,在同步等待。知道thread-1写线程结束,thread-2才取出刷新后的新值。
对应读与读之间是不互斥的,就不贴验证代码了。

刷新例子扩展

上面的代码Config类中,我们定义的锁是一个静态变量,什么意思呢,就是Config类中用到读写锁时,此锁的实例唯一,任何线程调用Config的读写方法都会发生同步。
现在,如果有需求,我不是一个字典表缓存,而是一个账户对象account,那么不同的账户读取(存取)是不相关的,而同一个账号实例读取是需要同步的。那么Account类中的set、get方法都要改成常规方法(不是静态方法),读写锁的static关键字也去掉。什么意思呢?每个account实例,对应一个特定的读写锁,多线程争用同一个account实例,需要获取特定锁,而且不同的account对象锁无关,这就是读写锁是否需要用static修饰的意义。同时,搞懂同步机制,是针对实例对象(也就是一个特定的内存块)的,读写锁也很好理解,一个对象,与一个锁对象关联,要操作此对象,先要争用此锁。

线程安全容器

ConcurrentHashMap

1)ConcurrentHashMap的同步机制用到的不仅仅是一个锁,其中包含segment数组,每个segment就是一个ReentrantLock锁。当我们需要对ConcurrentHashMap就行put、get操作时,首先会定位到要操作map的某个segment,并获取segment锁,而其他段完全不受此段锁的影响。这要提高了吞吐量。
2)上面的put、get方法等是局部操作,但有一些整体操作如size、clear等就可能需要获取全部分分段锁,然后一一处理。
3)ConcurrentHashMap可能出错的情况在于迭代,当我们获取了keySet,然后开始迭代时,可能其他线程修改过map,有可能有些key就取不到值,而造成异常。
4)使用ConcurrentHashMap时,要具体情况具体考虑。因为map的同步颗粒度非常小,是在map内部完成的。此map像读写锁的功能就不能实现,因为刷新要两步:(1)clear();(2)循环put()。用ConcurrentHashMap来做的话,这两步是被分别同步的,期间有操作间隙可供其他线程利用,此间隙就可能取出null值来。所以具体问题具体分析。
5)HashMap到底不安全在什么地方?主要出在HashMap的扩容上。比如多线程多对HashMap进行put,当达到某一阀值,HashMap就会自动扩容,若果扩容期间发生存取,HashMap内部索引的计算就可能会发生异常。

LinkedBlockingQueue

参考文档:http://www.cnblogs.com/jackyuj/archive/2010/11/24/1886553.html

这是一个链式阻塞队列,是“生产者-消费者”模式中非常常用的一个容器,他们字段阻塞速率不一致的生产端或消费端。经测试,此容器会阻塞的方法有:

会阻塞的取方法:take()和poll(long timeout,TimeUnit unit),注意无参poll()方法不会阻塞
会阻塞的入方法:put()和offer(E obj,long timeout,TimeUnit unit),无参offer()方法不会阻塞


add()会抛异常,queue full;

ArrayBlockingQueue

ArrayBlockingQueue和LinkedBlockingQueue都是阻塞队列。但从名称上可以看出根本区别,在于数据结构,前者是数组,后者是链表。其方法和功能上大同小异,都用于控制典型的“生产者-消费者”线程同步。区别在于前者初始化必须指定容量,而且超出容量会报错;前者可以指定自己的锁是公平锁还是非公平锁;前者存取两端用的是同一把锁,存取不能并发,后者的存取是两把锁,互不相关,可以并发存取;前者在添加对象的时候,不会生成额外的对象,后者在添加对象时,会生成一个node对象,对高并发会增加GC的消耗。
对于ArrayBlockingQueue,存取用的是同一把锁,个人有个疑问:当取值阻塞的时候,入值如何获取到同一把锁?查看代码一知半解,但大概知道含义:当取值阻塞的时候,当前线程进入等待,需要释放锁;此后,有写入线程执行时,才能获取此锁,加入数据后中断(interupt)等待的线程。

ConcurrentLinkedQueue

其中,还有一个并发队列也是作为生产者消费者的首选: ConcurrentLinkedQueue ,它是非阻塞队列,肯定就不是出自 Blockingqueue 接口,而是出自 AbstractQueue ,因此也就没有put和take方法,使用这个并发队列需要有两点注意:第一,判断是否为空尽量使用 isEmpty 方法,不要用 size() ,有人测试过 size 方法很耗费时间;第二就是线程问题,虽然 ConcurrentLinkedQueue 是线程安全的,但是只负责原子性的,就是说当你操作 queue.add() or queue.poll 的时候是安全的,当并发量较大时,你在使用 queue.isEmpty 时还不为空,但就在这空当有可能就执行 poll 操作,导致队列为空引起异常,可用如下代码来控制大并发:

synchronized(queue) {
    if(!queue.isEmpty()) {
       queue.poll();
    }
}

CopyOnWriteArrayList

参考链接:http://www.cnblogs.com/dolphin0520/p/3938914.html

如果说ArrayList是线程不安全的,那么其对应的线程安全的类就是CopyObWriteArrayList。

CopyOnWriteArrayList实现同步的原理:在写的时候,先将原数组拷贝一份,然后执行修改,修改完后将新对象的索引赋值给变量,就不会造成读值的异常。

此数组有两个缺点:1)数组修改基于拷贝,内存消耗比较大;2)数据实时性可能有点延迟。

个人有个疑问:如果在遍历的时候,如果有其他线程修改过此数组,那变量结果会是怎样?实验结果是:不会影响变量的访问,也不会抛出异常。但当前遍历结果为旧值。代码贴在下面。

1)测试类

package com.thread.copyOnWrite;

import java.util.concurrent.CopyOnWriteArrayList;

public class ListTest {

	private static CopyOnWriteArrayList cowList = new CopyOnWriteArrayList();
	
	public static void main(String[] args) {
		int size = 0;
		while(size++<10){
			cowList.add(String.valueOf(size));
		}
		new Thread(new Runnable(){
			@Override
			public void run() {
				int count = 0;
				System.out.println("--begain:"+cowList.size());
				for(String li:cowList){
					System.out.println(li);
					if(count==5){
						try {
							Thread.sleep(100);		//遍历数组时,让线程休眠100ms
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
					count++;
				}
				System.out.println("--begain:"+cowList.size());
			}
		}).start();
		try {
			Thread.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		new Thread(new Runnable(){
			@Override
			public void run() {
				cowList.remove("5");	//在遍历休眠的时候,删除其中的一个数
			}
		}).start();
	}
}
2)输出结果

--begain:10
1
2
3
4
5
6
7
8
9
10
--begain:9

3)结果说明:

如上,在遍历开始,输出数组大小为10,在遍历过程中,删除数组中的一个对象,遍历结束后, 数组大小编程了9。但细心观察,可发现数组中的输出依然包含“5”。即此数组在遍历中被修改,不会影响原遍历结果。

Vector与HashTable

Vector是一个线程安全的数字。但实际的多线程并发可能很少会用。因为Vector的实现机制很简单,就是继承、实现了list相关的父类、接口,然后重新其中的方法,然后再大部分方法前加了synchronized关键字,如get()、remove()方法等。这样做的结果是什么呢?Vector的一个对象,其中包含10多个同步方法,这些方法在多线程访问时互斥的,即只要有一个线程访问vector对象的任意同步方法,那么此对象的其他同步方法的访问线程均要等待。对于高并发的程序,是要细致考虑的。
HashTable与Vector的实现方式相仿。实际开发几乎没用过hashTable。

你可能感兴趣的:(java,同步,并发,java,同步,并发)