1.线程状态
2.线程安全
当我们程序没错但是运行的结果却和预想的不一致时,就叫做线程不安全,(所以,有时候并不是我们的错,曾经拥有就很好了,让我们交给命运吧)
public static void main(String[] args) throws InterruptedException {
counter counter = new counter();
Thread th1 = new Thread(()->{
for (int i = 0; i < 50000; i++) {
counter.increase();
}
});
Thread th2 = new Thread(()->{
for (int i = 0; i < 50000; i++) {
counter.increase();
}
});
th1.start();
th2.start();
//等待执行完成
th1.join();
th2.join();
System.out.println("count = " + counter.count);
}
public class counter {
int count = 0;
public void increase(){
count++;
}
}
3.线程不安全的原因
1.线程调度是随机的
线程在cpu上是抢占式执行的,人为干预不了,是线程安全的主要原因,并且也和CPU的核数相关
2.修改共享数据(多个线程改变了同一个变量)
上面示例就是T1和T2都改变了count
3.线程执行过程中不能保证原子性
要么全部执行要么全部不执行
我们最后想要得到的结果是对count变量进行累加操作,但是count++语句会被分为多个指令,而CPU是随机执行的,所以线程是被抢占式执行,会出现T1没执行完就开始执行T2的操作,导致累加的count被覆盖的情况
4.内存不可见
Java内存模型(JMM):Java虚拟机规范中定义了Java内存模型. ⽬的是屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到⼀致的并发效果
简单来说就是各个线程之间感知不到各个内存的改变,就会可能造成主内存不及时的改变
5.指令重排序
我们写的代码在编译过程中可能不会按照代码的指令逻辑运行(JVM运行可能会重排,CPU执行指令也会重排)
指令重排序需要代码之间不会造成影响
4.JMM


5.解决线程安全问题
所以我们要解决线程安全的问题本质上需要解决原子性,内存可见性,指令重排序其中一个即可
synchronized
本质上是让线程并行转化为线程串行,通过给线程加锁,来不让其他线程抢先执行。
锁
通过给线程加锁,如果这个线程还没有执行完,别的线程就不会运行,如果这个线程运行完了,就对其解锁,让其他线程运行并加锁
1.实现了原子性
2.间接实现了内存可见性(通过原子性实现了线程的串行,从而实现了内存可见性)
3.未实现有序性
1.使用方法:可以给方法加上前缀,也可以写成代码块
2.与CPU调度的区别
注意:由于cpu的调度问题,当运行t1时,可能因为cpu的随即调度,去运行其他线程,但是由于t1已经上了锁,别的线程就会发生阻塞等待,所以又会调度回来,直到t1运行完为止,所以其实不是一直运行t1而是有一个过程
3.使用synchronized必须要保证是同一个锁对象
4.锁对象
锁对象记录了锁的信息,所有对象都能被定义为锁对象
锁对象内部有四个组成
1.markword:用来记录锁对象的状态
2.类型指针:当前对象的类型
3.实例数据:成员变量
4.对齐填充:一个对象所占的内存必须是8的整数倍,负责对齐
5.判断是不是锁对象 
6.锁竞争
必须要保证竞争的是同一把锁才是锁竞争
7.synchronized关键字-监视器锁-monitor lock
1.互斥:一个线程获取锁之后,其他线程必须阻塞等待,直到这个线程执行完,释放锁,再给其他锁进行竞争
2.可重入
volatile关键字
volatile主要解决的是当我们想要用一个线程去影响到另一个线程时,避免出现线程安全问题
1.不能保证原子性
2.保证了内存可读性
3.保证了有序性
当我们想要通过改变 变量的值来实现影响别的线程时,要给此变量加上volatile
static int flag = 0;
public static void main(String[] args) {
Thread th1 = new Thread(()->{
System.out.println(Thread.currentThread().getName() + "线程启动");
while(flag == 0){
//不断循环
}
System.out.println(Thread.currentThread().getName() + "线程结束");
},"t1");
th1.start();
//然后我们希望通过th2来改变flag由此来影响th1
Thread th2 = new Thread(()->{
System.out.println(Thread.currentThread().getName() + "线程启动");
Scanner scanner = new Scanner(System.in);
System.out.println("请输入一个非零整数: ");
flag = scanner.nextInt();
System.out.println(Thread.currentThread().getName() + "线程结束");
},"t2");
th2.start();
}
此时th1不会如愿收到影响
当在定义flag的地方改成static volatile int flag = 0就没问题了
分析问题产生的原因
volatile的底层原理
cpu层面
MESI缓存一致性原则
从上面我们可以得知,造成th2没有对th1造成改变的原因是th1的缓存没有和主内存保持一致性,从而造成了th1未停下来
而MESI缓存一致性原则就是保证了当某一处理器改变了主内存的值时,通知其他处理器重新改变缓存的值
Java层面
内存屏障:保证了内存的可读性,和有序性
会在volatile定义的变量的前后加上内存屏障,从而就手动制定了读和写的具体位置,保证了有序性
也可以理解为告诉了编译器,不要进行指令重排序
6.wait和notify
等待和通知功能,wait负责通知其他处理器,notify就是处理好了之后返回告知wait,使其走下一步

1.wait和notify必须与synchronized一起使用
2.notify相当于通知功能,能无限次调用
3.wait和notify是object的方法,join和sleep等是Thread的类方法