多线程的常用关键字及示例代码(synchronizedvolatile)

Posted 零

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程的常用关键字及示例代码(synchronizedvolatile)相关的知识,希望对你有一定的参考价值。

1.synchronized

synchornized可以修饰方法跟代码块,核心本质是锁对象,如果是static修饰的,也可以锁类变量

注意:理解Java对象在内存中的组成:对象头,实际数据,对齐填充,重点理解对象头中的 markword

线程拥有锁,锁的是对象,对象的头信息中,指向的指针是线程

偏向锁,自旋锁,轻量锁,重量锁
1.偏向锁:当程序没有竞争,取消同步操作,发生竞争时,升级为轻量锁
2.轻量锁:(对象的头信息markword中存放了锁的状态和线程持有的索)如果当前线程用cas将markword锁记录(Lock Record)指针替换成功,获取轻量锁,如果失败尝试自旋,成功获取轻量锁,失败膨胀为重量锁
3.自旋锁:默认10次,JDK1.8变成自适应自旋,会根据上一个线程自旋的时间的状态来自动增减对该锁的自旋次数,(相当于循环获取锁)
4.重量锁:竞争失败后,阻塞

修饰方法:

  • 通过ACC_SYNCHORDIZED标记来实现同步,方法级的同步时隐式的
  • 同步方法在常量池中会有一个ACC_SYNCHORDIZED的标识,当线程进入某个方法的时候,会判断有没有这个标识,如果有会获得一个监视器锁(monitorenter),运行结束释放锁,有其他线程在这个过程中访问该方法会被阻塞,方法出异常时,这个锁会被释放

修饰代码块:

  • 每个对象都维护着一个被锁次数的计数器,未被锁的对象计数器为0,当线程获取锁(执行monitorenter后),计数器自增1,当同一线程再次获取该对象的锁,计数器继续自增,当同一对象释放锁(执行monitorexit)的时候,计数器自减1,当计数器为0的时候,其他线程可以获取锁

示例代码

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadDemo04 implements Runnable 
    private int num = 1000;
    private int count = 0;

    @Override
    public void run() 
            Thread thread = Thread.currentThread();
            while (num > 0) 
                synchronized(this) 
                    if (num == 0)
                        return;
                    
                    count++;
                    num--;
                    System.out.println(thread.getName() + "抢到了第" + count + "张票" + ",剩余" + num + "张票");
                
            

    

    public static void main(String[] args) throws InterruptedException 
        ThreadDemo04 threadDemo04 = new ThreadDemo04();
        Thread thread1 = new Thread(threadDemo04,"张三");
        Thread thread2 = new Thread(threadDemo04,"李四");
        Thread thread3 = new Thread(threadDemo04,"王五");
        thread1.start();
        thread2.start();
        thread3.start();
    

2.volatile

volatile关键字的作用是:不能保证原子性,但可以保证内存可见性,基本的核心思想就是,当我们修改完成后,将值强制写入主内存,使变量在多个线程间可见(具有可见性)

示例代码

public class VolatileThread extends Thread 
    boolean flag = false;
    int num = 0;

    @Override
    public void run() 
        while (!flag)
            num++;
        
    

    public static void main(String[] args) throws InterruptedException 
        VolatileThread volatileThread = new VolatileThread();
        volatileThread.start();
        Thread.sleep(1000);
        volatileThread.flag = true;
        while (true)
            System.out.println(volatileThread.num);
            Thread.sleep(1000);
        
    


可以看到,在此代码中,每隔一秒打印一次i的值,发现i值不断在增长,主线程中将vt.flag设置为true,但是没有影响到子线程中的flag。我们把flag变量加上volatile修饰后,i值将不再增长,证明子线程每次读取flag值时间会从主内存区同步。

如果我们不加上volatile修饰,而是的子线程循环中加入一条打印语句,可以发现运行一段时间后,子线程的flag值也一样被同步了。

**JVM会尽力保证内存的可见性,即便这个变量没有加同步关键字。**换句话说,只要CPU有时间,JVM会尽力去保证变量值的更新。这种与volatile关键字的不同在于,volatile关键字会强制的保证线程的可见性。而不加这个关键字,JVM也会尽力去保证可见性,但是如果CPU一直有其他的事情在处理,它也没办法。最开始的代码,一直处于死循环中,CPU处于一直被饱受占用的时候,这个时候CPU没有时间,JVM也不能强制要求CPU分点时间去取最新的变量值。而加了System.out.println之后,由于屏幕输出其实是比较耗时的,这个时候CPU就有可能有时间去保证内存的可见性

以上是关于多线程的常用关键字及示例代码(synchronizedvolatile)的主要内容,如果未能解决你的问题,请参考以下文章

Java中多线程编程--synchronized关键字

Java 多线程并发编程之 Synchronized 关键字

Java_多线程实现同步

java入门索引

java中volatile关键字的含义

java中volatile关键字的含义