java——线程安全总结

存在线程安全问题的三个条件

1、多线程并发
2、有共享数据(重点)
java中的三大变量:实例变量、静态变量、局部变量
实例变量和静态变量分别存在堆内存和方法区内存,是唯一的,属于共享数据,故线程不安全
而局部变量是在栈内存中,一个栈内存是一个线程,局部变量永远不会共享,所以是线程安全的。

3、共享数据有修改行为

解决线程安全问题的方法:线程同步机制(关键字:synchronized)

线程同步的是让线程排队执行,实际就是线程不能再并发了。线程同步会牺牲一定的效率。
案例:两个人向同一个账户同时取钱,假设账户为10000元,两个人各取5000,如下列代码:

package 多线程;

public class 两个线程对同一账户取款 {
	public static void main(String[] args) {
		Account act=new Account("act001",10000);//初始化银行账户,拥有10000存款
		Thread t1=new AccountThread(act);t1.start();
		Thread t2=new AccountThread(act);t2.start();
	}
}
//线程类,代表用户
class AccountThread extends Thread {
	private Account act;
	
	public AccountThread(Account act) {
		this.act=act;
	}
	public void run() {
		double money=5000;
		act.withdraw(money);
		System.out.println(act.getActno()+"取款成功,余额"+act.getBalance());
	}
}
//账户类,代表银行账户
class Account {
	private String actno;
	private double balance;//账户余额
	public Account() {
		
	}

	public Account(String actno, double balance) {
		this.setActno(actno);
		this.setBalance(balance);
	}

	public String getActno() {
		return actno;
	}

	public void setActno(String actno) {
		this.actno = actno;
	}

	public double getBalance() {
		return balance;
	}

	public void setBalance(double balance) {
		this.balance = balance;
	}
	// 取款方法
	public void withdraw(double money) {
		/*加入synchronized关键字后,synchronized用括号包住的代码不能并发
		 * 单线程t1执行到这以后,若还没结束,另一个线程t2也来到这里,则t2线程要进行等待
		 * 当t1执行完synchronized下的代码后,t2才可以开始执行这段代码,多个线程原理相同
		 */
		synchronized(this) {
			double before = this.balance;
			double after = before - money;
			this.setBalance(after);
		}
	}
}

synchronized的括号中要写是需要同步的线程,即共享的线程对象。上述代码中的共享对象是账户对象,所以是this,而t1和t2都是操作同一个账户对象 act,所以这两个线程要同步。
线程t1执行到synchronized处后,会释放之前占有的时间片,进入锁池找共享对象,此时共享对象this没有被占用,则占有这把锁(一个对象只有一把锁),进入到就绪态;然后线程t2执行到了synchronized,放弃时间片,进入锁池,但共享对象this被占用,t2则排队等待,直到t1执行完synchronized中的代码,释放锁,t2就可以占用这把锁,开始执行synchronized中的代码。
如果在上面加入另一个人对另一个账户取款,即在main方法中加入以下代码:

Account act2=new Account("act001",10000);//初始化银行账户,拥有10000存款
Thread t3=new AccountThread(act2);t3.start();

此时,线程t3无需在t2后面排队,因为t3执行到synchronized处,括号中的共享对象是act2,此时没有线程占用。

synchronized的其他用法

三大变量中,局部变量已经线程安全,实例变量通过synchronized(对象){代码块}或在方法声明中加入synchronized关键字(此时的共享对象是this,代码块是整个方法的内容)可以保证线程安全。

而静态变量加上synchronized,此时的锁是类锁,一个类(假设类名为Myclass)中的静态方法在内存只有一个,存在方法区中,无论创多少个对象,都是只有一个静态方法,故线程不安全,加上synchronized后,多个线程操作多个Myclass对象的synchronized静态方法,虽然是不同的对象,但操作的是静态方法只有一个内存区,类锁只有一个,因此多个线程要等待。

synchronized面试题

问,t2是否要等待t1执行 dosome()完才能执行doother()

public class synch面试题 {
	public static void main(String[] args) {
		Myclass mc=new Myclass();
		Th t1=new Th(mc);   Th t2=new Th(mc);
		t1.setName("t1");   t2.setName("t2");
		t1.start();
		try {
			Thread.sleep(1*1000);
		} catch (InterruptedException e) {
		}
		t2.start();
	}
}
class Th extends Thread{
	Myclass mc;
	public Th(Myclass mc){
		this.mc=mc;
	}
	public void run() {
		if(Thread.currentThread().getName()=="t1") {
			mc.dosome();
		}else {
			mc.doother();
		}
	}
}
class Myclass{
	public synchronized void dosome() {
		System.out.println("dosome");
		try {
			Thread.sleep(1000*10);
		} catch (InterruptedException e) {
		}
	}
	public  void doother() {
		System.out.println("doOther begin");
		System.out.println("doOther Over");
	}
}

注意:两个线程共享一个对象,若执行不一样的方法,其中一个方法没有synchronized,则该线程不需要等待,因为虽然对象共享,但只有执行到了synchronized,才会去锁池中找锁,否则是异步执行。 线程t2执行的代码没有synchronized,因此不需要排队
因此答案为:不需要等待。

若将Myclass改为如下:

class Myclass{
	public synchronized  void dosome() {
		System.out.println("dosome begin");
		try {
			Thread.sleep(1000*10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("dosome Over");
	}
	
	public synchronized void doother() {
		System.out.println("doOther begin");
		System.out.println("doOther Over");
	}
}

答案为:要等待
doOther()也加上synchronized,t2执行到synchronized,要去找锁池找共享对象this,也就是mc,此时mc被t1占用。

若代码再改为:

public class synch面试题 {
	public static void main(String[] args) {
		Myclass mc=new Myclass();
		Myclass mc2=new Myclass();
		Th t1=new Th(mc);Th t2=new Th(mc2);
		t1.setName("t1"); t2.setName("t2");
		t1.start();
		try {
			Thread.sleep(1*1000);
		} catch (InterruptedException e) {
		}
		t2.start();
	}
}
class Th extends Thread{
	Myclass mc;
	public Th(Myclass mc){
		this.mc=mc;
	}
	public void run() {
		if(Thread.currentThread().getName()=="t1") {
			mc.dosome();
		}else {
			mc.doother();
		}
	}
}

class Myclass{
	public static synchronized  void dosome() {
		System.out.println("dosome begin");
		try {
			Thread.sleep(1000*10);
		} catch (InterruptedException e) {
		}
		System.out.println("dosome Over");
	}
	public static synchronized void doother() {
		System.out.println("doOther begin");
		System.out.println("doOther Over");
	}
}

答案为:要等待
此时,两个线程操作不同的对象,但由于静态方法区只有一个,t1执行到 dosome()后,占用了类锁,t2执行到doother(),也需要去找类锁,但此时类锁被t1占用,故要等待。

死锁

public class 死锁 {
	public static void main(String[] args) {
		Object o1=new Object();
		Object o2=new Object();
		Th1 t1=new Th1(o1,o2);
		Th2 t2=new Th2(o1,o2);
		t1.start();
		t2.start();
	}
}

class Th1 extends Thread{
	Object o1;
	Object o2;
	public Th1(Object o1,Object o2) {
		this.o1=o1;
		this.o2=o2;
	}
	
	public void run() {
		synchronized (o1) {
			try {
				Thread.sleep(1000*2);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized (o2) {
				System.out.println(1);
			}
		}
	}
	
}

class Th2 extends Thread{
	Object o1;
	Object o2;
	public Th2(Object o1,Object o2) {
		this.o1=o1;
		this.o2=o2;
	}
	
	public void run() {
		synchronized (o2) {
			try {
				Thread.sleep(1000*2);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized (o1) {
				System.out.println(2);
			}
		}
	}
}

上述代码中,线程t1和t2共享o1和o2,而t1先占用o1,再占用o2,而t2先占用o2,再占o1.但t1占用o1后还没来的及占o2,o2就被t2占了,于是t1等待t2释放o2,t2等待t1释放o1,这种情况永远不会等到,陷入了死锁状态。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值