java 多线程 死锁和valitile关键字

死锁

两个或者多个线程都在等待对方释放锁,在写多线程代码时要注意避免这种死锁的发生

发生死锁后可以在dos命令行输入jps命令查看java进程状况

可以试用jstack -l 进程号   命令查看当前类的问题

关闭jvm停止死锁

 

以上节三个公司卖电影票为例,指出写问题,这个不是死锁啊,先看代码:

package collection;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;

//开启三个线程
public class synchronizedTest06 {
	public static void main(String[] args) {
		Ticket tc = new Ticket();
		Thread t1 = new Thread(tc, "美团电影");
		Thread t2 = new Thread(tc, "淘票票");
		Thread t3 = new Thread(tc, "猫眼电影");
		t1.start();
		t2.start();
		t3.start();

	}
}

class Ticket implements Runnable {
	// 100张电影票
	ArrayList ticketArr;
	Random random;

	public Ticket() {
		ticketArr = new ArrayList<>();
		random = new Random();
		for (int i = 0; i < 100; i++) {
			ticketArr.add(i + 1);
		}
	}

	@Override
	public synchronized void run() {
                
		while (true) {
                   //     if (ticketArr.size()<=0){
                   //           break;
                   //    }
			int j = random.nextInt(100) + 1;
			if (ticketArr.contains(j)) {
				// 利用循环迭代来删除List中的元素,直接删除可能会报错,因为删除元素后List元素的索引会变化,所以不能只用用索引来删除
				Iterator it = ticketArr.iterator();
				while (it.hasNext()) {
					if (it.next().equals(j)) {
						it.remove();
						break;
					}
				}
				System.out.println(Thread.currentThread().getName() + "卖出了第" + j + "张票,还剩余" + ticketArr.size() + "张票");

			}

		}

	}

}

这段代码注释掉了线程结束的条件,运行效果是只有一个线程会打印出所有的电影票,且打印完以后,程序不会停止,其他线程在等待这个线程释放锁,但是这并不是死锁,因为当前线程没有要运行被别的线程锁住的语句块或者方法

下面这个示例演示了死锁:

public class DeadLock {

	public static void main(String[] args) {
		Object obj1 = new Object();
		Object obj2 = new Object();
		Thread t1 = new Thread(new Dead(obj1, obj2));
		Thread t2 = new Thread(new Dead(obj2, obj1));
		t1.start();
		t2.start();

	}
}

class Dead implements Runnable {
	Object obj1;
	Object obj2;

	public Dead(Object obj1, Object obj2) {
		super();
		this.obj1 = obj1;
		this.obj2 = obj2;
	}

	@Override
	public void run() {
		
		synchronized (obj1) {
			System.out.println(Thread.currentThread().getName() + obj1);
			synchronized (obj2) {
				System.out.println(Thread.currentThread().getName() + obj2);
			}
		}
	}
}

上面 程序发生死锁,线程1先锁住obj1再锁住obj2,线程2先锁住obj2再锁住obj1.比如线程1先执行,锁住obj1,此时(线程2开始执行,锁住obj2,此时无论哪个线程都无法继续执行,都在等待对方释放锁,还有一种情况,线程1继续执行锁住obj2,然后释放掉锁,线程2就可以运行了,但这种情况发生的几率非常小)

 

Java 内存模型中的可见性、原子性和非原子性

可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的,

原子性:原子具有不可分割性,即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么都不执行

非原子性:不复合原子性的都是非原子性

       举例说明,int a=10;return 语句,这些操作是原子性,可以一次性执行完,规定基本数据类型的赋值是原子性的,

a++ 这个操作是先取出a,再将a+1,是可分割的,所以是非原子性的,非原子性的都会存在线程安全问题,

synchronized关键字可以让多条语句变成原子性的,比如同步方法,同步代码块

 

volatile关键字

             使用volitale关键字可以保证多个线程之间共享变量的可见性,只能修饰变量,不能修饰方法

画个图来说明:

java 多线程 死锁和valitile关键字_第1张图片

使用这个关键字之前,比如说线程t1在执行一个方法,调用了对象的属性flag,有些环境为了提高效率,将用到的属性保存到线程的私有内存,不再对堆内存中的属性进行访问,当线程t2对这个对象的属性修改后,线程t1用的还是私有内存中的flag值,这个时候就会出现线程安全问题,试用volitale关键字修饰属性后,共享变量具有可见性,就不会再出现上述问题

例子说明:

package chen_chapter_9;

public class VolitaleTest01 {
	public static void main(String[] args) {
		Work work = new Work();
		Thread t1 = new Thread(work, "线程t1");
		t1.start();
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	
		// 在某些环境下,线程会将堆内存中变量的值保存到自己的私有内存来提高效率,该线程不再访问堆内存中的值
		// 这时在主线程中修改堆内存中的值对上述线程无影戏
		work.setAge(20);
	}
}

class Work implements Runnable {
	private volatile int age = 9;

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public void run() {

		while (age<10) {
			System.out.println(Thread.currentThread().getName() + " : " + getAge());
		}

	}
}
out:
我的环境没有出现变量不可见的情况,在这里将其用volitale修饰是为了方便,不再修改了

在64位机上,以server模式运行,不加volitale关键字可能出现上述变量不可见的情况

修改为server的方法,在eclipse代码框上,右键-Run As-Run Configurations-Arguments-第二行的VM arguments,框中输入-server就会以服务器模式运行.

JVM的运行可以分两种模式:

client:启动快,运行后性能不如server模式,一般运行时默认是client模式

server:启动慢,运行后性能比client模式好。

volitale关键字不具有原子性

参考:http://www.monkey1024.com/javase/682

你可能感兴趣的:(JavaSE)