1. 进程与线程

1.1 进程

是程序的一次执行过程, 使系统运行程序的基本单位。运行一个程序就是创建一个线程, 运行到消亡的过程。

1.2 线程

线程是比进程更小的执行单位,也被成为轻量级的进程, 一个进程可以执行多个线程.然后多个线程共享进程的堆和方法区资源。每个线程都有自己的程序计数器,虚拟机栈, 本地方法栈。

  1. // 就是查看java中有哪些线程...
  2. public class ViewThreads {
  3. public static void main(String[] args) {
  4. ViewThreadTest viewThreadTest = new ViewThreadTest();
  5. viewThreadTest.start();
  6. // 获取java线程管理的MXBean
  7. ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
  8. // 不需要获取同步的monitor和synchronized消息, 仅获取线程和堆栈信息
  9. ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
  10. /**
  11. * 遍历线程信息,仅打印线程 ID 和线程名称信息
  12. */
  13. for (ThreadInfo threadInfo : threadInfos) {
  14. System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.getThreadName());
  15. }
  16. }
  17. }
  18. class ViewThreadTest extends Thread{
  19. @Override
  20. public void run() {
  21. try {
  22. Thread.sleep(3000);
  23. System.out.println("休眠完成了...");
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. }
  28. }
  29. 打印结果如下:
  30. [10] Thread-0
  31. [5] Monitor Ctrl-Break
  32. [4] Signal Dispatcher
  33. [3] Finalizer
  34. [2] Reference Handler
  35. [1] main

1.2.1 JVM角度图解进程和线程的关系

image.png

具体理解java内存模型详见:https://www.yuque.com/wangyanyang/qz7fwa/qs5van

1.3 线程启动的三种方式:

@Test
public void threadTest() throws InterruptedException, ExecutionException {
    // 方式1
    MyThread myThread = new MyThread();
    myThread.start();

    //方式2
    MyRunnable myRunnable = new MyRunnable();
    new Thread(myRunnable).start();

    // 方式3
    MyCallable myCallable = new MyCallable();
    //使用 FutureTask 类来包装 Callable 对象, 该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值
    FutureTask<String> futureTask = new FutureTask<>(myCallable);
    //使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程
    /**
         * 如果没有下行代码, 那么程序会一直等待执行, 原因是如果没有启动线程, 程序会执行
         * UNSAFE.park(false, 0L); 这个本地方法, 实现线程的阻塞。
         */
    new Thread(futureTask).start();
    System.out.println(futureTask.get());
}

class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println("MyCallable run...");
        return "Hello World";
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Mythread run...");
    }
}

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("MyRunnable run...");
    }
}

1.4 线程的中断机制:

1.4.1 解释:

中断机制的含义和中断不太一样, 中断机制就是某一线程接收到了中断指令, 但是不会马上进行中断, 而是在恰当是时候进行中断。在多线程场景中, 有很多线程无法结束任务(自旋). 此时就需要另外一个线程在恰当的时候给一个中断通知, 然后在恰当的时候进行中断。

1.4.2 中断方式:

interrupt(): 每一个线程都有一个中断标志, 该方法就是讲该标志设置为true
isInterrupted() 返回调用线程的中断标志位。
interrupted() 是一个Thread类的静态方法, 也是返回线程的中断状态, 但是有两点要注意, 第一点, 该方法返回的时候当前线程的状态。第二点该方法在调用结束后会清除中断标志位。
上面两个方法调用的都是底层的同一个native方法, 仅仅是入参不一样.image.png

public class EnableThread {
    private static class NewThread extends Thread {
        public NewThread(String name) {
            super(name);
        }

        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            while (!this.isInterrupted()) {
                System.out.println(threadName + "is run");
            }
            System.out.println(threadName + "interrput flag is " + isInterrupted() + " and now date ->" + System.currentTimeMillis());
        }
    }

    /**
     * 中断机制要想很好的发挥功效, 就需要不同线程间相关协调, 某一个线程发起中断, 另外一个线程要去监听中断信息。
     */
    public static void main(String[] args) throws Exception {
        NewThread newThread = new NewThread("endThread");
        newThread.start();
        Thread.sleep(20);
        newThread.interrupt();
        System.out.println("now date ->" + System.currentTimeMillis());
    }
}
------------------------------------------------------------------
public class EnableRunnable {
    private static class NewRunnable implements Runnable {
        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println(threadName + " is run");
            }
            System.out.println(threadName + "interrput flag is " + Thread.currentThread().isInterrupted() + " and now date -->" + System.currentTimeMillis());
        }
    }

    /**
     * 中断机制要想很好的发挥功效, 就需要不同线程间相关协调, 某一个线程发起中断, 另外一个线程要去监听中断信息。
     */
    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(new NewRunnable(), "NewRunnable");
        thread.start();
        Thread.sleep(20);
        thread.interrupt();
        System.out.println("now date -->" + System.currentTimeMillis());
    }
}
----------------------------------------------------------------
public class HasInterruptedException {
    private static class NewThread extends Thread {
        public NewThread(String name) {
            super(name);
        }

        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            while (!this.isInterrupted()) {
                try {
                    System.out.println("now interrupt flag is " + isInterrupted());
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    /**
                     * sleep 时候抛出异常原因: 当前线程正在sleep(暂停执行), 有线程去中断它, 它就会抛异常!!!
                     * 在抛出异常的同时, 还会将中断标志初始化。所以要想结束循环, 需要再次去中断线程。
                     */
                    System.out.println("warn -> " + threadName + " interrput flag is " + isInterrupted() + " and now date ->" + System.currentTimeMillis());
                    interrupt();
                    e.printStackTrace();
                }
            }
            System.out.println(threadName + " interrput flag is " + isInterrupted() + " and now date ->" + System.currentTimeMillis());
        }
    }

    public static void main(String[] args) throws Exception {
        NewThread newThread = new NewThread("HasInterruptedException");
        newThread.start();
        Thread.sleep(1000);
        newThread.interrupt();
        System.out.println("now date ->" + System.currentTimeMillis());
    }
}

1.5 线程的生命周期和状态

1.5.1 线程状态解释

状态名称 说明
new 初始状态, 线程被创建,还没有调start方法。
runnable 运行状态, java线程将操作系统中就绪(ready)和运行状态(runnable)统称为”运行中”。
线程创建并执行start方法, 线程处于ready状态,获得cpu时间片,处于runnable状态。
blocked 阻塞状态, 在没有获得锁情况下, 线程进入blocked状态。
waiting 等待状态, 线程执行wait方法, 线程进入waiting状态.进入等待状态的进程需要别的线程的通知才能返回运行状态。
没有设置timeout参数的Object.wait()方法,
没有设置timeout参数的Thread.join()方法,
LockSupport.park()方法 会进入等待状态。
time_waiting 超时等待状态, 不同于等待状态, 它可以在指定时间内自行返回。线程执行sleep(long)和wait(long)方法可以将线程置于超时等待状态。
terminated 终止状态,线程在执行run方法之后会进入终止状态

image.png

1.5.2 补充说明

1.5.2.1 sleep和wait方法的区别:

两个都可以暂停线程, wait方法被调用后, 会释放锁并且让出cpu, 线程不会自动苏醒, 需要别的线程调用notify方法唤醒。sleep方法执行后, 仅仅是让出cpu,但不会释放锁, 等执行完成后, 线程会自动苏醒。
sleep是Thread类的方法, wait是Object类的方法。
sleep在任何地方可以使用, wait方法只可以在synchronized方法或者synchronized类中使用。因为wait方法释放锁, 只有上锁以后才可以释放, 随便释放锁会报错。

/**
 * 当前类校验的是sleep方法与wait方法最明显的区别:
 * 执行wait方法时候, 会让出cpu并且释放锁
 * 执行sleep方法, 仅仅会让出cpu, 不会释放锁
 */
public class WaitSleepTest {
    static String value = "hello";

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Thread A start...");
                synchronized (value) {
                    try {
                        System.out.println("Thread A get WaitSleepTest class...start");
                        /**
                         * 情况1: 线程A执行wait:
                         *  线程A会释放锁, 所以线程B会执行,但是线程A中(Thread A done没有打印),因为不会自动唤醒,会被阻塞。
                         */
                        //value.wait();
                        /**
                         * 情况2: 线程A执行wait(long)方法:
                         *  线程A会释放锁, 线程B会执行, 隔一秒以后线程A打印(Thread A done),wait(long)会自动唤醒。
                         */
                        value.wait(1000);
                        /**
                         * 情况3: 线程A执行sleep方法:
                         * 线程A线程内容全部打印完以后, 才会打印线程B相关的信息。
                         */
                        //Thread.sleep(5000);
                        System.out.println("Thread A done");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
        try {
            Thread.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Thread B start...");
                synchronized (value) {
                    System.out.println("Thread B get WaitSleepTest class...start");
                }
            }
        }).start();
    }
}

1.5.2.2 调用start方法而不是run方法:

调用start方法, 会启动一个线程并使得线程进入就绪状态, 当分配到时间片时候, start方法执行线程的相关准备, 然后去调run方法。而如果直接执行run方法, 只会把run方法当成main线程下的一个普通方法去执行。

1.5.2.3 守护线程

守护线程在主线程结束的时候, 也会结束。正常情况下,新起一个线程A,即使主线程结束了, A线程如果没结束, 程序也不会结束。但是如果我们将A线程设置为守护线程(newThread.setDaemon(true);). 那么A线程会随着主线程结束而结束的。

1.6 线程死锁,如何避免

多个线程同时被阻塞,他们一直在等待某个资源被释放,程序不能正常结束。典型例子, 线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。

public class deadThread {
    private static Object resource1 = new Object();
    private static Object resource2 = new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized(resource1) {
                System.out.println(Thread.currentThread() + "get resource1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resource2");
                synchronized (resource2) {
                    System.out.println(Thread.currentThread() + "get resource2");
                }
            }
        },"线程A").start();

        new Thread(() -> {
            synchronized(resource2) {
                System.out.println(Thread.currentThread() + "get resource2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resourc1");
                synchronized (resource1) {
                    System.out.println(Thread.currentThread() + "get resource1");
                }
            }
        },"线程B").start();
    }
}

1.6.1 死锁产生的条件:

互斥条件: 共享资源 X 和 Y 只能被一个线程占用。
占有且等待条件: 线程A已经获取了共享资源X, 在等待共享资源Y, 但是不释放共享资源X。
不剥夺条件: 其他线程不能强行抢占线程T1占有的资源.
循环等待条件: 线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占有的资源,就是循环等待。

1.6.2 怎样避免死锁:

使用银行转账作为例子来解决死锁问题:
线程一执行的是:【账户A】给【账户B】转账
线程二执行的是:【账户B】给【账户A】转账
如果两个转账动作同时执行,则会出现线程一会请求对【账户B】进行加锁,线程二会请求对【账户A】进行加锁。由于此时的【账户A】已由线程一进行锁定,【账户B】已由线程二进行锁定, 此时就会产生死锁问题。
解决上述问题, 我们采取了破坏占且等待条件, 来避免死锁: 就是同时获取多个资源X和Y,多个资源只由统一线程获取。这样就不会存在取到了X还要等待Y。避免了死锁的产生。

/**
 * 模拟解决银行转账死锁问题
 *
 * @author halo
 */
public class TransFerTest {
    /**
     * 账户详细信息
     */
    static private List<Account> testAccount = new ArrayList<>();
    /**
     * 账户数目
     */
    static final int testAccountNumber = 100;
    /**
     * 线程数
     */
    static final int testThreadNumber = 1000;
    /**
     * 线程池
     */
    static final ThreadPoolExecutor threadPool =
            new ThreadPoolExecutor(10, testThreadNumber,
                    1L, TimeUnit.SECONDS,
                    new LinkedBlockingQueue<>(),
                    new ThreadPoolExecutor.AbortPolicy());
    /**
     * aqs工具
     */
    static final CountDownLatch countDownLatch = new CountDownLatch(testThreadNumber);

    static public void test() {
        /**
         * 随机获取两个账户A和B A给B转账。转账余额也是随机的
         */
        testAccount.get(ThreadLocalRandom.current().nextInt(testAccountNumber))
                .transactionToTarget(ThreadLocalRandom.current().nextInt(100),
                        testAccount.get(ThreadLocalRandom.current().nextInt(testAccountNumber)));
        // 表示一个请求已经被完成
        countDownLatch.countDown();
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < testAccountNumber; i++) {
            testAccount.add(new Account(i));
        }

        for (int i = 0; i < testThreadNumber; i++) {
            threadPool.execute(() -> {
                test();
            });
        }
        // 方法具有阻塞作用,也就是说主线程在这里暂停
        countDownLatch.await();
        long totalNumber = 0;
        for (Account i : testAccount) {
            if (i.getBalance() < 0) {
                System.out.println("余额小于0: " + i.toString());
                exit(-1);
            }
            totalNumber += i.getBalance();
        }
        System.out.println("总余额: " + totalNumber);
        /**
         * 线程池使用完毕要关闭
         */
        threadPool.shutdown();
    }
}

class Allocator {
    /**
     * 单例的账号管理对象
     */
    private volatile static Allocator singleAllocator;

    /**
     * 有进行转账的账号都记录到这里。
     */
    private Set<Account> accountSet = new HashSet<>();


    /**
     * 双层锁校验, 确保获得到的锁对象是单例的。
     *
     * @return
     */
    public static Allocator getAllocator() {
        if (null == singleAllocator) {
            synchronized (Allocator.class) {
                if (null == singleAllocator) {
                    singleAllocator = new Allocator();
                }
            }
        }
        return singleAllocator;
    }

    /**
     * @param source 转出账户
     * @param target 转入账户
     */
    public synchronized void addLock(Account source, Account target) {
        while (accountSet.contains(source) || accountSet.contains(target)) {
            //如果转出账户 和 转入账户有一方已经得到锁资源, 那么另外一方不获取锁资源。
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        accountSet.add(source);
        accountSet.add(target);
    }

    public synchronized void releaseLock(Account source, Account target) {
        accountSet.remove(source);
        accountSet.remove(target);
        notifyAll();
    }
}

class Account {
    /**
     * 余额
     */
    private Integer balance;
    /**
     * 账户id
     */
    private Integer id;

    Account(Integer id) {
        this.id = id;
        balance = 100;
    }

    public Integer getBalance() {
        return balance;
    }

    public void setBalance(Integer balance) {
        this.balance = balance;
    }

    @Override
    public String toString() {
        return "账号id " + id.toString() + " 余额 ¥" + balance.toString() + " ";
    }

    //转账
    public void transactionToTarget(Integer money, Account target) {
        //转账过程先上锁
        Allocator.getAllocator().addLock(this, target);
        System.out.println("转账开始--转出账号:" + this.toString() + "|转入账号:" + target.toString());
        if (this.balance - money >= 0) {
            this.balance -= money;
            target.setBalance(target.getBalance() + money);
            System.out.println("转账成功--转出账号:" + this.toString() + "|转入账号:" + target.toString());
        } else {
            System.out.println("转账失败--转出账号:" + this.toString() + "|想转金额:¥" + money.toString());
        }
        //释放锁
        Allocator.getAllocator().releaseLock(this, target);
    }
}

2. 线程间的共享:

2.1 synchronized关键字

2.1.1 介绍

使用synchronized关键字后, 被它修饰的方法或者代码块在任何时候只能有一个线程执行。一般我们使用synchronized关键字来修饰实例方法,修饰静态方法,修饰代码块
synchronized加到static静态方法上以及加到代码块中是synchronized (xxx.class){…} 这种格式, 那么此时加的是类锁。 如果synchronized加到实例方法上以及代码块中是synchronized (this){…}这种格式,那么此时加的是对象锁。
在类内部声明一个私有属性如private Object lock,在需要加锁的代码段synchronized(lock). 此时加的是私有锁。
同一个对象: 不同线程之间, 对象锁之间存在竞争关系, 类锁之间存在竞争关系, 对象锁和类锁不存在竞争关系, 对象锁和私有锁也不存在竞争关系.
不同对象: 不同线程之间, 对象锁不存在竞争关系, 修饰静态方法和修改类(xxx.class)的类锁存在竞争关系, 修饰静态方法的类锁和修饰静态变量的类锁不存在竞争关系。(代码示例如下)

package com.joker.thread.share;


public class ObjectLock {
    // 用于类锁的计数
    private static int i = 0;
    // 私有锁
    private Object lock = new Object();

    private static Object staticLock = new Object();

    /**
     * 无锁方法
     *
     * @param threadId
     * @param objThread
     */
    public void noSyncLock(long threadId, ObjThread objThread) {
        System.out.println("noSyncLock...class is " + objThread + " threadId is " + threadId + " TIME = " + System.currentTimeMillis() + "ms");
    }

    /**
     * 对象锁方法1
     */
    public synchronized void synOnMethod() {
        System.out.println("synOnMethod begins" + ", time = "
                + System.currentTimeMillis() + "ms");
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("synOnMethod ends");
    }

    /**
     * 对象锁方法2,采用synchronized (this)来加锁
     */
    public void synInMethod() {
        synchronized (this) {
            System.out.println("synInMethod begins" + ", time = "
                    + System.currentTimeMillis() + "ms");
            try {
                Thread.sleep(2000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("synInMethod ends");
        }

    }

    /**
     * 对象锁方法3
     */
    public void synMethodWithObj() {
        synchronized (lock) {
            System.out.println("synMethodWithObj begins" + ", time = "
                    + System.currentTimeMillis() + "ms");
            try {
                Thread.sleep(2000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("synMethodWithObj ends");
        }
    }

    // ------------------------------------------------------------------------------

    /**
     * 类锁 锁静态变量
     */
    public static void staticSynMethodWithObj() {
        synchronized (staticLock) {
            System.out.println("staticSynMethodWithObj begins" + ", time = "
                    + System.currentTimeMillis() + "ms");
            try {
                Thread.sleep(2000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("staticSynMethodWithObj ends");
        }
    }

    /**
     * 类锁 加载静态方法上
     */
    public static synchronized void increament() {
        System.out.println("class synchronized. i = " + i + ", time = "
                + System.currentTimeMillis() + "ms");
        i++;
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("class synchronized ends.");
    }

    /**
     * 类锁 锁住ObjectLock.class
     */
    public static void staticSynInMethod() {
        synchronized (ObjectLock.class) {
            System.out.println("staticSynInMethod begins" + ", time = "
                    + System.currentTimeMillis() + "ms");
            try {
                Thread.sleep(2000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("staticSynInMethod ends");
        }
    }


    public static void main(String[] args) {
        System.out.println("start time = " + System.currentTimeMillis() + "ms");
        ObjectLock test = new ObjectLock();
        for (int i = 0; i < 3; i++) {
            //ObjectLock test = new ObjectLock();
            Thread thread = new ObjThread(test, i);
            thread.start();
        }
    }
}

class ObjThread extends Thread {
    ObjectLock lock;
    int i = 0;

    public ObjThread(ObjectLock lock, int i) {
        this.lock = lock;
        this.i = i;
    }

    @Override
    public void run() {
        //无锁方法
        //lock.noSyncLock(this.getId(), this);
        //对象锁方法1,采用synchronized synInMethod的方式
        //lock.synInMethod();
        //对象锁方法2,采用synchronized(this)的方式
        //lock.synOnMethod();
        //私有锁方法,采用synchronized(object)的方式
        //lock.synMethodWithObj();
        //类锁方法,采用static synchronized increment的方式
        ObjectLock.increament();
        ObjectLock.staticSynInMethod();
        //ObjectLock.staticSynMethodWithObj();
    }
}

2.1.2 synchronized经典例子(单例代码实现):

/**
 * 双重校验锁 实现单例
 */
public class Singleton {
    /**
     * uniqueSingleton = new Singleton(); 可以拆分为三步去执行:
     * 1. 为 uniqueSingleton分配内存空间
     * 2. 初始化 uniqueSingleton
     * 3. 将 uniqueSingleton 指向分配的内存空间
     * 正常情况下, 是上述三步, 但是jvm存在指令重排的特性,执行顺序变成了1 -> 3 -> 2. 所以在多线程情况下,
     * 会获得没有初始化的实例对象。但是volatile可以禁止jvm指令重排,保证多线程情况也可以使用。
     */
    private volatile static Singleton uniqueSingleton;

    public static Singleton getSingleton() {
        // 先判断对象是否已经实例化, 没有实例化才可以进加锁代码
        if (uniqueSingleton == null) {
            // class类 上锁
            synchronized (Singleton.class) {
                if (uniqueSingleton == null) {
                    uniqueSingleton = new Singleton();
                }
            }
        }
        return uniqueSingleton;
    }
}

2.1.3 synchronized底层实现原理

补充: java的对象头和monitor

在jvm中, 对象是分为三个部分存在的: 对象头, 实例数据, 对齐填充. 实例数据存放类的属性数据信息, 包括父类的属性信息。对齐填充的作用是如果虚拟机要求对象起始地址必须是8字节整数倍, 对齐填充仅仅是为了使字节对齐。对象头主要结构是由Mark Word 和 Class Metadata Address组成,其中Mark Word存储对象的hashCode、锁信息(一个锁对应一个monitor)或分代年龄或GC标志等信息,Class Metadata Address是类型指针指向对象的类元数据,JVM通过该指针确定该对象是哪个类的实例。
PS: 在jdk6之前, 锁只有两种状态(无锁,重量级锁)。jdk6之后, 有了四种状态:无锁状态、偏向锁、轻量级锁、重量级锁

2.1.3.1 修饰代码块(上述单例的代码即符合)

执行 javap -c -s -v -l Singleton.class
image.png
使用synchronized同步代码块的首尾会出现monitorenter和monitorexit指令, 其中monitorenter指令指向代码同步开始的位置, monitorexit指向结尾位置。当执行minitorenter指令,线程就会获取monitor(也就是锁)的持有权。当计数器是0的时候,可以成功获取。获取后计数器+1。当执行monitorexit后, 将计数器置为0。

2.1.3.2 修饰方法:

public class SynchronizedDemo {
    public synchronized void method() {
        System.out.println("synchronized 代码块");
    }
}

image.png
JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。

2.1.4 synchronized优化

jdk1.6新增了两个锁状态(偏向锁,轻量级锁),通过锁消除,锁粗化,自旋锁等方法使用各种场景,给synchronized性能带来了很大的提升。

2.1.4.1 锁升级:

Synchronized 同步锁初始为偏向锁,随着线程竞争愈发激烈,偏向锁升级到轻量级锁,最终升级到重量级锁。
偏向锁: 一个线程获得锁以后, 那么这个锁进入偏向状态,当该线程再次请求锁时候,只要检查对象是不是偏向锁
并且当前线程是不是和对象头里记录的是不是一致即可。不需要再做任何同步操作(锁申请之类的操作)。
轻量级锁: 当存在第二个线程加入了锁竞争,偏向锁就会立即升级为轻量级锁。在轻量级锁状态下继续锁竞争,没有抢到锁的线程将自旋,即不停地循环判断锁是否能够被成功获取。
PS: 锁竞争 -> 如果多个线程轮流获取一个锁,但是每次获取锁的时候都很顺利,没有发生阻塞,那么就不存在锁竞争。只有当某线程尝试获取锁的时候,发现该锁已经被占用,只能等待其释放,这才发生了锁竞争。
重量级锁: 如果锁竞争情况严重,某个达到最大自旋次数的线程,锁就会被升级成重量级锁,当后续线程尝试获取锁时,发现被占用的锁是重量级锁,则直接将自己挂起(而不是忙等),等待将来被唤醒。

2.1.4.2 锁消除:对运行上下文进行扫描,去除不可能存在竞争的锁

image.png
object锁是私有变量,不存在锁竞争关系

2.1.4.3 锁粗化: 通过扩大锁的范围,避免反复加锁和释放锁

image.png

2.1.5 synchronized与ReenTrantLock区别:

1.两者都是可重入锁, 可重入锁概念请见:https://www.yuque.com/wangyanyang/qz7fwa/lqczuy.
2.synchronized依赖于 JVM. 而 ReenTrantLock依赖于API.
3.ReenTrantLock增加了高级功能:
ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。 ReenTrantLock默认情况是非公平的,可以通过 ReenTrantLock类的ReentrantLock(boolean fair)构造方法来制定是否是公平的。
synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类也可以实现,但是需要借助于Condition接口与newCondition() 方法。在一个Lock对象中可以创建多个Condition实例(即对象监视器),线程对象可以注册在指定的Condition中,从而可以**有选择性的进行线程通知**,在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知”。

2.2. volatile关键字

2.2.1 已有的问题:

在java内存模型中,主内存存放全局变量, 在进行变量的修改的时候, 是当前线程先把数据copy到自己当前的线程中来, 做完一系列操作后再会写到主内存中。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。

2.2.2 解决的问题:

当变量被声明为volatile之后, 就告诉JVM, 这个变量是不稳定的. 每次使用它都到主内存中拿, 换句话说 volatile 关键字的主要作用就是**保证变量的可见性, 还有一个作用是防止指令重排序。