线程安全 synchronized

 

package gucas.xiaoxia.demo;

public class Demo {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Runnable safe = new ThreadSafe();
		Thread thread1 = new Thread(safe, "thread1");
		Thread thread2 = new Thread(safe, "thread2");
		thread1.start();
		thread2.start();
	}

}

class ThreadSafe implements java.lang.Runnable {

	int num = 1;
	Object o = new Object();

	public void run() {
		for (int i = 0; i < 3; i++) {
			synchronized (this) {
				num = num + 1;
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("num is value +==="
						+ Thread.currentThread().getName() + "---------" + num);
			}
		}
	}

}

输出:

num is value +===thread1---------2

num is value +===thread1---------3

num is value +===thread1---------4

num is value +===thread2---------5

num is value +===thread2---------6

num is value +===thread2---------7

 

在启用同步锁机制以后,需要避免

1无线等待,,线程B等待线程A执行完毕,然后线程A确进入了死循环。

2循环等待:两个线程相互调用,都要求要同步执行,这个时候就先会循环等待,我等你执行,你也在等我执行,这个时候就死锁了。

 

注意:

理论上,每个对象都可以做为锁,但一个对象做为锁时,应该被多个线程共享,这样才显得有意义,在并发环境下,一个没有共享的对象作为锁是没有意义的。假如 有这样的代码:

 

public class ThreadTest{  
  public void test(){  
     Object lock=new Object();  
     synchronized (lock){  
        //do something  
     }  
  }  
}  

 lock变量作为一个锁存在根本没有意义,因为它根本不是共享对象,每个线程进来都会执行Object lock=new Object();每个线程都有自己的lock,根本不存在锁竞争。

 

        每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列,就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程,当一个被线程被唤醒 (notify)后,才会进入到就绪队列,等待cpu的调度。

 

 

生产者/消费者模式 
        生产者/消费者模式其实是一种很经典的线程同步模型,很多时候,并不是光保证多个线程对某共享资源操作的互斥性就够了,往往多个线程之间都是有协作的。
        假设有这样一种情况,有一个桌子,桌子上面有一个盘子,盘子里只能放一颗鸡蛋,A专门往盘子里放鸡蛋,如果盘子里有鸡蛋,则一直等到盘子里没鸡蛋,B专门 从盘子里拿鸡蛋,如果盘子里没鸡蛋,则等待直到盘子里有鸡蛋。其实盘子就是一个互斥区,每次往盘子放鸡蛋应该都是互斥的,A的等待其实就是主动放弃锁,B 等待时还要提醒A放鸡蛋。
如何让线程主动释放锁
很简单,调用锁的wait()方法就好。wait方法是从Object来的,所以任意对象都有这个方法。看这个代码片段:

 

Object lock=new Object();//声明了一个对象作为锁  
   synchronized (lock) {  
       balance = balance - num;  
       //这里放弃了同步锁,好不容易得到,又放弃了  
       lock.wait();  
}  

 如果一个线程获得了锁lock,进入了同步块,执行lock.wait(),那么这个线程会进入到lock的阻塞队列。如果调用 lock.notify()则会通知阻塞队列的某个线程进入就绪队列。

声明一个盘子,只能放一个鸡蛋

 

import java.util.ArrayList;  
import java.util.List;  
  
public class Plate {  
  
    List<Object> eggs = new ArrayList<Object>();  
  
    public synchronized Object getEgg() {  
        if (eggs.size() == 0) {  
            try {  
                wait();  
            } catch (InterruptedException e) {  
            }  
        }  
  
        Object egg = eggs.get(0);  
        eggs.clear();// 清空盘子  
        notify();// 唤醒阻塞队列的某线程到就绪队列  
        System.out.println("拿到鸡蛋");  
        return egg;  
    }  
  
    public synchronized void putEgg(Object egg) {  
        if (eggs.size() > 0) {  
            try {  
                wait();  
            } catch (InterruptedException e) {  
            }  
        }  
        eggs.add(egg);// 往盘子里放鸡蛋  
        notify();// 唤醒阻塞队列的某线程到就绪队列  
        System.out.println("放入鸡蛋");  
    }  
      
    static class AddThread extends Thread{  
        private Plate plate;  
        private Object egg=new Object();  
        public AddThread(Plate plate){  
            this.plate=plate;  
        }  
          
        public void run(){  
            for(int i=0;i<5;i++){  
                plate.putEgg(egg);  
            }  
        }  
    }  
      
    static class GetThread extends Thread{  
        private Plate plate;  
        public GetThread(Plate plate){  
            this.plate=plate;  
        }  
          
        public void run(){  
            for(int i=0;i<5;i++){  
                plate.getEgg();  
            }  
        }  
    }  
      
    public static void main(String args[]){  
        try {  
            Plate plate=new Plate();  
            Thread add=new Thread(new AddThread(plate));  
            Thread get=new Thread(new GetThread(plate));  
            add.start();  
            get.start();  
            add.join();  
            get.join();  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        System.out.println("测试结束");  
    }  
}  

 

 


 放入鸡蛋  
拿到鸡蛋  
放入鸡蛋  
拿到鸡蛋  
放入鸡蛋  
拿到鸡蛋  
放入鸡蛋  
拿到鸡蛋  
放入鸡蛋  
拿到鸡蛋  
测试结束  

   volatile是java提供的一种同步手段,只不过它是轻量级的同步,为什么这么说,因为volatile只能保证多线程的内存可见性,不能保证多线 程的执行有序性。而最彻底的同步要保证有序性和可见性,例如synchronized。任何被volatile修饰的变量,都不拷贝副本到工作内存,任何 修改都及时写在主存。因此对于Valatile修饰的变量的修改,所有线程马上就能看到,但是volatile不能保证对变量的修改是有序的。什么意思 呢?假如有这样的代码:

public class VolatileTest{  
  public volatile int a;  
  public void add(int count){  
       a=a+count;  
  }  
}  
 

当一个VolatileTest对象被多个线程共享,a的值不一定是正确的,因为a=a+count包含了好几步操作,而此时多个线程的执行是无序的,因 为没有任何机制来保证多个线程的执行有序性和原子性。volatile存在的意义是,任何线程对a的修改,都会马上被其他线程读取到,因为直接操作主存, 没有线程对工作内存和主存的同步。所以,volatile的使用场景是有限的,在有限的一些情形下可以使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:
1)对 变量的写操作不依赖于当前值。
2)该变量没有包含在具有其他变量的不变式中
 
volatile只保证了可见性,所以Volatile适合直接赋值的场景,如

 

public class VolatileTest{  
  public volatile int a;  
  public void setA(int a){  
      this.a=a;  
  }  
}  
 


在没有volatile声明时,多线程环境下,a的最终值不一定是正确的,因为this.a=a;涉及到给a赋值和将a同步回主存的步骤,这个顺序可能被 打乱。如果用volatile声明了,读取主存副本到工作内存和同步a到主存的步骤,相当于是一个原子操作。所以简单来说,volatile适合这种场 景:一个变量被多个线程共享,线程直接给这个变量赋值。这是一种很简单的同步场景,这时候使用volatile的开销将会非常小。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值