线程安全问题
由于多线程的(并行)引入有一些共享的代码块(资源)会出现不准确问题;
被总结为一下原因:不能一气呵成的执行完,共想变量没有可见性,在执行很多指令是,可能说出错。
线程安全必须满足一下要求
1,原子性
2,可见性
3,防止指令重派
用一个实例来说明问题所在
让两个线程各加5000;
import org.w3c.dom.css.Counter;
public class ThreadUnsafeCount {
public static class Counter{
int count=0;
void increase(){
count++;
}
}
public static void main(String [] args) throws InterruptedException {
Counter counter=new Counter();
Thread thread01=new Thread(()->{
for(int i=0;i<50000;i++){
counter.increase();
}
});
Thread thread02=new Thread(()->{
for(int i=0;i<50000;i++){
counter.increase();
}
});
thread01.start();
thread02.start();
//join()等待执行完成,在进行下面的代码
thread01.join();
thread02.join();
System.out.println(counter.count);
}
}
输出
69176
原本应该是100000,但是输出才69176(每一次输出可能会不一样)
这就是线程不安全导致的
在单线程下指令重排没有问题,但是在多线程下可能就会有问题,一般是对象还没初始化完成就被别的线程调用了。
具体例子
解决线程安全问题
这里有一些想操作系统中的 PV操作思想(获取到一个资源,对这个资源临界区同时只能一个进程或者线程访问,在退出时归还临界区)
首先要明白不同的对像不同的锁
import org.w3c.dom.css.Counter;
public class ThreadUnsafeCount {
public static class Counter{
int count=0;
void increase(){
count++;
}
}
public static void main(String [] args) throws InterruptedException {
Counter counter1=new Counter();
Counter counter2=new Counter();
Thread t1=new Thread(()->{
for(int i=0;i<5000;i++){
counter1.increase();
}
});
Thread t2=new Thread(()->{
for(int i=0;i<5000;i++){
counter2.increase();
}
});
t1.start();
t.start();
//join()等待执行完成,在进行下面的代码
t1.join();
t2.join();
int total=counter1.count+ counter2.count;
System.out.println(total);
}
}
这样输出的结果就是正确的(100000)
2,使用synchronized来实现
import org.w3c.dom.css.Counter;
public class ThreadUnsafeCount {
public static class Counter{
int count=0;
synchronized void increase(){
count++;
}
}
public static void main(String [] args) throws InterruptedException {
Counter counter=new Counter();
/*Counter counter01=new Counter();
Counter counter02=new Counter()*/;
Thread thread01=new Thread(()->{
for(int i=0;i<5000;i++){
/*counter01.increase();*/
counter.increase();
}
});
Thread thread02=new Thread(()->{
for(int i=0;i<5000;i++){
/* counter02.increase();*/
counter.increase();
}
});
thread01.start();
thread02.start();
//join()等待执行完成,在进行下面的代码
thread01.join();
thread02.join();
/*int total=counter01.count+ counter02.count;
System.out.println(total);*/
System.out.println(counter.count);
}
}
在方法块上加入synchronized就可以实现。
synchronized void increase(){
count++;
}
这里先了解一下 JMM(java Memory Model)
synchronized支持线程可重入
加入目前有两个线程,非别是 小红 和小吕(他们之前没有线程进入)
小红进入了一个synchronized锁定的代码块,在代码块中还有synchronized锁,小红还是可以进去,但是对于小吕来说第一个synchronized都进不去,主要原因是 他们是在同一个对象下实现的,一般这里是这样实现的,在synchronized头中定义一个计数器,进入一个线程就+1,退出就-1(和操作系统中的PV一个原理;
synchronized修饰静态方法
synchronized 对一个静态方法上锁
import sun.plugin2.message.BestJREAvailableMessage;
public class SychronizedInstance {
public static void main(String[] args) {
Counter counter01=new Counter();
Counter counter02=new Counter();
Counter counter03=new Counter();
Thread t1=new Thread(()->{
counter01.count();
//System.out.println(Thread.currentThread().getName()+"获取到了锁");
},"t1");
Thread t2=new Thread(()->{
counter02.count();
//System.out.println(Thread.currentThread().getName()+"获取到了锁");
},"t2");
Thread t3=new Thread(()->{
counter03.count();
//System.out.println(Thread.currentThread().getName()+"获取到了锁");
},"t3");
t1.start();
t2.start();
t3.start();
}
private static class Counter{
synchronized static void count() {
while(true){//如果没有while(true)循环 这个锁被其中一个对象占用后又释放,看不出静态
// 方法锁的一个类,所以静态方法是对这个类上的锁,和创建多少个对象无关
System.out.println(Thread.currentThread().getName()+"我是静态方法上的锁,锁的是整个类");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
截取输出的一部分
t2我是静态方法上的锁,锁的是整个类
t1我是静态方法上的锁,锁的是整个类
t3我是静态方法上的锁,锁的是整个类
t2我是静态方法上的锁,锁的是整个类
t1我是静态方法上的锁,锁的是整个类
方法锁的一个类,所以静态方法是对这个类上的锁,和创建多少个对象无关
你可以将将这里的 方法中的static去掉;如下,你就会感受到这是一个在并发的,并没有互斥的执行。
private static class Counter{
synchronized void count() {
while(true){//如果没有while(true)循环 这个锁被其中一个对象占用后又释放,看不出静态
// 方法锁的一个类,所以静态方法是对这个类上的锁,和创建多少个对象无关
System.out.println(Thread.currentThread().getName()+"我是静态方法上的锁,锁的是整个类");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}