J2SE(10)之多线程(线程安全)

1、线程安全

       在多线程系统中,当多个线程共享同一份资源时,会出现线程不安全的情况。我们可以使用关键字synchronized加锁来解决线程不安全的问题。

1.1 同步方法

在方法上加上synchronized关键字,普通同步方法默认的锁对象是当前对象this,对于静态同步方法,锁是当前类的Class对象。

public synchronized void test2(){
    //方法体
}

1.2 同步方法块

用关键字synchronized修饰可能存在线程不安全的代码块,锁对象是我们指定的引用类型的对象,基本类型不能作为锁。

synchronized(锁对象){
    //可能存在线程不安全的代码
}

1.3 以售票讲解线程同步

public class SynDemo01 {
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		//真实角色
		Web12306 web= new Web12306();
		//代理
		Thread t1 =new Thread(web,"路人甲");
		Thread t2 =new Thread(web,"黄牛已");
		Thread t3 =new Thread(web,"攻城师");
		//启动线程
		t1.start();
		t2.start();
		t3.start();			
		
	}

}
/**
 * 线程安全的类
 */
class Web12306 implements Runnable {
	private int num =10;
	private boolean flag =true;
	@Override
	public void run() {
		while(flag){
			test5();
		}
	}
	
	public void test6(){   	
		if(num<=0){
			flag=false; //跳出循环
			return ;
		}
		 //a  b  c 	
		synchronized(this){
			try {
				Thread.sleep(500); //模拟 延时
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"抢到了"+num--);
		}
	}
	
	//线程不安全  锁定资源不正确
	public void test5(){
		//a  b  c
		synchronized((Integer)num){//以num作为对象,可以理解为大门没关严实,比如里面的flag都还没有关闭,所以这属于锁对象不正确导致线程不安全
			if(num<=0){
				flag=false; //跳出循环
				return ;
			}
			try {
				Thread.sleep(500); //模拟 延时
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"抢到了"+num--);
		}
	}
	
	//锁定范围不正确 线程不安全
	public void test4(){
		//   c  1
		synchronized(this){
			//b
			if(num<=0){
				flag=false; //跳出循环
				return ;
			}
		}
		// b
		try {
			Thread.sleep(500); //模拟 延时
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()+"抢到了"+num--);
	}//a -->1
	
	//线程安全  锁定正确
	public void test3(){
		//a  b  c
		synchronized(this){//也是以this作为锁,相当于是大门
			if(num<=0){
				flag=false; //跳出循环
				return ;
			}
			try {
				Thread.sleep(500); //模拟 延时
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"抢到了"+num--);
		}
	}
	//线程安全  同步方法,锁对象是this
	public synchronized void test2(){
		if(num<=0){
			flag=false; //跳出循环
			return ;
		}
		try {
			Thread.sleep(500); //模拟 延时
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()+"抢到了"+num--);
	}
	
	//线程不安全
	public void test1(){
		if(num<=0){
			flag=false; //跳出循环
			return ;
		}
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()+"抢到了"+num--);
	}
}

2、从单例设计模式认识线程同步

2.1 最简单的懒汉式单例

懒汉式单例模式设计步骤:

1 私有构造器      2 声明私有的静态实例对象      3 提供静态的访问该对象的方法

代码如下:

public class Jvm{
    private static Jvm instance = null;
    private Jvm(){}
    public static Jvm getInstance(){
        if(instance==null){
            instance = new Jvm();
        }
        return instance;
    }
}

测试单线程的时候,单例模式创建的多个对象是否是同一个对象:

public class JvmTest {

	public static void main(String[] args) {
		//单线程下创建两个Jvm对象
		Jvm instance1 = Jvm.getInstance();
		Jvm instance2 = Jvm.getInstance();
		System.out.println("两个对象是否相等:"+(instance1 == instance2));//两个对象是否相等:true
	}
}

由此可知,单线程的时候,这种简单的懒汉式单例模式是线程安全的。

 

2.2 多线程下,线程不安全的懒汉式

2.2.1 单例模式2

public class Jvm2 {
	private static Jvm2 instance = null;
    private Jvm2(){}
    public static Jvm2 getInstance(long time){
        if(instance==null){
        	try {
				Thread.sleep(time);//模拟线程阻塞,增加错误概率
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
            instance = new Jvm2();
        }
        return instance;
    }
}

2.2.2 创建线程类

public class JvmThread extends Thread{
	private long time;//创建对象的延迟时间,只是用来放大错误的
	public JvmThread(){
		
	}
	public JvmThread(long time){
		this.time = time;
	}
	@Override
	public void run() {
		//调用Jvm得到实例对象
		System.out.println(Thread.currentThread().getName()+"-->创建:"+Jvm2.getInstance(time));
	}
}

2.2.3 测试多线程下单例模式得到的对象不是同一个

public static void main(String[] args) {
		JvmThread jvmThread1 = new JvmThread(500);
		JvmThread jvmThread2 = new JvmThread(100);
		jvmThread1.start();
		jvmThread2.start();
	}

控制台输出:

Thread-1-->创建:org.dt.framework.util.Jvm2@3cf5b814
Thread-0-->创建:org.dt.framework.util.Jvm2@28084850

可以看到这种懒汉式的单例模式在多线程下是线程不安全的,创建了多个对象。

2.3 线程安全,效率低的懒汉式单例

因为上面的单例模式线程不安全,所以我们可以加上同步,使其线程安全,

2.3.1 修改Jvm2为Jvm3:

public class Jvm3 {
	private static Jvm3 instance = null;
    private Jvm3(){}
    public static Jvm3 getInstance(long time){
    	synchronized (Jvm3.class) {//加上锁,并且静态方法中,锁对象是该类的class文件
    		if(instance==null){
    			try {
    				Thread.sleep(time);//模拟线程阻塞,增加错误概率
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			instance = new Jvm3();
    		}
    		return instance;
		}
    }
}

2.3.2 修改线程类

修改线程类,调用的是Jvm3的单例:

public class JvmThread extends Thread{
	private long time;//创建对象的延迟时间,只是用来放大错误的
	public JvmThread(){
		
	}
	public JvmThread(long time){
		this.time = time;
	}
	@Override
	public void run() {
		//调用Jvm得到实例对象,这里改成了Jvm3
		System.out.println(Thread.currentThread().getName()+"-->创建:"+Jvm3.getInstance(time));
	}
}

运行上面的测试类,控制台打印:

Thread-0-->创建:org.dt.framework.util.Jvm3@8523ca2
Thread-1-->创建:org.dt.framework.util.Jvm3@8523ca2

可以看到,此时是线程安全的,多线程下我们得到的是同一个对象。

2.4 线程安全,效率高的懒汉式

       2.3的懒汉式单例已经是线程安全的,但是效率比较低。因为如果有多个线程访问它想得到对象,都要在synchronized外进行等待,获取锁对象,然后判断对象是否存在。获取锁对象是一个比较耗时的操作,多此获取锁对象会降低效率。那么我们如何优化呢?

      其实优化方式就是减少等待。所以我们可以在synchronized外再加上一层判断,只有当第一次的时候,对象不存在的时候才获取锁对象,创建实例。第二次以后,我们判断的时候,对象已经不为null,直接返回对象即可。

2.4.1 修改Jvm3为Jvm4

//双重检查的懒汉式单例
public class Jvm4 {
	private static Jvm4 instance = null;
    private Jvm4(){}
    public static Jvm4 getInstance(long time){
    	if(instance == null){
    		synchronized (Jvm4.class) {//加上锁,并且静态方法中,锁对象是该类的class文件
    			if(instance==null){
    				instance = new Jvm4();
    			}
    		}
    	}
    	return instance;
    }
}

这个也成为双重检查的懒汉式单例设计模式。

3、饿汉式的单例设计模式

3.1 常见的饿汉式单例

public class Jvm {
	//声明的时候就实例化,就不会存在线程不安全的情况,因为只要类一加载,就会实例化
	private static Jvm instance = new Jvm();
    private Jvm(){}
    public static Jvm getInstance(){
        return instance;
    }
}

该饿汉式单例模式是线程安全的,它的缺点就是 可能创建对象的时机比较早,只要类加载就会被创建,也许此时我们还不需要该对象。比如这个类中还有其它的方法,当我们调用其他方法的时候,类加载,该单例对象会被创建。如果我们需要在真正需要使用该对象的时候在创建,该怎么办呢?

3.2 优化饿汉式单例模式

public class Jvm2 {
	private static class Jvm2Holder{
		private static Jvm2 instance = new Jvm2();
	}
    private Jvm2(){}
    public static Jvm2 getInstance(){
        return Jvm2Holder.instance;
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值