线程安全性问题
1.多线程环境下
2.多个线程共享一个资源
3.对资源进行非操原子性作
public class Sequence {
private int value;
/**
* synchronized 放在普通方法上,内置锁就是当前类的实例
* @return
*/
public synchronized int getNext() {
return value ++;
}
/**
* 修饰静态方法,内置锁是当前的Class字节码对象
* Sequence.class
* @return
*/
public static synchronized int getPrevious() {
// return value --;
return 0;
}
public int xx () {
// monitorenter
synchronized (Sequence.class) {
if(value > 0) {
return value;
} else {
return -1;
}
}
// monitorexit
}
public static void main(String[] args) {
Sequence s = new Sequence();
// while(true) {
// System.out.println(s.getNext());
// }
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
System.out.println(Thread.currentThread().getName() + " " + s.getNext());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
System.out.println(Thread.currentThread().getName() + " " + s.getNext());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
System.out.println(Thread.currentThread().getName() + " " + s.getNext());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
通过命令查看java字节码,可以看到value 是一个共享变量,value++是两步操作[iadd,putfield],这是一个非原子操作因此如果不加synchronized在多线程条件下会发生线程安全性问题。
javap -verbose Seqence.class
活跃性问题
1.死锁
同时获取共享资源,又同时不释放资源就会死锁
2.饥饿与公平
低优先级的线程没有机会获取资源就会出现饥饿
高优先级吞噬所有低优先级的CPU时间片
线程被永久堵塞在一个等待进入同步块的状态
等待的线程永远不被唤醒
如何尽量避免饥饿问题
设置合理的优先级
使用锁来代替synchronized
3.活锁
同时获取资源,同时互相让对方,导致无法获取资源
性能问题
1.多线程序程在核单服务器上性能不一并定快
2.时间片切换非常占用CPU资源
3.线程间的上下文切花也非常消耗CPU资源
线程安全性问题的解决
1.Synchronized原理与使用
原理:
内置锁(Java中每一个对象都可以用作同步的锁,这些锁就可以被称之为内部锁),锁是互斥的,当代码进入到Synchronized代码块时,先拿到锁的线程可以进入执行状态,没有拿到锁的进入等待。
从字节码的角度来看进入同步代码块会执行monitorenter,执行完毕你会执行monitorexit,同步方法是通过另一种方式实现ACC_SYNCHRONIZED。
java的虚拟机中的锁的信息保存在对象的对象头中。
Mark Word 存放对象的Hash值,锁信息【线程id,Epoch,对象的分代年龄信息,是否是偏向锁,锁标志位】等
Class Metadata Address 对象类型地址等
Array Length 如果是数组会保存数组长度
用法:
修饰普通方法,synchronized 放在普通方法上,内置锁就是当前类的实例(this对象)
修饰静态方法,修饰静态方法,内置锁是当前的Class字节码对象
修饰代码块,可以锁任意对象
2.Synchronized锁的类型
偏向锁,等到竞争出现才会释放
每次获取锁和释放锁会浪费资源,
很多情况下,竞争锁不是由多个线程,而是由一个线程在使用。
试用场景:只有一个线程在访问同步代码块的场景,性能会比较高
轻量级锁
允许多个线程可以同时进入同步代码块,多个线程会Mark Word查看锁类型,当第一个线程获得锁以后第二个线程会自动升级成重量级锁。
线程在获取资源时,如果获取不到会通过自旋的方式【消耗cpu资源相当于while(true)】等待获取。
重量级锁
3.自旋锁,死锁和锁重入
a.自旋锁 :当一个线程拿到对象头信息进入栈中时,有其他线程进入后会一直自旋等待其他线程执行完毕。
import java.util.Random;
/**
* 多个线程执行完毕之后,打印一句话,结束
*/
public class Demo {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 线程执行...");
try {
Thread.sleep(new Random().nextInt(2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 线程执行完毕了...");
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 线程执行...");
try {
Thread.sleep(new Random().nextInt(2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 线程执行完毕了...");
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 线程执行...");
try {
Thread.sleep(new Random().nextInt(2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 线程执行完毕了...");
}
}).start();
while(Thread.activeCount() != 1) {
// 自旋
}
System.out.println("所有的线程执行完毕了...");
}
}
b.锁重入:同一线程可以进入锁住同一个对象的多个代码块。
执行结果a , b会很快被打印出来证明锁可以重入
public class Demo {
public synchronized void a () {
System.out.println("a");
b();
}
public synchronized void b() {
System.out.println("b");
}
//执行结果a , b会很快被打印出来证明锁可以重入
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
Demo d = new Demo();
d.a();
}
}).start();
}
}
不同的线程拿同一个对象加锁先调用a(),再调用b();
再a()没有执行完的时候调用b()会被锁住,需要等a()执行完毕。
public class Demo {
public synchronized void a () {
System.out.println("a");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void b() {
System.out.println("b");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Demo d1= new Demo();
new Thread(new Runnable() {
@Override
public void run() {
d1.a();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
d1.b();
}
}).start();
}
}
不同的线程拿不同对象加锁先调用a(),再调用b();
再a()没有执行完的时候调用b()不会被锁住,a,b很快会被打印出来,所有加锁应该用同一对象来调用。
public class Demo {
public synchronized void a () {
System.out.println("a");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void b() {
System.out.println("b");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Demo d1= new Demo();
Demo d2= new Demo();
new Thread(new Runnable() {
@Override
public void run() {
d1.a();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
d2.b();
}
}).start();
}
}
c.死锁:线程之间彼此拿着对方需要的资源,等待对方释放资源
public class Demo {
private Object obj1 = new Object();
private Object obj2 = new Object();
public void a () {
synchronized (obj1) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj2) {
System.out.println("a");
}
}
}
public void b () {
synchronized (obj2) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj1) {
System.out.println("b");
}
}
}
public static void main(String[] args) {
Demo d = new Demo();
new Thread(new Runnable() {
@Override
public void run() {
d.a();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
d.b();
}
}).start();
}
}