多线程之wait,notify,volatile,synchronized,sleep

  最近在学习多线程,现在进行总结一下吧。首先要了解一下以下几个名词。

  (1)wait:当线程调用wait()方法时,当前该线程会进入阻塞状态,且释放锁,使用wait方法的时候,必须配合synchronized使用。

  (2)notify:当线程调用notify()方法时,会唤醒一个处于等待该对象锁的线程,不释放锁,使用notify方法的时候,必须配合synchronized使用。

  (3)sleep:当线程调用sleep()方法时,会让出CPU执行权,不释放锁。当指定的时间到了后,会自动恢复运行状态。

  (4)volatile:可见性,它修饰的成员变量在每次被线程访问时,都强迫从内存中重读该成员变量的值;而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存,这样在任何时刻两个不同线程总是看到某一成员变量的同一个值,这就是保证了可见性。(当多个线程操作同一个成员变量的时候,为了提高效率,JVM为每个线程单独复制了一份,这样会导致各个线程读取的数据出现脏数据,所以使用volatile关键字可以解决脏数据问题)。

  (5)synchronized:synchronized为一段操作或内存进行加锁,它具有互斥性。当线程要操作被synchronized修饰的内存或操作时,必须首先获得锁才能进行后续操作;但是在同一时刻只能有一个线程获得相同的一把锁(对象监视器),所以它只允许一个线程进行操作。

  (6)原子性:类似"a += b"这样的操作不具有原子性,在JVM中"a += b"可能要经过这样三个步骤:

    (1)取出a和b

    (2)计算a+b

    (3)将计算结果写入内存

  如果有两个线程t1,t2在进行这样的操作。t1在第二步做完之后还没来得及把数据写回内存就被线程调度器中断了,于是t2开始执行,t2执行完毕后t1又把没有完成的第三步做完。这个时候就出现了错误,相当于t2的计算结果被无视掉了。所以上面的买碘片例子在同步add方法之前,实际结果总是小于预期结果的,因为很多操作都被无视掉了。类似的,像"a++"这样的操作也都不具有原子性,需要配合synchronized实现原子性。java的concurrent包下提供了一些原子类,我们可以通过阅读API来了解这些原子类的用法。比如:AtomicInteger、AtomicLong、AtomicReference等。

  (7)调用start()方法的顺序不代表线程的启动顺序,线程启动的顺序具有不确定性。

  下面写一个简单的例子,线程t1向集合list添加数据,当集合的大小等于5的时候,通知t2线程向集合添加数据。

public class ListAdd {
	//集合
	public static volatile List<String> list=new ArrayList<String>();
	//向集合增元素
	public void add(){
	  list.add("gdpuzxs");
	}
	//返回集合大小
	public int size(){
	  return list.size();
	}
	public static void main(String[]args){
		final ListAdd listAdd=new ListAdd();
		//创建锁
		final Object lock =new Object();
		//线程t1
		Thread t1=new Thread(new Runnable() {
			public void run() {
				System.out.println("进入t1");
				synchronized (lock) {
					for(int i=0;i<10;i++){
						listAdd.add();
						try {
							Thread.sleep(500);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						System.out.println("当前线程:"+Thread.currentThread().getName()+"添加了一个元素");
						if(listAdd.size()==5){
							System.out.println("发出唤醒通知!");
							lock.notify();
						}
					}
				}
			}
		},"t1");
		//线程t2
		Thread t2=new Thread(new Runnable() {
			public void run() {
				System.out.println("进入t2");
				synchronized (lock) {
					if(listAdd.size()!=5){
						try {
							lock.wait();
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
					System.out.println("当前线程:"+Thread.currentThread().getName()+"已经收到通知!");
					for(int i=0;i<5;i++){
						listAdd.add();
						System.out.println("当前线程:"+Thread.currentThread().getName()+"添加了一个元素");
					}
				}
			}
		},"t2");
		t2.start();
		t1.start();
	}
}

  运行结果如下:

  

  分析结果:运行多次会出现上面两个不同的结果集。(证明调用start()方法的顺序不代表线程的启动顺序,线程启动的顺序具有不确定性。)

  结果(1):线程t2先抢到CPU操作权且拿到了对象锁,然后判断size!=5,调用了wait方法,进入阻塞状态,释放锁。线程t1拿到CPU操作权,向集合添加了5个元素后,size=5,调用notify方法唤醒线程t1,由于nofity方法不释放锁,所以t1继续添加5个元素。当t1执行完毕后,释放锁,这是t2拿到锁后继续添加5个元素。

  结果(2):线程t1先抢到CPU操作权且拿到了对象锁,然后添加了5个元素,当size=5的时候,调用了notify方法(这时t1没有线程处于阻塞状态,所以调用该方法一点作用都没有),接着t1继续添加5个元素。当t1执行完毕,释放锁。t2拿到对象锁后,判断size!=5为true,调用了wait方法进入阻塞状态。下面代码没有执行。程序一直处于运行状态。

  这个例子由于notify不释放锁,导致线程之间存在一个时效性。即t1虽然唤醒t2,但是由于t1不释放锁,所以t1执行完synchronized方法里面的代码后,t2获取锁才能继续执行。可以使用countDownLatch解决时效性的问题。代码如下:

  

public class ListAdd2 {
	//集合
	public static volatile List<String> list=new ArrayList<String>();
	//向集合增元素
	public void add(){
		list.add("gdpuzxs");
	}
	//返回集合大小
	public int size(){
		return list.size();
	}
	public static void main(String[]args){
		final ListAdd2 listAdd2=new ListAdd2();

		final CountDownLatch countDownLatch=new CountDownLatch(1);
		//线程t1
		Thread t1=new Thread(new Runnable() {
			public void run() {
				System.out.println("进入t1");
					for(int i=0;i<10;i++){
						listAdd2.add();
						try {
							Thread.sleep(500);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						System.out.println("当前线程:"+Thread.currentThread().getName()+"添加了一个元素");
						if(listAdd2.size()==5){
							System.out.println("发出唤醒通知!");
							countDownLatch.countDown();
						}
					}
				}
		},"t1");
		//线程t2
		Thread t2=new Thread(new Runnable() {
			public void run() {
				System.out.println("进入t2");
					if(listAdd2.size()!=5){
						try {
							countDownLatch.await();
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
					System.out.println("当前线程:"+Thread.currentThread().getName()+"已经收到通知!");
					for(int i=0;i<5;i++){
						listAdd2.add();
						System.out.println("当前线程:"+Thread.currentThread().getName()+"添加了一个元素");
					}
				}
		},"t2");
		t1.start();
		t2.start();
	}
}

  结果如下:使用countDownLatch.countDown(),countDownLatch.await()释放锁。

  

  参考网址:http://www.cnblogs.com/mengyan/archive/2012/08/22/2651575.html

时间: 2024-10-24 15:51:56

多线程之wait,notify,volatile,synchronized,sleep的相关文章

Java多线程之wait(),notify(),notifyAll()

在多线程的情况下,因为同一进程的多个线程共享同一片存储空间,在带来方便的同一时候,也带来了訪问冲突这个严重的问题.Java语言提供了专门机制以解决这样的冲突,有效避免了同一个数据对象被多个线程同一时候訪问. wait与notify是java同步机制中重要的组成部分.结合与synchronizedkeyword使用,能够建立非常多优秀的同步模型. synchronized(this){ }等价于publicsynchronized void method(){.....} 同步分为类级别和对象级别

JAVA多线程之wait/notify

本文主要学习JAVA多线程中的 wait()方法 与 notify()/notifyAll()方法的用法. ①wait() 与 notify/notifyAll 方法必须在同步代码块中使用 ②wait() 与  notify/notifyAll() 的执行过程 ③中断 调用wait()方法进入等待队列的 线程 ④notify 通知的顺序不能错 ⑤多线程中测试某个条件的变化用 if 还是用 while? ①wait() 与 notify/notifyAll 方法必须在同步代码块中使用 wait()

java 多线程之wait(),notify,notifyAll(),yield()

wait(),notify(),notifyAll()不属于Thread类,而是属于Object基础类,也就是说每个对像都有wait(),notify(),notifyAll()的功能.因为都个对像都有锁,锁是每个对像的基础,当然操作锁的方法也是最基础了. wait与notify是java同步机制中重要的组成部分.结合与synchronized关键字使用,可以建立很多优秀的同步模型.   synchronized(this){ }等价于publicsynchronized void method

多线程之:lock和synchronized的区别

多次思考过这个问题,都没有形成理论,今天有时间了,我把他总结出来,希望对大家有所帮助 1.ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候 线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定, 如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断 如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情 Reentra

JAVA多线程之volatile 与 synchronized 的比较

一,volatile关键字的可见性 要想理解volatile关键字,得先了解下JAVA的内存模型,Java内存模型的抽象示意图如下: 从图中可以看出: ①每个线程都有一个自己的本地内存空间--线程栈空间???线程执行时,先把变量从主内存读取到线程自己的本地内存空间,然后再对该变量进行操作 ②对该变量操作完后,在某个时间再把变量刷新回主内存 关于JAVA内存模型,更详细的可参考: 深入理解Java内存模型(一)——基础 因此,就存在内存可见性问题,看一个示例程序:(摘自书上) 1 public c

Java多线程之Wait()和Notify()

1.Wait()和Notify.NotifyAll都是Object的方法 2.多线程的协作是通过控制同一个对象的Wait()和Notify()完成 3.当调用Wait()方法时,当前线程进入阻塞状态,直到有另一线程调用了该对象的Notify()方法 package Thread.Wait; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.con

java多线程之volatile讲解

java多线程之volatile讲解 ?? 最近一直在看多线程的一些知识,看了一些书和一些博客,收获还是挺多的,最近看了<java并发编程的艺术>这本书感觉收获很大也推荐给各位,同时也结合以前看的博客就好好的总结一下自己所学的东西吧,有不足的地方欢迎各位指正,这篇文章主要是讲volatile关键字的知识. volatile的特性 可见性:volatile在多线程中能够保证共享变量的"可见性",简单的说就是当一个线程修改了volatile变量的时候,java线程内存模型能够确

Java多线程之notifyAll的作用域

notifyAll()因某个特定锁而被调用时,只有等待这个锁的任务才会被唤醒. package Thread.Wait; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; class Blocker { synchronized void waitingCall() { try

Java线程之Lock

重入锁 Java中的重入锁(即ReentrantLock)与Java内置锁一样,是一种排它锁.使用synchronized的地方一定可以用ReentrantLock代替. 重入锁需要显示请求获取锁,并显示释放锁.为了避免获得锁后,没有释放锁,而造成其它线程无法获得锁而造成死锁,一般建议将释放锁操作放在finally块里,如下所示. 1 2 3 4 5 6 try{ renentrantLock.lock(); } finally { renentrantLock.unlock(); } 如果重入