本文已参与「新人创作礼」活动,一起开启掘金创作之路。
线程状态
1.NEW 安排了工作但是没有去执行
2.RUNNABLE 可工作的,又可以分成正在工作和即将开始工作
3.BLOCKED 表示排队等着其他事情
4.WAITING 表示排队等待其他事情
5.TIMED_WAITING 表示排队等待其他事情
6.TERMINATED 工作完成了
这些可以通过t.getState()来了解指定线程的状态
public class Demo11 {
public static void main(String[] args) {
Thread t = new Thread(()->{
while(true){
System.out.println("hello");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
System.out.println(t.getState());
t.start();
while(true){
System.out.println(t.getState());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
这是关于线程状态的转换,我画了一个简图。比较好了解。
线程安全问题
是什么引起的线程安全问题呢?
是之前提到的线程抢占式执行,就让我们程序执行什么线程变成了随机。让我们有点捉摸不透。
有一段代码可以明显看出这一点。
class test{
public int count;
public void add(){
count++;
}
}
public class Demo12 {
public static test t = new test();
public static void main(String[] args) {
Thread t1 = new Thread(()->{
for(int i = 0;i<5000;i++){
t.add();
}
});
Thread t2 = new Thread(()->{
for(int i = 0;i<5000;i++){
t.add();
}
});
t1.start();
t2.start();
try {
t2.join();
t1.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(t.count);
}
}
这个是我执行之后的结果,当你多运行几次之后你会发现,这个数字不等于10000,我们按道理来讲,这两个线程都让t里面的count执行++操作5000次,那么加起来是10000,那为什么会没有达到呢?
这就是抢占式的弊端。因为我们在进行add()这个方法的时候,并不是只有一个操作。是有三个操作的,第一步是读取,第二步是加1,第三步是写入。既然这个操作没有原子性,那么其他的线程就会在这个线程在做某一步的时候强行插队。就会导致最后的答案不一样。两个线程对于一个变量的操作是比较危险的。
如何解决上面的问题呢?
那就是加个锁
加锁
加锁就可以让我们避免前面的问题。让++的操作原子性。这个就好像是什么呢?
就好像去银行ATM取钱,在我们取钱的时候都会把门给锁上,这样别人就进不来了,
而且这边的重点是,这个门只有一个锁,
如果有两把锁,两把都可以开这个门的话,就会让锁没有用了。
加锁之后弊端就是,并发变成了串行。速度变慢了,但是安全了。
synchronized
这个词还是很重要的。
class test{
public int count;
synchronized public void add(){ //对于方法加锁
count++;
}
}
public class Demo12 {
public static test t = new test();
public static void main(String[] args) {
Thread t1 = new Thread(()->{
for(int i = 0;i<5000;i++){
t.add();
}
});
Thread t2 = new Thread(()->{
for(int i = 0;i<5000;i++){
t.add();
}
});
t1.start();
t2.start();
try {
t2.join();
t1.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(t.count);
}
}
加锁之后输出就是10000了。
内存可见性的安全问题
当两个线程对于一个变量分别同时做读和写的操作,这个就会有线程安全问题
当我们对于一个变量进行了频繁的读取的时候,而且这个变量没有改变的时候,
我们的编译器就会对这个线程进行优化,就不会进行读取操作,
如果这个时候,其他线程对这个变量进行了修改,那么优化过之后线程就不会读取到了。
来看代码
public class Demo13 {
private static int quit = 0;
public static void main(String[] args) {
Thread t = new Thread(()->{
while(quit == 0){
}
System.out.println("Thread end");
});
t.start();
Scanner sc = new Scanner(System.in);
quit = sc.nextInt();
System.out.println("main end");
}
}
当我们输入1的时候,正常情况下面进程会结束,但是实际操作之后发现结果不是这个。
我们发现main end 打印了,但是迟迟没有打印Thread end。
这个就是内存可见性的危害
解决方法
1.加锁 synchronized
2. volatile
public class Demo13 {
private static volatile int quit = 0;
public static void main(String[] args) {
Thread t = new Thread(()->{
while(quit == 0){
}
System.out.println("Thread end");
});
t.start();
Scanner sc = new Scanner(System.in);
quit = sc.nextInt();
System.out.println("main end");
}
}
以上代码就线程安全了。
指令重排序
这个就和上面那个一样,是编译器优化带来的线程安全问题。
指令重排序就是让一些指令改变操作顺序来提高效率。
解决方法
1.加锁 synchronized
synchronized 的用法
1.直接修饰普通的方法
synchronized public void add(){
}
2.修饰代码块
public void add(int index){
synchronized (Demo14.class){
index++;
}
}
3.修饰静态方法
synchronized public static int add(int a , int b){
return a+b;
}