为了在简历上写掌握Java多线程和并发编程,做了两万字总结!!!

概述

面试中,多线程和并发编程已经是必不可少的了,我经常看到此类问题,当时也简单了解过,什么继承Thread类,实现Runnable接口,这些都被说烂了,知道这些当然是远远不够的,于是这几天搜索相关资料恶补了一下,为了方便后期复习,在此做个总结。

继承Thread类

这个可以说是很原始的方式,就是继承Thread类,重写run方法。

package com.hzy;

public class Main {
    public static void main(String[] args) {
        new MyThread().start();
    }
}
class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

由于Java是单继承的,所以这种方法用的比较少,一般都是用Runnable接口

实现Runnable接口

这里给出几个常用的构造方法

先给出一个传统的方法实现

package com.hzy;

public class Main {
    public static void main(String[] args) {
        new Thread(new MyThread()).start();
        new Thread(new MyThread(),"贺志营").start();
    }
}
class MyThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

我们也可以通过匿名内部类实现

package com.hzy;

public class Main {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                }
            }
        }).start();
    }
}

还可以通过Lamata表达式实现

package com.hzy;

public class Main {
    public static void main(String[] args) {
        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }).start();
    }
}

实现Callable接口

Callable接口可以接收返回值,可以抛出异常,重写的是call方法

package com.hzy;

import java.util.concurrent.*;

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建执行服务
        ExecutorService service = Executors.newFixedThreadPool(3);
        // 提交执行
        Future<Boolean> future1 = service.submit(new MyThread());
        Future<Boolean> future2 = service.submit(new MyThread());
        Future<Boolean> future3 = service.submit(new MyThread());
        // 获取返回值
        Boolean b1 = future1.get();
        Boolean b2 = future2.get();
        Boolean b3 = future3.get();

        System.out.println(b1);
        System.out.println(b2);
        System.out.println(b3);
        // 关闭服务
        service.shutdown();
    }
}

class MyThread implements Callable<Boolean> {

    @Override
    public Boolean call() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
        return true;
    }
}

另外还可以通过FutureTask适配器创建


package com.hzy;

import java.util.concurrent.*;

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建一个适配器
        FutureTask futureTask = new FutureTask(new MyThread());
        new Thread(futureTask,"A").start();
        Boolean o = (Boolean) futureTask.get();
        System.out.println(o);
    }
}

class MyThread implements Callable<Boolean> {

    @Override
    public Boolean call() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
        return true;
    }
}

线程池

线程的创建跟Callable差不多,也是用ExecutorService

package com.hzy;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
    public static void main(String[] args){

        // 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
        ExecutorService service1 = Executors.newCachedThreadPool();

        // 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
        ExecutorService service2 = Executors.newFixedThreadPool(10);

        // 创建一个定长线程池,支持定时及周期性任务执行。
        ExecutorService service3 = Executors.newScheduledThreadPool(10);

        // 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
        ExecutorService service4 = Executors.newSingleThreadExecutor();

        // 执行
        service1.execute(new MyThread());
        service2.execute(new MyThread());
        service3.execute(new MyThread());
        service4.execute(new MyThread());
        
        // 关闭连接
        service1.shutdown();
        service2.shutdown();
        service3.shutdown();
        service4.shutdown();
    }
}
class MyThread implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

在阿里巴巴开发手册中有规定,创建线程池要用ThreadPoolExecutor

对于ThreadPoolExecutor的学习,就从七大参数四种拒绝策略

int corePoolSize// 核心线程池大小
int maximumPoolSize// 最大核心线程池大小
long keepAliveTime// 超时存活时间
TimeUnit unit//  超时单位
BlockingQueue<Runnable> workQueue// 阻塞队列
ThreadFactory threadFactory// 线程工厂,用于创建线程
RejectedExecutionHandler handler// 拒绝策略

AbortPolicy());// 银行满了还有人进来,不处理,抛出异常(默认)
CallerRunsPolicy();// 银行满了,不处理,哪里来的去哪里,一般抛给main线程
DiscardPolicy();// 银行满了,把该线程丢掉,不抛异常
DiscardOldestPolicy();// 银行满了,会和先来的线程竞争,不抛异常
package com.hzy;

import java.util.concurrent.*;

public class Main {
    public static void main(String[] args){

        /**
         * 用一个银行的例子进行讲解这七大参数
         */
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,// 两个常开营业窗口
                5,// 五个窗口,其中三个应急用
                3,// 超时存货时间
                TimeUnit.SECONDS,// 超时时间单位
                new LinkedBlockingDeque<>(3),// 银行候客区大小
                Executors.defaultThreadFactory(),// 默认线程池工厂
                new ThreadPoolExecutor.AbortPolicy());// 银行满了还有人进来,不处理,抛出异常(默认)
                // new ThreadPoolExecutor.CallerRunsPolicy();// 银行满了,不处理,哪里来的去哪里,一般抛给main线程
                // new ThreadPoolExecutor.DiscardPolicy();// 银行满了,把该线程丢掉,不抛异常
                // new ThreadPoolExecutor.DiscardOldestPolicy();// 银行满了,会和先来的线程竞争,不抛异常
        for (int i = 0; i < 8; i++) {
            threadPool.execute(()->{
                System.out.println(Thread.currentThread().getName());
            });
        }
        // 关闭连接
        threadPool.shutdown();
    }
}

在定义线程池最大大小的时候,一般有两种策略CPU密集型IO密集型,所谓CPU密集型,也就是,几核的CPU就定义为几,我的是八核,所以定义为8,Runtime.getRuntime().availableProcessors();// 获取CPU的核数,IO密集型,就是判断程序中有多少个非常耗IO线程的程序,最大线程池的大小要大于这个值即可。

线程的五大状态


创建状态
所谓的创建状态,也就是我们的new Thread();
就绪状态
所谓的就绪状态,就是我们myThread.start();
运行状态
运行状态就是我们的代码执行。
阻塞状态
当线程等待(wait)、同步(synchronized)、sleep和join的时候
死亡状态
run()结束、main()结束
线程常用方法

  • setPriority(int new Priority)更改线程的优先级
  • sleep(long millis)让当前线程进入休眠
  • join()相当于插队,插入的线程执行结束后,被插队线程继续执行。
  • yield()线程礼让,暂停当前的线程,并把该线程状态转化为就绪状态
  • interrupt()中断线程(不建议用)
  • isAlive()判断线程是否处于存活状态

多线程买票案例

当多个线程操作同一个资源的时候,就会出现线程安全问题。假设我们有100张票,在三个窗口同时卖,也就是我们有三个线程去买票。

package com.hzy;

public class Main {
    public static void main(String[] args){
        MyThread myThread = new MyThread();
        new Thread(myThread,"窗口1").start();
        new Thread(myThread,"窗口2").start();
        new Thread(myThread,"窗口3").start();
        new Thread(myThread,"窗口4").start();
        new Thread(myThread,"窗口5").start();
    }
}

class MyThread implements Runnable {
    private int ticket = 100;
    private boolean flag = true;
    @Override
    public void run() {
        while (flag) {
            buy();
        }
    }
    public void buy() {
        if (ticket <= 0) {
            flag = false;
            return;
        }
        try {
            Thread.sleep(100);// 模拟买票延迟100ms
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ":"+ ticket--);
    }
}

可以看出,出现了线程安全问题,原因是在对ticket变为0之前,有多个线程同时进来。
解决办法,可以通过synchronized线程同步,可以使用同步方法和同步代码块进行解决。同步方法,也就是在方法上加一个synchronized关键字。

package com.hzy;

public class Main {
    public static void main(String[] args){
        MyThread myThread = new MyThread();
        new Thread(myThread,"窗口1").start();
        new Thread(myThread,"窗口2").start();
        new Thread(myThread,"窗口3").start();
    }
}

class MyThread implements Runnable {
    private int ticket = 100;
    private boolean flag = true;
    @Override
    public void run() {
        while (flag) {
            buy();
        }
    }
    public synchronized void buy() {
        if (ticket <= 0) {
            flag = false;
            return;
        }
        try {
            Thread.sleep(100);// 模拟买票延迟100ms
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ":"+ ticket--);
    }
}

同步代码块锁的是一个对象,即是需要变化的量

package com.hzy;

public class Main {
    public static void main(String[] args){
        MyThread myThread = new MyThread();
        new Thread(myThread,"窗口1").start();
        new Thread(myThread,"窗口2").start();
        new Thread(myThread,"窗口3").start();
    }
}

class MyTicket {
    int ticket;

    public MyTicket(int ticket) {
        this.ticket = ticket;
    }
}

class MyThread implements Runnable {
    MyTicket myTicket = new MyTicket(100);
    private boolean flag = true;
    @Override
    public void run() {
        while (flag) {
            buy();
        }
    }
    public void buy() {
        synchronized (myTicket) {
            if (myTicket.ticket <= 0) {
                flag = false;
                return;
            }
            try {
                Thread.sleep(100);// 模拟买票延迟100ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":"+ myTicket.ticket--);
        }

    }
}

死锁

所谓死锁,也就是A拥有A资源的同时想要B的资源,B拥有B资源的同时想要A的资源。

package com.hzy;

public class Main {
    public static void main(String[] args){
        A a = new A();
        B b = new B();
        new Thread(new MyThread(a,b,0)).start();
        new Thread(new MyThread(a,b,1)).start();
    }
}
class A {

}
class B {

}

class MyThread implements Runnable {
    private A a;
    private B b;
    private int choice;
    public MyThread(A a,B b,int choice) {
        this.a = a;
        this.b = b;
        this.choice = choice;
    }
    @Override
    public void run() {
        if (choice == 0) {
            synchronized (a) {
                System.out.println(Thread.currentThread().getName() + "获得" + a);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (b) {
                    System.out.println(Thread.currentThread().getName() + "获得" + b);
                }
            }
        }else {
            synchronized (b) {
                System.out.println(Thread.currentThread().getName() + "获得" + b);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (a) {
                    System.out.println(Thread.currentThread().getName() + "获得" + a);
                }
            }
        }
    }
}

可以看出,A需要B资源,B需要A资源,出现了死锁的状态。

Lock锁

Lock是一个接口,而不是一个关键字,他是一个显示锁,只能锁同步代码块,不能锁方法,可以显示加锁,释放锁,可以指定唤醒某一个线程,Lock锁有一个实现类是ReentrantLock,可重入锁(递归锁),在这里说一下,所有的锁都是可重入锁,也就是如果我们获得了外面的锁之后,会自动获取里面的锁。

正常的,如果我们不加锁,会出现线程安全问题

package com.hzy;

public class Main {
    public static void main(String[] args){
        MyThread myThread = new MyThread();
        new Thread(myThread).start();
        new Thread(myThread).start();
        new Thread(myThread).start();
    }
}
class MyThread implements Runnable {

    private int ticket = 100;
    @Override
    public void run() {
        while (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(ticket --);
        }
    }
}

可以通过RenntrantLock进行加锁,显示锁,记得解锁。

package com.hzy;

import java.util.concurrent.locks.ReentrantLock;

public class Main {
    public static void main(String[] args){
        MyThread myThread = new MyThread();
        new Thread(myThread).start();
        new Thread(myThread).start();
        new Thread(myThread).start();
    }
}
class MyThread implements Runnable {

    private int ticket = 100;
    // 定义lock锁
    ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true) {
            lock.lock();
            if (ticket > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(ticket --);
            }else {
                break;
            }
            lock.unlock();
        }
    }
}

比ReentrantLock更细的锁是,ReentrantReadWriteLock,这里有一个读锁,一个写锁

package com.hzy;

import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        MyReadWriteLock myReadWriteLock = new MyReadWriteLock();
        for (int i = 0; i < 5; i++) {
            int temp = i;
            new Thread(()->{
                myReadWriteLock.put(temp + "",temp + "");
            },temp + "").start();
        }

        for (int i = 0; i < 5; i++) {
            int temp = i;
            new Thread(()->{
                myReadWriteLock.get(temp + "");
            },temp + "").start();
        }
    }
}

class MyReadWriteLock {
    private volatile Map<String,String> map = new HashMap<>();

    public void put(String key,String value) {
        System.out.println(Thread.currentThread().getName() + "写入" + key);
        map.put(key,value);
        System.out.println(Thread.currentThread().getName() + "写入完成");
    }

    public void get(String key) {
        System.out.println(Thread.currentThread().getName() + "读取" + key);
        map.get(key);
        System.out.println(Thread.currentThread().getName() + "读取完成");
    }
}

如果不加锁的话,在写入完成之前会被其他线程插入

而我们想要的是,在写入的时候,只能是一个线程,而读取的时候,可以是多个线程。

package com.hzy;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Main {
    public static void main(String[] args) {
        MyReadWriteLock myReadWriteLock = new MyReadWriteLock();
        for (int i = 0; i < 5; i++) {
            int temp = i;
            new Thread(()->{
                myReadWriteLock.put(temp + "",temp + "");
            },temp + "").start();
        }

        for (int i = 0; i < 5; i++) {
            int temp = i;
            new Thread(()->{
                myReadWriteLock.get(temp + "");
            },temp + "").start();
        }
    }
}

class MyReadWriteLock {
    private volatile Map<String,String> map = new HashMap<>();
    ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    public void put(String key,String value) {
        readWriteLock.writeLock().lock();
        System.out.println(Thread.currentThread().getName() + "写入" + key);
        map.put(key,value);
        System.out.println(Thread.currentThread().getName() + "写入完成");
        readWriteLock.writeLock().unlock();
    }

    public void get(String key) {
        readWriteLock.readLock().lock();
        System.out.println(Thread.currentThread().getName() + "读取" + key);
        map.get(key);
        System.out.println(Thread.currentThread().getName() + "读取完成");
        readWriteLock.readLock().unlock();
    }
}

可以看出,在写入的时候,是一个写入,一个写入完成,在读取的时候,可能会有多个读取。


synchronized和Lock的区别

  • synchronized是关键字,Lock是类
  • synchronized无法获取锁的状态,Lock可以
  • synchronized会自动释放锁,Lock需要手动
  • synchronized没有Lock锁灵活(Lock锁可以自己定制)

只说理论是不行的,下面说几个ReentrantLock的场景

  • ReentrantLock默认是非公平锁,但它可以设置公平锁,也就是谁先来的谁先获得锁new ReentrantLock(true)
  • ReentrantLock可以响应中断,也就是当两个线程发生死锁的时候,你把A线程中断了,B线程可以正常运行。
  • ReentrantLock可以通过tryLock()实现限时等待,这样可以解决死锁问题。

synchronized

  • synchronized在JDK1.6进行了锁的优化,也就是当一个线程多次访问一个同步代码块的时候,此时会记录该线程的threadId也就是,当你再来访问的时候,我就只需判断threadId就行了,效率高,这属于偏向锁。
  • 当有多个线程来的时候,那么这个锁就会升级为轻量级锁,也就是通过CAS,来进行尝试获取锁,是一种自旋锁的状态。如果在短时间内可以获得锁,不会堵塞,而且节约了CUP上下文切换的时间。
  • 如果长时间没有获取到锁,会消耗CUP的资源,因为在那一直死循环,经过一个时间段后会升级为重量级锁,会发生阻塞。其中锁升级是不可逆的。

生产者消费者问题

这是一个经典的线程通信问题,也就是不同线程之间有联系,生产者生产的东西放到缓冲区,如果缓冲区满了,生产者进入堵塞,缓冲区空了,消费者堵塞。

常用的方法有wait(),notify(),notifyAll()

package com.hzy;

import java.util.concurrent.locks.ReentrantLock;

public class Main {
    public static void main(String[] args){
        Buffer buffer = new Buffer();
        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                System.out.println("生产者生产了" + i);
                buffer.put();
            }
        },"生产者").start();
        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                System.out.println("消费者消费了" + i);
                buffer.get();
            }
        },"消费者").start();
    }
}

// 缓冲区
class Buffer {
    private int len = 0;
    public synchronized void put() {
        if (len < 10) {
            len ++;
        } else {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        notifyAll();
    }
    public synchronized void get() {
        if (len > 0) {
            len --;
        } else {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        notifyAll();
    }
}

学习了Lock锁,这里的生产者消费者问题,可以通过Lock锁实现,可以指定唤醒某个线程,常用的方法是await,signal。
这里给出缓冲区

// 缓冲区
class Buffer {
    private int len = 0;
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();// 该对象可以设置一些条件
    public void put() {
        lock.lock();
        if (len < 10) {
            len ++;
        } else {
            try {
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        condition.signalAll();
        lock.unlock();
    }
    public void get() {
        lock.lock();
        if (len > 0) {
            len --;
        } else {
            try {
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        condition.signalAll();
        lock.unlock();
    }
}

其中这里引入了Condition,他可以指定唤醒某个线程,这里我们演示三个线程ABC。

package com.hzy;

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

public class Main {
    public static void main(String[] args){
        Buffer buffer = new Buffer();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                buffer.a();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                buffer.b();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                buffer.c();
            }
        },"C").start();
    }
}

// 缓冲区
class Buffer {
    private int flag = 1;// 1执行A,2执行B,3执行C
    Lock lock = new ReentrantLock();
    Condition condition1 = lock.newCondition();// 该对象可以设置一些条件
    Condition condition2 = lock.newCondition();// 该对象可以设置一些条件
    Condition condition3 = lock.newCondition();// 该对象可以设置一些条件
    public void a() {
        lock.lock();
        try {
            while (flag != 1) {
                condition1.await();
            }
            System.out.println("A");
            flag = 2;
            condition2.signal();// 指定唤醒B
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void b() {
        lock.lock();
        try {
            while (flag != 2) {
                condition2.await();
            }
            System.out.println("B");
            flag = 3;
            condition3.signal();// 指定唤醒C
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void c() {
        lock.lock();
        try {
            while (flag != 3) {
                condition3.await();
            }
            System.out.println("C");
            flag = 1;
            condition1.signal();// 指定唤醒A
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

}

结果都是ABC、ABC…

八锁问题

package com.hzy;
import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args){
        Phone phone = new Phone();
        new Thread(()->{
            phone.sendMsg();
        }, "A").start();
        try {
            TimeUnit.SECONDS.sleep(1);// 休息一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone.call();
        }, "B").start();
    }
}

class Phone {
    public synchronized void sendMsg() {
        System.out.println("sendMsg");
    }

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

先输出sendMsg,不要理解为是先调用了sendMsg,而是因为A线程先获得了锁。


package com.hzy;


import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args){
        Phone phone = new Phone();
        new Thread(()->{
            phone.sendMsg();
        }, "A").start();
        try {
            TimeUnit.SECONDS.sleep(1);// 休息一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone.call();
        }, "B").start();
    }
}

class Phone {
    public synchronized void sendMsg() {
        try {
            TimeUnit.SECONDS.sleep(3);// 休息一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendMsg");
    }

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

这个例子,再次解释了是因为sendMsg先获得的锁,这里的synchronized锁的对象是调用者,这两个方法用的是同一把锁,谁先拿到,谁先执行


package com.hzy;


import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args){
        Phone phone = new Phone();
        new Thread(()->{
            phone.sendMsg();
        }, "A").start();
        try {
            TimeUnit.SECONDS.sleep(1);// 休息一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone.call();
        }, "B").start();
    }
}

class Phone {
    public synchronized void sendMsg() {
        try {
            TimeUnit.SECONDS.sleep(3);// 休息一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendMsg");
    }

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

这里把call方法的synchronized去掉了,会先输出哪个呢,答案是先输出call,因为他不去获取锁资源,所以直接输出了


package com.hzy;


import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args){
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();
        new Thread(()->{
            phone1.sendMsg();
        }, "A").start();
        try {
            TimeUnit.SECONDS.sleep(1);// 休息一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone2.call();
        }, "B").start();
    }
}

class Phone {
    public synchronized void sendMsg() {
        try {
            TimeUnit.SECONDS.sleep(3);// 休息一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendMsg");
    }

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

这里用两个phone对象,分别调用sendMsg和call方法,会先执行哪个,答案是先执行call,因为这里的锁锁的是对象,而他们不是同一个对象,所以资源不受影响。


package com.hzy;


import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args){
        Phone phone = new Phone();
        new Thread(()->{
            phone.sendMsg();
        }, "A").start();
        try {
            TimeUnit.SECONDS.sleep(1);// 休息一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone.call();
        }, "B").start();
    }
}

class Phone {
    public static synchronized void sendMsg() {
        try {
            TimeUnit.SECONDS.sleep(3);// 休息一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendMsg");
    }

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

这里是通过一个对象,去调用静态的同步方法,看看是先sendMsg还是call,答案是sentMsg,因为这里锁的是Class对象(只有一个)


package com.hzy;


import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args){
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();
        new Thread(()->{
            phone1.sendMsg();
        }, "A").start();
        try {
            TimeUnit.SECONDS.sleep(1);// 休息一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone2.call();
        }, "B").start();
    }
}

class Phone {
    public static synchronized void sendMsg() {
        try {
            TimeUnit.SECONDS.sleep(3);// 休息一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendMsg");
    }

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

这里是通过两个对象去调用sentMsg和call,结果是什么呢,答案还是先执行sentMsg,因为锁的是Class对象,所以不管是phone1还是phone2,都是属于Phone的Class对象。



package com.hzy;


import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args){
        Phone phone = new Phone();
        new Thread(()->{
            phone.sendMsg();
        }, "A").start();
        try {
            TimeUnit.SECONDS.sleep(1);// 休息一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone.call();
        }, "B").start();
    }
}

class Phone {
    public static synchronized void sendMsg() {
        try {
            TimeUnit.SECONDS.sleep(3);// 休息一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendMsg");
    }

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

这里锁的一个是静态同步方法,一个是普通同步方法,用一个对象对调用,结果是什么呢,答案是先call,因为静态同步方法锁的是Class对象,而普通同步方法锁的是调用者,锁的不是同一个东西。


package com.hzy;

import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args){
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();
        new Thread(()->{
            phone1.sendMsg();
        }, "A").start();
        try {
            TimeUnit.SECONDS.sleep(1);// 休息一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone2.call();
        }, "B").start();
    }
}

class Phone {
    public static synchronized void sendMsg() {
        try {
            TimeUnit.SECONDS.sleep(3);// 休息一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendMsg");
    }

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

这里用两个对象去调用静态同步方法sendMsg和普通同步方法call,会先输出哪个,答案是call,因为sendMsg锁的是Class,而call锁的是调用者,不是同一个一个东西

volatile

说起volatile,不难想出三大特性保证可见性不保证原子性禁止指令重排
在讲解volatile之前,需要讲解一个东西,Java内存模型(Java Memory Model,JMM),当线程读取内存中的一个变量时,会先把这个变量拷贝到CPU的高速缓存区,然后对其进行操作,操作完成后,会把该变量写入到内存中。在单线程中是不会出现任何问题的,但是在多线程中就会有问题,当线程1读取了该变量a=1到缓存区进行了加1操作,还没写到内存中,线程2读取了内存中的变量a=1也进行加1操作,然后线程1写入内存a=2,线程2也写入a=2到内存,那么最后,该变量的值是2,而不是3(出现了线程安全问题)。我们想要当线程1进行了加1操作之后,让线程2知道,这就是volatile的作用了,可以保证可见性,也就是,当线程1对a变量进行了加1操作,会直接写入到内存中(立即马上),并且通知线程2,变量被修改了,要求线程2缓冲区的值去内存中重新读取。但是,加1操作不是原子性的(三步,首先读取a变量的值,然后对其进行加1操作,然后赋值给a),也就是说,当线程1读取a变量到缓冲区后,还没有修改a的值,此时线程2进来了,读取了a的值,并且对其进行了加1操作,由于可见性,会把线程1缓冲区的值进行修改,但是,线程1中的CPU已经读取了缓冲区的值,而且是更新前的值,所以出现了线程安全问题,也是volatile不保证原子性的问题。于是就需要加1操作是原子性操作,于是就有了一个automic包,通过该包下的方法,可以实现加1的原子性操作(还有其他原子性操作)。

在原子操作里的自增,其实用的是自旋锁,也就是一直进行比较并交换。

说起原子性操作,其实得聊聊CAS(Compare And Swap,比较并交换),如果我们想要修改某个值num,那么我们可以通过一个方法compareAndSet(5,6)意思是,我们期望num的值是5,如果是,就修改为6。但是这就会有一个问题,我们期望的num是5,如果有其他线程把5修改为了8,然后又修改为了5,最终是5,但是已经被修改过一次了,这就是ABA问题。我们可以通过AtomicStampedReference,也就是原子引用,在创建的时候,有一个印记,相当于版本号,每被修改一次,版本号都被更新,所以,当出现ABA问题的时候,我们就可以清楚的知道,被修改了多少次。

还有一个特性就是禁止指令重排,既然要禁止他,那么就得知道什么是指令重排,所谓指令重排,也就是,为了提高程序执行效率,寄存器对代码的优化,如果我们有一段代码如下

a = 1;// 语句1
b = 2;// 语句2
a = a + 1;// 语句3
b = a + 1;// 语句4

所谓指令重排,也就是在不影响单线程程序程序结果的情况下进行最优执行排序,可以看出,语句1和语句2的执行顺序并不会影响程序的结果,最终a的值为2,b的值为3。下面看这个代码Instance instance = new Instance();
从微观的角度看,这条语句可以分解为三步,一是分配内存空间,二是初始化对象三是指向该内存空间,其中二和三会发生指令重排,如果在多线程的情况下,线程A分配了内存空间,并且执行了该内存空间(没有初始化对象),线程B进行了访问该内存空间,这就会出错了。而volatile就是禁止指令重排的,也并非绝对的禁止,假设我们有1、2、3、4、5条语句

a = 1;
b = 2;
c = 3;
d = 4;
e = 5;

如果我们对c操作进行了volatile修饰,那么a和b依然可以指令重排,d和e也可以指令重排,ab和de不能指令重排了,c把他们分开了(专业术语是给c加了个内存屏障)。

写在后面

贺贺学编程,专注于讲解Java后端、面试题、数据结构、算法、数据库、Linux等编程知识,期待你的关注。

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 1024 设计师:白松林 返回首页