多线程概念:
在同一时间段内,有2个或以上的线程在同时进行。
引发的问题:
1.竞态条件:
public class hello {
private static volatile int num = 0;
public static void count(String s)
{
num++;
System.out.println(s+' '+num);
}
public static void main(String[] args)
{
Runnable runnable1 = ()->{
while(true) {
count("1 ");
}
};
Runnable runnable2 = ()->{
while(true) {
count("2 ");
}
};
Thread thread1 = new Thread(runnable1);
Thread thread2 = new Thread(runnable2);
thread1.start();
thread2.start();
}
}
如我们所见,上面一段代码中,有2个线程,他们的run()方法都同时访问了同一个变量num,这会引起什么问题呢?其实num++看似是一个操作,其实是三个操作,读取num,修改num,更改num,那么就会有一个问题了,线程1读取num,修改num,但还没有修改num,这时线程2读取了num,完成了一系列操作,那么num最后的结果为1, 而正确的结果应该是2.
2.数据竞争
public class hello {
private static String num = null;
public static void count()
{
if(num == null)
num = new String();
}
public static void main(String[] args)
{
Runnable runnable1 = ()->{
count();
};
Runnable runnable2 = ()->{
count();
};
Thread thread1 = new Thread(runnable1);
Thread thread2 = new Thread(runnable2);
thread1.start();
thread2.start();
}
}
数据竞争和竞态条件十分相似,但数据竞争往往指的是一段代码,而不是某一个变量,就像上面的代码,只是举一个栗子,执行完之后,num有可能曾经指向过2个不同的内存。
3.缓存变量。
这个东西其实指的就是我们常说的多线程缓存一致性,(我觉得只会发生在多核cpu里面,因为我在我电脑没有碰到过这种情况),现在我们的电脑对内存进行了优化,如果所有的内存操作都要访问内存,效率必然不高,所以cpu通常会把一些内存操作存进缓存里,而不是对主存进行直接更新,这就导致了一个问题,若2个线程在2个不同的cpu上面运行,那么他们就用到了不同缓存,它们对静态变量的操作只会在他们自己对应的缓存里更新(只对这个变量的拷贝进行更新,这个拷贝存在缓存当中),而如果现在主线程去读取这个变量的时候,往往变量的值不会发生改变。
java解决方法:
解决问题1,2:
我们只需要在对应代码段或者方法名前加上synchronized就可以了,代码如下:
import javax.naming.ldap.PagedResultsControl;
public class hello {
private static volatile int num = 0;
public static synchronized void count(String s)
{
num++;
System.out.println(s+' '+num);
}
public static void main(String[] args)
{
Runnable runnable1 = ()->{
while(true) {
count("1 ");
}
};
Runnable runnable2 = ()->{
while(true) {
count("2 ");
}
};
Thread thread1 = new Thread(runnable1);
Thread thread2 = new Thread(runnable2);
thread1.start();
thread2.start();
}
}
有什么用呢,这个关键字可以保证在同一时间内只能有一个线程进入该代码段,其实就是把这段代码从异步变成同步,实际上就是加上了一个锁。
解决问题3:
volatile关键字,首先我来说一下这个关键字的作用,我们前面都提到了我们在一个线程里面修改某一个静态变量里的值,其实修改的缓存里的值,而并非是主存里的值,加上这个关键字修饰变量以后,我们所有的修改操作都是经过主存的,从而解决了这一问题,但他并不能保证同步,他只是一个用来解决读取的都是主存。