关于synchronized锁String

今天遇到的面试官提问了相关的问题:
使用synchronized关键字锁String参数对象的情况下,能不能保证线程安全

当时的想法(脑子已浆糊):

  • String类由final修饰,不可变

  • Java只有值传递,只要比较String对象引用的内存地址是否一致

测试代码

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * MySynchronizedTest
 *
 * @author <b>孤寂灬无痕</b>
 * <p>
 * 994300880@qq.com
 * @version 1.0
 * @date 2024/4/16 12:10
 */
public class MySynchronizedTest {
    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(4, 8, 5L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), new ThreadFactory() {
            private final AtomicInteger id = new AtomicInteger(1);

            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setName("myThreadPool - " + id.getAndIncrement());
                return thread;
            }
        });
        for (int i = 1; i < 5; i++) {
            threadPool.execute(new MyTestThread3("任务 - " + i));
        }
        //阻塞主线程
        System.out.println("主线程阻塞...");
        Thread.sleep(5000L);
        //关闭线程池
        threadPool.shutdown();
        // 等待所有任务执行完毕
        while(!threadPool.awaitTermination(2L, TimeUnit.SECONDS)){
            System.out.println("线程池未关闭!");
        }
        System.out.println("主线程结束!");
    }
}

//第一种情况
record MyTestThread(String taskName) implements Runnable {
    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        synchronized (name) {
            System.out.println(name + " 开始执行任务!");
            try {
                //睡1秒
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(name + " 结束任务!");
        }
    }
}

//第二种情况
record MyTestThread2(String taskName) implements Runnable {
    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        String s = "a";
        synchronized (s) {
            System.out.println(name + " 开始执行任务!");
            try {
                //睡1秒
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(name + " 结束任务!");
        }
    }
}

//第三种情况
record MyTestThread3(String taskName) implements Runnable {
    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        String s = new String("a");
        synchronized (s) {
            System.out.println(name + " 开始执行任务!");
            try {
                //睡1秒
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(name + " 结束任务!");
        }
    }
}

第一种:值不一样

主线程阻塞...
myThreadPool - 1 开始执行任务!
myThreadPool - 2 开始执行任务!
myThreadPool - 3 开始执行任务!
myThreadPool - 4 开始执行任务!
myThreadPool - 3 结束任务!
myThreadPool - 2 结束任务!
myThreadPool - 4 结束任务!
myThreadPool - 1 结束任务!
主线程结束!

第二种:值一样,内存地址一样

主线程阻塞...
myThreadPool - 1 开始执行任务!
myThreadPool - 1 结束任务!
myThreadPool - 4 开始执行任务!
myThreadPool - 4 结束任务!
myThreadPool - 3 开始执行任务!
myThreadPool - 3 结束任务!
myThreadPool - 2 开始执行任务!
myThreadPool - 2 结束任务!
主线程结束!

第三种:值一样,内存地址不一样

主线程阻塞...
myThreadPool - 1 开始执行任务!
myThreadPool - 4 开始执行任务!
myThreadPool - 3 开始执行任务!
myThreadPool - 2 开始执行任务!
myThreadPool - 4 结束任务!
myThreadPool - 1 结束任务!
myThreadPool - 3 结束任务!
myThreadPool - 2 结束任务!
主线程结束!

总结

  • String类由final修饰,不可变

  • Java只有值传递,只要比较String对象引用的内存地址是否一致

  • 通过内存地址判断是不是同一个对象

  • 涉及字符串常量池和String.intern()方法