死锁
两个或者多个线程都在等待对方释放锁,在写多线程代码时要注意避免这种死锁的发生
发生死锁后可以在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关键字可以保证多个线程之间共享变量的可见性,只能修饰变量,不能修饰方法
画个图来说明:
使用这个关键字之前,比如说线程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