并发编程(Synchronized/volatile/atomic)

本文深入探讨了并发编程中的核心概念,如线程安全、synchronized关键字的使用、锁的管理和volatile关键字的应用等,通过实例解释了如何有效避免并发问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

并发编程

线程安全

  当多个线程访问某个类是,这个类始终能表现出正常的行为,那么这个类(对象或方法)就是线程安全的。

synchronized

可以在任意个对象及方法上加锁,而加锁的这段代码称为互斥区或临界区。

  关键字synchronized取得的锁都是对象锁,而不是把一段代码当作锁,所以示例代码中的那个线程先执行synchronized关键字的方法,那个线程就持有该方法所属对象的锁(Lock),两个对象,线程获得的就是两个不同的锁,他们互不影响。

  有一种情况则是相同的锁,即在静态方法上加synchronized的关键字,表示锁定.class类,类一级别的锁(独占.class类)。

/**
 * 多个线程单个锁
 * @author Carl_Hugo
 *
 */
public class MyThread extends Thread{

    private int count=5;

    @Override
    public synchronized void run() {
        count--;
        System.out.println(this.currentThread().getName()+" count="+count);
    }

    public static void main(String[] args) {
        Thread myThread = new MyThread();
        Thread t1 = new Thread(myThread,"t1");
        Thread t2 = new Thread(myThread,"t2");
        Thread t3 = new Thread(myThread,"t3");
        Thread t4 = new Thread(myThread,"t4");
        Thread t5 = new Thread(myThread,"t5");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }

}

一个对象一个锁

/**
 * 一个对象一个锁 
 * @author Carl_Hugo
 */
public class MyObject {

    public synchronized void method1(){
        try{
            System.out.println(Thread.currentThread().getName());
            Thread.sleep(4000);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
    }

    public synchronized void method2(){
        System.out.println(Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        final MyObject myObject = new MyObject();

        Thread t1=new Thread(new Runnable(){
            @Override
            public void run() {
                myObject.method1();
            }

        },"t1");

        Thread t2=new Thread(new Runnable(){
            @Override
            public void run() {
                myObject.method2();
            }

        },"t2");
        t1.start();
        t2.start();
    }
}

static修饰Synchronized

/**
 * 一个对象一把锁
 * 在方法上加static表示在静态的方法上加synchronized修饰表示多个对象共用一把锁
 * @author Carl_Hugo
 *
 */
public class MultiThread {

    private static int num =0;
    public static synchronized void printNum(String tag){
//  public synchronized void printNum(String tag){
        try{
            if("a".equals(tag)){
                num = 100;
                System.out.println("tag a,set num over!");
                Thread.sleep(1000);
            }else{
                num = 200;
                System.out.println("tag b,set num over!");
            }
            System.out.println("tag "+tag+",num = "+num);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        //两个不同的对象
        final MultiThread m1 = new MultiThread();
        final MultiThread m2 = new MultiThread();

        Thread t1 = new Thread(new Runnable(){
            @Override
            public void run() {
                m1.printNum("a");
            }
        });

        Thread t2 = new Thread(new Runnable(){
            @Override
            public void run() {
                m2.printNum("b");
            }
        });
        t1.start();
        t2.start();
    }
}

加Synchronized避免脏读的出现

package zx.test4;

public class DirtyRead {

    private String username = "Tom";
    private String password = "123456";

    public synchronized void setValue(String username,String password){
        this.username=username;

        try{
            Thread.sleep(2000);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        this.password=password;

        System.out.println("setValue的最终结果:username = "+username+",password = "+password);
    }

    public synchronized void getValue(){
        System.out.println("getValue方法得到: username = "+this.username+" ,password = "+this.password);
    }

    public static void main(String[] args) throws InterruptedException {
        final DirtyRead dr = new DirtyRead();
        Thread t1=new Thread(new Runnable(){
            @Override
            public void run() {
                dr.setValue("z3", "456");
            }
        });
        t1.start();
        Thread.sleep(1000);
        dr.getValue();
    }
}
synchronized锁重入

  关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象的锁后,再次请求此对象时可以再次得到该对象的锁。

/**
 * synchronized的重入
 * @author Carl_Hugo
 */
public class SyncDubbo1 {

    public synchronized void method1(){
        System.out.println("method1...");
        method2();
    }

    public synchronized void method2(){
        System.out.println("method2...");
        method3();
    }

    public synchronized void method3(){
        System.out.println("method3...");
    }

    public static void main(String[] args){
        final SyncDubbo1 sd1 = new SyncDubbo1();
        Thread t1 = new Thread(new Runnable(){
            @Override
            public void run() {
                sd1.method1();
            }
        });
        t1.start();
    }

}
/**
 * 有父类和子类继承关系的情况下,方法修饰为synchronized的没有线程安全问题 
 * @author Carl_Hugo
 */
public class SyncDubbo2 {

    static class Main{
        public int i = 10;
        public synchronized void operationSup(){
            try{
                i--;
                System.out.println("Main print i="+i);
                Thread.sleep(100);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }   
    }

    static class Sub extends Main{
        public synchronized void operationSub(){
            try{
                while(i>0){
                    i--;
                    System.out.println("Sub print i="+i);
                    Thread.sleep(100);
                    this.operationSup();
                }
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable(){
            @Override
            public void run() {
                Sub sub = new Sub();
                sub.operationSub();
            }
        });
        t1.start();
    }
}
synchronized异常释放锁

  当发生异常时可以选择继续执行或者终止执行。

  synchronized可以使用任意的Object进行加锁,用法比较灵活。
另外特别注意一个问题,不要使用String常量加锁,会出现死循环问题。
锁对象的改变问题,当使用一个对象进行加锁的时候,要注意对象本身发生改变的时候,那么持有的锁就不同。如果对象本身不发生改变,那么依旧是同步的,即使是对象的属性发生了改变。

/**
 * 同步synchronized的异常处理
 * 适用于处理多个任务,要求同时成功或者同时失败
 * @author Carl_Hugo
 *
 */
public class SyncException {

    private int i=0;
    public synchronized void operation(){
        while(true){
            try{
                i++;
                Thread.sleep(200);
                System.out.println(Thread.currentThread().getName()+" ,i="+i);
                if(i==10){
                    Integer.parseInt("a");
                }
            }catch(Exception e){
                e.printStackTrace();
                System.out.println(" log info i = "+i);
                throw new RuntimeException();
            }
        }
    }

    public static void main(String[] args){
        final SyncException se=new SyncException();
        Thread t1 =new Thread(new Runnable(){
            @Override
            public void run() {
                se.operation();
            }
        },"t1");
        t1.start();
    }
}

多个线程多个锁:多个线程,每个线程都可以拿到自己指定的锁,分别获得锁之后,执行synchronized方法体的内容。

public class ObjectLock {

    public void method1(){
        //对象锁
        synchronized (this) {
            try{
                System.out.println("do method1..");
                Thread.sleep(2000);
            }catch(InterruptedException e){
                e.printStackTrace();
            }   
        }
    }

    public void method2(){
        //类锁
        synchronized (ObjectLock.class) {
            try{
                System.out.println("do method2..");
                Thread.sleep(2000);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
    }

    private Object lock = new Object();
    public void method3(){
        //任何对象锁
        synchronized (lock) {
            try{
                System.out.println("do method3..");
                Thread.sleep(2000);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        final ObjectLock objectLock = new ObjectLock();
        Thread t1 = new Thread(new Runnable(){
            @Override
            public void run() {
                objectLock.method1();               
            }
        });

        Thread t2 = new Thread(new Runnable(){
            @Override
            public void run() {
                objectLock.method2();
            }
        });

        Thread t3 = new Thread(new Runnable(){
            @Override
            public void run() {
                objectLock.method3();
            }
        });

        t1.start();
        t2.start();
        t3.start();
    }
}

锁发生改变

/**
 * 锁发生修改
 * @author Carl_Hugo
 */
public class ChangeLock {

    private String lock = "lock";

    private void method(){
        synchronized (lock) {
            try{
                System.out.println("当前线程:"+Thread.currentThread().getName()+"开始");
                lock="change lock";
                Thread.sleep(2000);
                System.out.println("当前线程:"+Thread.currentThread().getName()+"结束");
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        final ChangeLock changelock = new ChangeLock();
        Thread t1 = new Thread(new Runnable(){
            @Override
            public void run() {
                changelock.method();
            }
        },"t1");
        Thread t2 = new Thread(new Runnable(){
            @Override
            public void run() {
                changelock.method();
            }
        },"t2");
        t1.start();
        try{
            Thread.sleep(100);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        t2.start();
    }

}

对象中属性的变化不会影响锁的变化

/**
 * 对象中属性的变化不会影响锁的变化
 * @author Carl_Hugo
 */
public class ModifyLock {

    private int age;
    private String name;
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    public synchronized void changAttribute(String name,int age){
        try{
            System.out.println("当前线程:"+Thread.currentThread().getName()+" 开始");
            this.setAge(age);
            this.setName(name);
            System.out.println("当前线程:"+Thread.currentThread().getName()+"修改对象内容为:"
                    +this.getName()+", "+this.getAge());
            Thread.sleep(2000);
            System.out.println("当前线程:"+Thread.currentThread().getName()+" 结束");
        }catch(InterruptedException e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        final ModifyLock modifyLock = new ModifyLock();
        Thread t1 = new Thread(new Runnable(){
            @Override
            public void run() {
                modifyLock.changAttribute("张三", 20);
            }
        },"t1");
        Thread t2 = new Thread(new Runnable(){
            @Override
            public void run() {
                modifyLock.changAttribute("李四", 21);
            }
        },"t2");
        t1.start();

        try{
            Thread.sleep(100);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        t2.start();
    }

}

使用字符串常量加锁会导致死循环问题,即使对象执行完后也不会释放锁

/**
 * 使用字符串常量加锁会导致死循环问题 
 * @author Carl_Hugo
 */
public class StringLock {

    public void method(){
        synchronized ("字符串常量") {
            try{
                while(true){
                    System.out.println("当前线程:"+Thread.currentThread().getName()+"开始");
                    Thread.sleep(1000);
                    System.out.println("当前线程:"+Thread.currentThread().getName()+"结束");
                } 
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        final StringLock stringlock = new StringLock();
        Thread t1 = new Thread(new Runnable(){
            @Override
            public void run() {
                stringlock.method();
            }
        },"t1");

        Thread t2 = new Thread(new Runnable(){
            @Override
            public void run() {
                stringlock.method();
            }
        },"t2");
        t1.start();
        t2.start();
    }

}

volatile关键字

主要作用是使变量在多个线程可见。

在java中每个线程都会有一块工作内存区,其中存放着所有线程共享的主内存的变量值的拷贝。当线程执行时,他会在自己的工作内存区中操作这些变量。为了存取一个共享的变量,一个线程通常先获取锁并清除它的内存工作区,把这些共享变量从所有的线程的共享内存区中正确的装入他自己所在的工作内存区中,当线程解锁时保证该工作内存区中的变量的值写回到共享内存中。

一个线程可执行的操作有使用(use)、赋值(assign)、装载(load)、存储(store)、锁定(lock)、解锁(unlock)。
而主内存执行的操作有读(read)、写(write)、锁定(lock)、解锁(unlock),每个操作都是原子性的。

volatile的作用:强制线程到主内存(共享内存)里去读变量,而不去线程工作内存区里去读取,从而实现多个线程间的变量可见。也就是满足线程安全的可见性。

volatile关键字虽然拥有多个线程之间的可见性,但是却不具备同步性(也就是原子性),可以算是一个轻量级的synchronized,性能要比synchronized强很多,不会造成阻塞(在很多开源框架中,比如netty的底层代码就大量使用volatile,可见netty的性能是不错的。)这里需要注意,一般volatile用于只针对多个线程可见的变量操作,并不能代替synchronized的同步功能。

volatile关键字值具有可见性,没有原子性。要实现原子性建议使用atomic类系列的对象,支持原子性操作(注意:atomic类只保证本身方法的原子性,并不能保证多次操作的原子性)

public class RunThread extends Thread{

    //volatile
    private volatile boolean isRunning = true;
    private void setRunning(boolean isRunning){
        this.isRunning=isRunning;
    }

    @Override
    public void run() {
        System.out.println("进入run方法。。");
        while(isRunning==true){

        }
        System.out.println("线程停止");
    }

    public static void main(String[] args) throws InterruptedException {
        RunThread rt = new RunThread();
        rt.start();
        Thread.sleep(3000);
        rt.setRunning(false);
        System.out.println("isRunning的值已经被设置了false");
        Thread.sleep(1000);
        System.out.println(rt.isRunning);
    }

}

原子操作:在多进程(线程)访问共享资源时,能够确保所有其他的进程(线程)都不在同一时间内访问相同的资源。原子操作(atomic operation)是不需要synchronized,这是Java多线程编程的老生常谈了。所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。

public class AtomicUse {

    private static AtomicInteger count = new AtomicInteger(0);
    //多个addAndGet在一个方法中是非原子性的,需要加synchronized进行修饰,保证4个addAndGet的原子性
    private synchronized int multiAdd(){
        try{ 
            Thread.sleep(100);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        count.addAndGet(1);
        count.addAndGet(2);
        count.addAndGet(3);
        count.addAndGet(4);
        return count.get();
    }

    public static void main(String[] args) {
        final AtomicUse au = new AtomicUse();

        List<Thread> ts = new ArrayList<Thread>();
        for(int i=0;i<100;i++){
            ts.add(new Thread(new Runnable(){
                @Override
                public void run() {
                    System.out.println(au.multiAdd());
                }
            }));
        }
        for(Thread t:ts){
            t.start();
        }
    }

}

使用原子性操作实现自增

public class VolatileNoAtomic extends Thread{

//  private static volatile int count;
    private static AtomicInteger count = new AtomicInteger(0);
    private static void addCount(){
        for(int i=0;i<1000;i++){
//          count++;
            count.incrementAndGet();
        }   
        System.out.println(count);
    }

    public void run(){
        addCount();
    }

    public static void main(String[] args) {
        VolatileNoAtomic[] arr = new VolatileNoAtomic[10];
        for(int i=0;i<10;i++){
            arr[i] = new VolatileNoAtomic();
        }
        for(int i=0;i<10;i++){
            arr[i].start();
        }
    }
}

可见性和原子性的区别
volatile变量只保证了可见性,不保证原子性, 比如a++这种操作在编译后实际是多条语句,比如先读a的值,再加1操作,再写操作,执行了3个原子操作,如果并发情况下,另外一个线程很有可能读到了中间状态,从而导致程序语意上的不正确。所以a++实际是一个复合操作。
加锁可以保证复合语句的原子性,sychronized可以保证多条语句在synchronized块中语意上是原子的。
显式锁保证临界区的原子性。
原子变量也封装了对变量的原子操作。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值