多线程(五)
关于多线程基础部分就主要在前面四节讲解了,如果还有小伙伴有疑问或者笔者梳理的有问题的话,欢迎在下方评论区指正,大家一起学习共同进步。结束了基础知识,我们进入到并发工具和锁的学习。
线程间的共享
多线程完成任务必然会存在一个数据共享的过程,而这个过程我们需要采取措施来避免出现错误。
1.首先讲一下synchronized内置锁
每个线程如果单独的运行,相互之间不进行协作,那他们产生的结果也不会有太大的价值,如果线程之间能过共同完成任务,那这个结果也会十分有价值,而共同完成任务的同时需要保证数据的准确性,毕竟如果结果不对,那做再多的工作也将是无用功。java程序本身就是支持多线程的,当多个线程同时访问一个对象时,我们可以加上synchronized关键字修饰方法或者同步代码块,它能够保证同一时刻,只有一个线程可以进入方法或者代码块内。
现在我们用代码演示下在多线程的情况下synchronized关键字使用前和使用后的情况。
public static void main(String[] args) {
Util util = new Util();
SelfThread3 selfThread3 = new SelfThread3(util);
for (int i =0;i<5;i++){
new Thread(selfThread3).start();
}
Thread.sleep(10000);
}
//线程
class SelfThread3 implements Runnable{
private Util util;
public SelfThread3(Util util){
this.util = util;
}
public void run(){
try {
util.job();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//业务工具类
class Util{
private int add1;
public void job() throws InterruptedException {
System.out.println(Thread.currentThread().getName()+"--------"+"线程开始处理业务");
Thread.sleep(2000);//模拟业务处理
System.out.println(Thread.currentThread().getName()+"--------"+"线程结束处理业务");
}
// public synchronized void job() throws InterruptedException {
// System.out.println(Thread.currentThread().getName()+"--------"+"线程开始处理业务");
// Thread.sleep(2000);
// System.out.println(Thread.currentThread().getName()+"--------"+"线程结束处理业务");
// }
}
首先演示了在业务处理方法不加synchronized关键字的情况:
不加synchronized关键字所有的线程同时进入方法进行业务操作,并同时结束。 接下来我们看看在方法上加synchronized关键字的效果:
每个线程都需要去获取锁才能进入方法体内,如果锁被其他线程占有,则需要等它释放锁后才能去争抢锁。这也就体现了排他性。
这里还有一个对象锁和类锁的概念,对象锁是指synchronized用于实例方法上,类锁是用于类的静态方法上。一个类可以有多个对象实例,每个对象实例都是一个对象锁,他们互不干扰。类可以有多个对象锁,但是只有一个类锁,因为类锁的实质是指每个类对应的class对象。类锁和对象锁之间也是互不干扰的。
2.volatile
volatile可以保证线程之间对变量的操作后的可见性(这里需要了解下多线程的内存模型,只做简单的介绍,后续作内存模型专门讲解时在详细介绍)
当线程A对变量a进行操作后,并且更新到主存中,而这时候线程b并没有及时从主存中更新a到本地内存中,那么就会使用旧值而导致出错。而如果使用volatile关键字修饰变量a,那么线程A修改变量a并更新到主存中,线程b会去主存中获取变量a的最新值。这也就是volatile关键字保证内存可见性,他比synchronized关键字要更加轻量级,但是它不能保证原子性。比较适合多个线程读,一个线程写的场景。另外volatile还还有一个特性禁止指令的重排序。
接下来用代码演示下volatile的可见性和不能保证原子性。
public class VolatileTest {
private static boolean flag;
// private volatile static boolean flag;
private static int num;
public static class Thread1 extends Thread{
@Override
public void run() {
System.out.println("start!!!!!!!!!!!");
while (!flag);
System.out.println("num="+num);
}
}
public static void main(String[] args) {
new thread1().start();
Thread.sleep(1000);
flag = true;
num = 199;
Thread.sleep(3000);
System.out.println("main end*******************");
}
}
我们先来看看当一个线程改变不带volatile关键字变量的值,另一个线程是否能获取到新值?
可以发现在不使用volatile关键字修饰时,无法即时获得到新的flag值,下面我们用volatile关键字修饰flag变量,看看结果如何。
在添加了volatile关键字后,可以立即获得到最新的值,这就体现了volatile的可见性。那volatile是否能保证原子性呢?接下来我们也一起验证一下。
可以看的出来这时没有对变量添加volatile关键字修饰,而是使用了synchronized锁,结果是正确的。那么,如果我们使用volatile关键字不用synchronized关键字呢?
可以看到volatile关键字修饰的时候并不能得到正确结果,也就证明了volatile关键字无法保证操作的原子性。volatile还有禁止指令重排序的功能。
本节主要对synchronized和volatile进行了讲解。下一节主要会讲解ThreadLocal的相关知识。
以上就是本节内容,谢谢大家的阅读,如有错漏,欢迎评论区指正提出😚!