• Java多线程-线程关键字(二)


    Java中和线程相关的关键字就两:volatilesynchronized

    volatile以前用得较少,以后会用得更少(后面解释)。它是一种非常轻量级的同步机制,它的三大特性是:

    1、保证可见性,即强制将CPU高速缓存的数据立即写入主存,会导致其他CPU核中对应的高速缓存内容无效,就像这样:

     

    如果由于同步需要,某行代码使用了volatile关键字,那么当CPU内核1收到指令时,会立即将它位于高速缓存中的数据(这里是字符串“1”)写到主存中去,那么其余的数据(“哈哈”、“test”、9.9)会全部失效。

    2、有序性(禁止指令重排)

    所谓指令重排,就是对于int a = 0、 int b = 1这类赋值语句,编译成class字节码时,a = 0,b = 1的顺序可能会因为编译器的优化而导致和书写时的顺序不一致。但如果有c = a + b,那么这行代码必须在前两句的后面执行。这个不用深究,知道就好了。

    3、不保证原子性

    所谓不保证原子性,就是如果遇到多个线程同时执行i = i + 1,那么i可能就不能保证仅仅被加1了。

    根据Java的内存模型整理出如下这些规则,看看就好,能理解就记住,理解不了也没关系,不用记:

     

     

    下面还是以代码来说明:

    复制代码
    public class VolatileTest extends Thread {
        static boolean flag = true;
    
        @Override
        public void run() {
            while (flag) {
            }
            System.out.println("子线程结束");
        }
    
        public static void main(String[] args) throws InterruptedException {
            new Thread(new VolatileTest()).start();
            TimeUnit.SECONDS.sleep(1);
            flag = false;
            System.out.println("主线程结束");
        }
    }
    复制代码

    加上volatile之后,子线程就不会一直卡住了:

    复制代码
    public class VolatileTest2 extends Thread {
        volatile static boolean flag = true;
    
        @Override
        public void run() {
            while (flag) {
            }
            System.out.println("子线程结束");
        }
    
        public static void main(String[] args) throws InterruptedException {
            new Thread(new VolatileTest2()).start();
            TimeUnit.SECONDS.sleep(1);
            flag = false;
            System.out.println("主线程结束");
        }
    }
    复制代码

    其实在第一个类VolatileTest中,即使不用volatile关键字,也能让程序正常结束,只需要添加一行代码就行了:

    复制代码
    public class VolatileTest3 extends Thread {
        static boolean flag = true;
    
        @Override
        public void run() {
            while (flag) {
                // 只要在这里随便加一行代码,虽然原理不同,但效果和加了volatile一样
                System.out.println(" ");
            }
            System.out.println("子线程结束");
        }
    
        public static void main(String[] args) throws InterruptedException {
            new Thread(new VolatileTest3()).start();
            TimeUnit.SECONDS.sleep(1);
            flag = false;
            System.out.println("主线程结束");
        }
    }
    复制代码

    总结一下,适应volatile的条件:

    1、对变量的写操作不依赖于当前值,就是不会出现 x++ 或 x = x + y 这样的自增性质的语句;

    2、变量没有包含在具有其他变量的代码中,就是不会出现 if(x > y) { // dosomething; } 这样的语句,因为这样的话,dosomething就不是线程安全的。

    所以适合使用volatile的场景是:

    1、状态标记量

    2、双重检查

    状态标记量:只设置简单的状态值,希望立即看到,且无其他变量参与。之前Volatile2中的flag就是这样的状态标记量。

    双重检查(这个有点生僻冷门,不深究)。

    因为目前synchronized的性能已经有了大幅提升,而且机器硬件的性能也较之前翻了几十倍,volatile存在的意义已经不大了。所以如果对volatile的理解不够深入,就干脆不用理解了。


     

    再来看另一个重量级选手:synchronized

     

    synchronized关键字用于解决多个线程之间的资源共享问题,通常有三大作用域:

     

    1、静态方法:public synchronized static void methodName(),进入方法前要获得当前类对象的锁;

     

    2、实例方法:public synchronized void methodName(),进入方法前要获得当前对象实例的锁;

     

    3、代码块:最常用,指定一个特别的加锁对象,进入同步代码块前要获得这个对象锁。

     

    这几个就不一个个地说了,因为网上这类例子太多了。

     

    就简单说一下volatile和synchronized的比较:

     

    1、volatile是synchronized的轻量级实现,性能要比synchronized要好;

     

    2、volatile只能修饰变量,synchronized只能修饰方法和代码块;

     

    3、volatile不会造成阻塞,synchronized会。

     

    最后再说一个我之前遇到的小问题:

     

    如果使用的IDE是Eclipse,那么当前活动的线程数量可能是1;

     

    但如果使用的IDE是IDEA,那么当前活动的线程数量可能是2;

     

    有图为证:

     

     

     这是因为在IDEA中多了一个Monitor Ctrl-Break。

     

     

  • 相关阅读:
    【云原生之Docker实战】使用docker部署Memos碎片化知识管理工具
    如何使用国际腾讯云服务器进行外网访问?
    【云原生】使用Dockerfile制作openGauss镜像
    Windows 下 Kafka 2.8.1 启动报错“输入行太长”问题解决方案
    A预测蛋白质结构
    【dart】动态获取类实例(model)的属性
    npm 彻底卸载
    初始Tomcat(Tomcat的基础介绍)
    Zookeeper-JavaApI操作
    数据分享|R语言逻辑回归、线性判别分析LDA、GAM、MARS、KNN、QDA、决策树、随机森林、SVM分类葡萄酒交叉验证ROC...
  • 原文地址:https://www.cnblogs.com/sun-10387834/p/16847641.html