目录
一、isAlive()
二、 join()
三、start和run
四、 volatile()
五、synchronized
1、synchronized 引入
2、死锁
第一种情况:反复加锁
第二种情况 对不同对象嵌套加锁的死锁
3、形成死锁条件
六、wait和notify
一、isAlive()
isAlive表示线程时候结束,判断时候回调函数(这里是run)时候结束.
public class Test2_isAlive {
public static void main(String[] args) {
Thread t=new Thread(()->{
System.out.println("线程开始");
try {
Thread.sleep(1000);//只是用来让程序暂时休息下,方便看结果
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("线程结束");
});
t.start();//其次被执行
System.out.println(t.isAlive());//首先被执行,因为t.start需要准备一些资源
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(t.isAlive());//最后执行,会是false,也可能run中执行的慢也会被提前执行,true
}
}
第一种情况,run还没有执行完毕,也就是回调函数还没有执行完毕,执行了最后一次System.out.println(t.isAlive());
第二种情况,run执行完毕了,在执行最后一次 System.out.println(t.isAlive());
我们可以加大sleep()的时间来选择这两次结果
二、 join()
这是一个等待函数,创建两个线程A 和 B,对应的实例分别是a和b,在A中用b调用了join(),那么A线程会被暂停,等待B线程执行完毕在执行
public class Thread_join {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
for (int i = 0; i < 5; i++) {
System.out.println("t线程工作中");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
System.out.println("join开始等待");
t.join();
System.out.println("join等待结束");
}
}
描述: 我们在main线程中使用Thread的t实例调用了join()方法,main需要等待Thread 执行完毕在执行
三、start和run
四、 volatile()
我们先来聊聊什么叫内存可见性?
我们先来看一下下面代码,这里定义了一个变量flag通过线程t1判断flag的值看是否循环,通过改变t2来改变flag的值终止t1的循环,我们预期输入1来终止t1的循环
public class Thread_volatile {
public static int flag=0;
public static void main(String[] args) {
Thread t1=new Thread(()->{
while (flag==0){
//这个循环一秒钟会执行很多次
}
System.out.println("t线程推出");
});
t1.start();
Thread t2=new Thread(()->{
System.out.println("请输入flag值");
Scanner scan=new Scanner(System.in);
flag=scan.nextInt();
});
t2.start();
}
}
程序进入了死循环,为啥呢?
这就是内存的可见性问题,编译器在编译t1的进程while循环时,发现每次需要从内存中读取flag的值在进行while条件判断,由于这个while循环执行非常快,短时间内需要大量读flag的值,在判断,但是这个从内存读取flag的值的速度时判读的速度的千分之一甚至是万分之一倍,所以编译器决定只进行一次读取,进行大量判断,编译器初心是为了提高代码性能,如果放在单线程中没有问题,但是这是多线程。
解决方案(不让编译器自动优化代码了,就是进行多次读取)
给flag加上volatile修饰
public volatile static int flag=0;
五、synchronized
1、synchronized 引入
- 进入 synchronized 修饰的代码块, 相当于 加锁
- 退出 synchronized 修饰的代码块, 相当于 解锁
我们先来看一组简单代码:
public class Test_Synchronized {
public static int count=0;
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
for (int i = 0; i < 5000; i++) {
count++;
}
});
Thread t2=new Thread(()->{
for (int i = 0; i <5000 ; i++) {
count++;
}
});
t1.start();
t2.start();
t1.join();//这里不加join会在没有加完情况下打印count的值
t2.join();
System.out.println(count);
}
}
描述:创建两个线程每个线程让count变量加5k次,最后我们的预期结果应该是1w。可是如下:
原因分析: count++这个操作本质上,是分为三部进行的
- load 把数据从内存读取到cpu寄存器中
- add 把寄存器中的数据加1
- save 把寄存器中的数据保存到内存
我们的理想状态是两个线程分别执行count++,可是线程之间是并发执行的呀,假设count开始值是0,如果我们在执行a线程的count++的时候就是上面count++三部曲,执行到了第二步,如果这个时候b线程也开始执行三部曲的第一步开时读取count的值那肯定是和a线程读取的一样都是0,因为这时候a线程还没有改变count的值。继续往后走,当a线程执行完毕三部曲,count变成1,a线程继续读取继续执行,这时候b线程执行完了,把count也变成了1,b线程继续执行。发现问题了吗?
解决方案一
我们先执行完毕一个线程,在执行一个另外一个线程,代码如下:
t1.start();
t1.join();//这里不加join会在没有加完情况下打印count的值
t2.start();
t2.join();
System.out.println(count);
解决方案二
使用synchronized加锁,如果加锁的对象一样,那么后者需要等待前者加锁部分执行完毕在执行
举个例子理解:多个人等待上一个厕所,这个厕所同一时间只能允许一个上,上厕所都要锁门,假设现在a在上厕所,后面依次是b,c,d在排队,当a上完厕所出来,后面下一个上厕所的人不一定是b这是有优先级的,而且也不是下一个人立刻上厕所,有一个cpu调用过程
public class Test_Synchronized {
public static int count=0;
public static void main(String[] args) throws InterruptedException {
Object object=new Object();
Thread t1=new Thread(()->{
synchronized (object){
for (int i = 0; i < 5000; i++) {
count++;
}
}
});
Thread t2=new Thread(()->{
synchronized (object){
for (int i = 0; i < 5000; i++) {
count++;
}
}
});
t1.start();
t2.start();
t1.join();//这里不加join会在没有加完情况下打印count的值
t2.join();
System.out.println(count);
}
}
2、死锁
第一种情况:反复加锁
举个例子:我们已经对一个对象加锁a了,但是现在我们在这个锁里面在对这个对象加锁b,这样外层锁执行到b锁的时候,b锁要等待这个已经加a锁的对象执行完毕在执行,可是a锁又要执行b锁才能执行完,程序就卡住了
解决方案
第二种情况 对不同对象嵌套加锁的死锁
举个例子:你想开车,但是车钥匙在房间里,你想进房间拿钥匙,但是房间钥匙在锁在了你想开的车里,很离谱把,就是这么离谱,哈哈哈
代码什么情况下会出现死锁?
public class Deal_Synchronized2 {
public static void main(String[] args) {
Object object1=new Object();
Object object2=new Object();
Thread t1=new Thread(()->{
synchronized (object1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (object2){
System.out.println("t1加锁完毕");
}
}
});
Thread t2=new Thread(()->{
synchronized (object2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (object1){
System.out.println("t2加锁完毕");
}
}
});
t1.start();
t2.start();
}
}
描述:t1线程和t2线程开始都会先获得object1的锁和object2的锁,当t1线程执行到synchronized (object2)的时候,t2对象已经对object2加上锁了,t1进程进入阻塞态等待object2释放,但是t2线程的object2需要等待t1线程执行完毕,所以程序进入阻塞态
解决方案
在编写锁的时候,注意先编写序号小的锁,后编写序号大的锁(或先大后小)要有一定规则,修改代码如下:
public class Deal_Synchronized2 {
public static void main(String[] args) {
Object object1=new Object();
Object object2=new Object();
Thread t1=new Thread(()->{
synchronized (object1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (object2){
System.out.println("t1加锁完毕");
}
}
});
Thread t2=new Thread(()->{
synchronized (object1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (object2){
System.out.println("t2加锁完毕");
}
}
});
t1.start();
t2.start();
}
}
3、形成死锁条件
- 互斥使用.(锁的基本特性)当一个线程持有一把锁的时候,另一个线程也想获取到锁,就要阻塞等待
- 不可抢占(锁的基本特性)当锁已经被线程1拿到之后,线程2只能等待线程1主动释放
- 请求保持(代码决定)一个线程尝试获取多把锁(有锁1了还想得到锁2,在获取锁2的过程不会释放锁1)
- 循环嵌套锁,你想开车,但是车钥匙在房间里,你想进房间拿钥匙,但是房间钥匙在锁在了你想开的车里
我们在以后解决的主要针对第4条就可以,即注意锁的编号
六、wait和notify
wait是在线程还没有结束的时候进入阻塞,join是影响线程的结束顺序,wait需要用notify唤醒
public class Wait_Notify {
public static void main(String[] args) {
Object object=new Object();
Thread t1=new Thread(()->{
synchronized (object){
System.out.println("wait之前");
try {
object.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("wait之后");
}
});
Thread t2=new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (object){
System.out.println("进行通知");
object.notify();
}
});
t1.start();
t2.start();
}
}