深入浅出Java多线程 笔记分享(1)

进程与线程:

​ 进程就是应用程序在内存中分配的空间,也就是正在运行的程序,各个进程之间互不干扰。同时进程保存着程序每一个时刻运行的状态。进程是操作系统进行资源分配的基本单位,而线程是操作系统进行调度的基本单位。

1. 线程的创建与启动:

继承Thread类,并重写 run 方法:

public class Demo {
 public static class MyThread extends Thread {
 	@Override
 	public void run() {
 	System.out.println("MyThread");
 	}
 }
 public static void main(String[] args) {
 	Thread myThread = new MyThread();
 	myThread.start();
 }
}

实现Runnable接口

public class Demo {
 public static class MyThread implements Runnable {
 	@Override
 	public void run() {
 	System.out.println("MyThread");
 	}
 }
 public static void main(String[] args) {
 	new Thread(new MyThread()).start();
 	// Java 8 函数式编程,可以省略MyThread类
 	new Thread(() -> {
 	System.out.println("Java 8 匿名内部类");
 	}).start();
 }
}

还有几种可以获取返回值的方法在后面讲解;
    
   

2.Thread 源码

Java.lang.Thead

public
class Thread implements Runnable {
    
    /* Make sure registerNatives is the first thing <clinit> does. */
    private static native void registerNatives();
    static {
        registerNatives();
    }

    private volatile String name;
    private int            priority;
    private Thread         threadQ;
    private long           eetop;

    /* Whether or not to single_step this thread. */
    private boolean     single_step;

    /* Whether or not the thread is a daemon thread. */
    private boolean     daemon = false;

    /* JVM state */
    private boolean     stillborn = false;

    /* What will be run. */
    private Runnable target;

    /* The group of this thread */
    private ThreadGroup group;

    /* The context ClassLoader for this thread */
    private ClassLoader contextClassLoader;

    /* The inherited AccessControlContext of this thread */
    private AccessControlContext inheritedAccessControlContext;

    /* For autonumbering anonymous threads. */
        private static int threadInitNumber;//Thread - 0 -1 
    private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    }

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

    /*
     * The requested stack size for this thread, or 0 if the creator did
     * not specify a stack size.  It is up to the VM to do whatever it
     * likes with this number; some VMs will ignore it. Xss
     */
    private long stackSize;

    /*
     * JVM-private state that persists after native thread termination.
     */
    private long nativeParkEventPointer;

    /*
     * Thread ID
     */
    private long tid;

    /* For generating thread ID */
    private static long threadSeqNumber;

    /* Java thread status for tools,
     * initialized to indicate thread 'not yet started'
     */

    private volatile int threadStatus = 0;



    /**
     * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = 10;
    
   
}

Thread 构造方法

Thread(Runnabletarget)  //name = "Thread - ...."
Thread(Runnabletarget, String name)  
Thread(ThreadGroupgroup, Runnable target)  
Thread(ThreadGroupgroup, Runnable target, String name)  
Thread(ThreadGroupgroup, Runnable target, String name, long stackSize) 

String name:线程的名子。这个名子可以在建立Thread实例后通过Thread类的setName方法设置。默认线程名:Thread-N,N是线程建立的顺序,是一个不重复的正整数。

ThreadGroup group:当前建立的线程所属的线程组。如果不指定线程组,所有的线程都被加到一个默认的线程组中。

long stackSize:线程栈的大小,这个值一般是CPU页面的整数倍。如x86的页面大小是4KB.在x86平台下,默认的线程栈大小是12KB。

ThreadLocal

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }

    this.name = name;

    Thread parent = currentThread();
    SecurityManager security = System.getSecurityManager();
    if (g == null) {
        /* Determine if it's an applet or not */

        /* If there is a security manager, ask the security manager
           what to do. */
        if (security != null) {
            g = security.getThreadGroup();
        }

        /* If the security doesn't have a strong opinion of the matter
           use the parent thread group. */
        if (g == null) {
            g = parent.getThreadGroup();
        }
    }

    /* checkAccess regardless of whether or not threadgroup is
       explicitly passed in. */
    g.checkAccess();

    /*
     * Do we have the required permissions?
     */
    if (security != null) {
        if (isCCLOverridden(getClass())) {
            security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
        }
    }

    g.addUnstarted();

    this.group = g;
    this.daemon = parent.isDaemon();
    this.priority = parent.getPriority();
    if (security == null || isCCLOverridden(parent.getClass()))
        this.contextClassLoader = parent.getContextClassLoader();
    else
        this.contextClassLoader = parent.contextClassLoader;
    this.inheritedAccessControlContext =
            acc != null ? acc : AccessController.getContext();
    this.target = target;
    setPriority(priority);
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    /* Stash the specified stack size in case the VM cares */
    this.stackSize = stackSize;

    /* Set thread ID */
    tid = nextThreadID();
}

start() 与 run()

public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

private native void start0();
/**
 * If this thread was constructed using a separate
 * <code>Runnable</code> run object, then that
 * <code>Runnable</code> object's <code>run</code> method is called;
 * otherwise, this method does nothing and returns.
 * <p>
 * Subclasses of <code>Thread</code> should override this method.
 *
 * @see     #start()
 * @see     #stop()
 * @see     #Thread(ThreadGroup, Runnable, String)
 */
@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

这里介绍一下Thread类的几个常用的方法:

currentThread():静态方法,返回对当前正在执行的线程对象的引用;

start():开始执行线程的方法,java虚拟机会调用线程内的run()方法;

yield():yield在英语里有放弃的意思,同样,这里的yield()指的是当前线程愿意让出对当前处理器的占用。这里需要注意的是,就算当前 线程调用了yield()方法,程序在调度的时候,也还有可能继续运行这个线程的;

sleep():静态方法,使当前线程睡眠一段时间;

join():使当前线程等待另一个线程执行完毕之后再继续执行,内部调用的是Object类的wait方法实现的;

Thread类与Runnable接口的比较:

由于Java“单继承,多实现”的特性,Runnable接口使用起来比Thread更灵活。
Runnable接口出现更符合面向对象,将线程单独进行对象的封装。
Runnable接口出现,降低了线程对象和线程任务的耦合性。
如果使用线程时不需要使用Thread类的诸多方法,显然使用Runnable接口更为轻量。

CallableFutureFutureTask

// 自定义Callable
class Task implements Callable<Integer>{
 @Override
 public Integer call() throws Exception {
 // 模拟计算需要一秒
 Thread.sleep(1000);
 return 2;
 }
 public static void main(String args[]) throws Exception {
 // 使用
 ExecutorService executor = Executors.newCachedThreadPool();
 Task task = new Task();
 Future<Integer> result = executor.submit(task);
 // 注意调用get方法会阻塞当前线程,直到得到结果。
 // 所以实际编码中建议使用可以设置超时时间的重载get方法。
 System.out.println(result.get()); 
 }
}
public abstract interface Future<V> {
 public abstract boolean cancel(boolean paramBoolean);
 public abstract boolean isCancelled();
 public abstract boolean isDone();
 public abstract V get() throws InterruptedException, ExecutionException;
 public abstract V get(long paramLong, TimeUnit paramTimeUnit)
 throws InterruptedException, ExecutionException, TimeoutException; }


public interface RunnableFuture<V> extends Runnable, Future<V> {
 /**
 * Sets this Future to the result of its computation
 * unless it has been cancelled.
 */
 void run(); }







// 自定义Callable,与上面一样
class Task implements Callable<Integer>{
 @Override
 public Integer call() throws Exception {
 // 模拟计算需要一秒
 Thread.sleep(1000);
 return 2;
 }
 public static void main(String args[]) throws Exception {
 // 使用
 ExecutorService executor = Executors.newCachedThreadPool();
 FutureTask<Integer> futureTask = new FutureTask<>(new Task());
 executor.submit(futureTask);
 System.out.println(futureTask.get());
 }
}

3. 线程组**(ThreadGroup)**

每个Thread必然存在于一个ThreadGroup中,Thread不能独立于ThreadGroup存在。

public class Demo {
 public static void main(String[] args) {
 	Thread testThread = new Thread(() -> {
 		System.out.println("testThread当前线程组名字:" +
 		Thread.currentThread().getThreadGroup().getName());
 		System.out.println("testThread线程名字:" +
 		Thread.currentThread().getName());
 	});
 	testThread.start();
 	System.out.println("执行main所在线程的线程组名字: " + Thread.currentThread().g
 	System.out.println("执行main方法线程名字:" + Thread.currentThread().getNa
 }
}
                       
                       
输出结果:
执行main所在线程的线程组名字: main
执行main方法线程名字:main
testThread当前线程组名字:main
testThread线程名字:Thread-0

ThreadGroup管理着它下面的Thread,ThreadGroup是一个标准的向下引用的树状结构,这样设计的原因是防止**"上级"线程被"下级"线程引用而无法有效地被GC**回 收。

线程的优先级

int getPriority() 方法可以获得

线程组源码

public class ThreadGroup implements Thread.UncaughtExceptionHandler {
 private final ThreadGroup parent; // 父亲ThreadGroup
 String name; // ThreadGroupr 的名称
 int maxPriority; // 线程最大优先级
 boolean destroyed; // 是否被销毁
 boolean daemon; // 是否守护线程
 boolean vmAllowSuspension; // 是否可以中断
 int nUnstartedThreads = 0; // 还未启动的线程
 int nthreads; // ThreadGroup中线程数目
 Thread threads[]; // ThreadGroup中的线程
 int ngroups; // 线程组数目
 ThreadGroup groups[]; // 线程组数组
    
    
    
    // 私有构造函数
private ThreadGroup() { 
 this.name = "system";
 this.maxPriority = Thread.MAX_PRIORITY;
 this.parent = null; }
// 默认是以当前ThreadGroup传入作为parent ThreadGroup,新线程组的父线程组是目前正在运行线
public ThreadGroup(String name) {
 this(Thread.currentThread().getThreadGroup(), name);
}
// 构造函数
public ThreadGroup(ThreadGroup parent, String name) {
 this(checkParentAccess(parent), parent, name);
}
// 私有构造函数,主要的构造函数
private ThreadGroup(Void unused, ThreadGroup parent, String name) {
 this.name = name;
 this.maxPriority = parent.maxPriority;
 this.daemon = parent.daemon;
 this.vmAllowSuspension = parent.vmAllowSuspension;
 this.parent = parent;
 parent.add(this);
}
}

4. 操作系统中的线程状态转换

// Thread.State 源码
public enum State {
 NEW,
 RUNNABLE,
 BLOCKED,
 WAITING,
 TIMED_WAITING,
 TERMINATED;
}

1、新状态:线程对象已经创建,还没有在其上调用start()方法。

2、可运行状态:当线程有资格运行,但调度程序还没有把它选定为运行线程时线程所处的状态。当start()方法调用时,线程首先进入可运行状态。在线程运行之后或者从阻塞、等待或睡眠状态回来后,也返回到可运行状态。

3、运行状态:线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。

​ 4、等待/阻塞/睡眠状态:这是线程有资格运行时它所处的状态。实际上这个三状态组合为一种,其共同点是:线程仍旧是活的,但是当前没有条件运行。换句话说,它是可运行的,但是如果某件事件出现,他可能返回到可运行状态。

​ 5、死亡态:当线程的run()方法完成时就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。如果在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

在这里插入图片描述

5. Java线程间的通信

5.1 锁与同步

同步可以以解释为:线程同步是线程之间按照一定的顺序执行

public class NoneLock {
 	static class ThreadA implements Runnable {
 		@Override
		public void run() {
 			for (int i = 0; i < 100; i++) {
 				System.out.println("Thread A " + i);
 			}
 		}
 	}
 	static class ThreadB implements Runnable {
 		@Override
 		public void run() {
 			for (int i = 0; i < 100; i++) {
 			System.out.println("Thread B " + i);
 		}
 	}
 }
 public static void main(String[] args) {
 	new Thread(new ThreadA()).start();
 	new Thread(new ThreadB()).start();
 }
}
out:
....
Thread A 48
Thread A 49
Thread B 0
Thread A 50
Thread B 1
Thread A 51
Thread A 52
....


如果有一个需求是让两个线程按顺序执行


public class ObjectLock {
 	private static Object lock = new Object();
 	static class ThreadA implements Runnable {
 		@Override
 		public void run() {
 			synchronized (lock) {
 				for (int i = 0; i < 100; i++) {
 				System.out.println("Thread A " + i);
 				}
 			}
 		}
 	}
 	static class ThreadB implements Runnable {
 		@Override
 		public void run() {
 			synchronized (lock) {
 				for (int i = 0; i < 100; i++) {
 					System.out.println("Thread B " + i);
 				}
 			}
 		}
 	}
 	public static void main(String[] args) throws InterruptedException {
 		new Thread(new ThreadA()).start();
 		Thread.sleep(10);
 		new Thread(new ThreadB()).start();
 	}
}

5.2 等待**/**通知机制

Java多线程的等待/通知机制是基于 Object 类的 wait() 方法和 notify() ,notifyAll() 方法来实现的。

一个锁同一时刻只能被一个线程持有。

​ 而假如线程A现在持有了一个锁 lock 并开始执行,它可以使用 lock.wait() 让自己进入等待状态。这个时候, lock 这个锁是被释放了的。

​ 这时,线程B获得了 lock 这个锁并开始执行,它可以在某一时刻,使用 lock.notify() ,通知之前持有 lock 锁并进入等待状态的线程A,说“线程A你不用等了,可以往下执行了”。

public class WaitAndNotify {
 private static Object lock = new Object();
 static class ThreadA implements Runnable {
 @Override
 public void run() {
 synchronized (lock) {
 for (int i = 0; i < 5; i++) {
 try {
 System.out.println("ThreadA: " + i);
 lock.notify();
 lock.wait();
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 lock.notify();
 }
 }
 }
 static class ThreadB implements Runnable {
 @Override
 public void run() {
 synchronized (lock) {
 for (int i = 0; i < 5; i++) {
 try {
 System.out.println("ThreadB: " + i);
 lock.notify();
 lock.wait();
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 lock.notify();
 }
 }
 }
 public static void main(String[] args) throws InterruptedException {
 new Thread(new ThreadA()).start();
 Thread.sleep(1000);
 new Thread(new ThreadB()).start();
 }
}
// 输出:
ThreadA: 0
ThreadB: 0
ThreadA: 1
ThreadB: 1
ThreadA: 2
ThreadB: 2
ThreadA: 3
ThreadB: 3
ThreadA: 4
ThreadB: 4

其他通信相关

  1. join()

  2. sleep()

  3. ThreadLocal类

    ​ 有些朋友称ThreadLocal为线程本地变量或线程本地存储。严格来说,ThreadLocal类并不属于多线程间的通信,而是让每个线程有自己”独立“的变量,线程之间互不影响。它为每个线程都创建一个副本,每个线程可以访问自己内部的副本变量。

5.3 信号量

volatile 关键字 : 能够保证内存的可见性,如果用volatile关键字声明了一个变量,在一个线程里面改变了这个变量的值,那其它线程是立马可见更改后的值的。

public class Signal {
 	private static volatile int signal = 0;
 	static class ThreadA implements Runnable {
 	@Override
 	public void run() {
 		while (signal < 5) {
 			if (signal % 2 == 0) {
 				System.out.println("threadA: " + signal);
 				signal++;
 			}
 		}
 	}
 }
 static class ThreadB implements Runnable {
 	@Override
 	public void run() {
 		while (signal < 5) {
 			if (signal % 2 == 1) {
 				System.out.println("threadB: " + signal);
 				signal = signal + 1;
 			}
 		}
 	}
 }
 public static void main(String[] args) throws InterruptedException {
 		new Thread(new ThreadA()).start();
 		Thread.sleep(1000);
 		new Thread(new ThreadB()).start();
 }
}
// 输出:
threadA: 0
threadB: 1
threadA: 2
threadB: 3
threadA: 4

并发编程模型的两个关键问题

在这里插入图片描述
在这里插入图片描述

共享的,为什么在堆中会有内存不可见问题?

这是因为现代计算机为了高效,往往会在高速缓存区中缓存共享变量,因为cpu访问缓存区比访问内存要快得多.

  1. 所有的共享变量都存在主内存中。

  2. 每个线程都保存了一份该线程使用到的共享变量的副本。

  3. 如果线程A与线程B之间要通信的话,必须经历下面2个步骤:

    i. 线程A将本地内存A中更新过的共享变量刷新到主内存中去。

    ii. 线程B到主内存中去读取线程A之前已经更新过的共享变量。

所以,线程A无法直接访问线程B的工作内存,线程间通信必须经过主内存。

JMM (Java memory model)

重排序与happens-before

计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排。

a = b + c;
d = e - f ;

编译器优化重排

​ 编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。

指令并行重排

​ 现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性(即后一个执行的语句无需依赖前面执行的语句的 结果),处理器可以改变语句对应的机器指令的执行顺序。

内存系统重排

​ 由于处理器使用缓存和读写缓存冲区,这使得加载(load)和存储(store)操作看上去可能是在乱序执行,因为三级缓存的存在,导致内 存 与缓存的数据同步存在时间差。

顺序一致性

如果程序是正确同步的,程序的执行将具有顺序一致性。

数据竞争

在一个线程中写一个变量,在另一个线程读同一个变量,并且写和读没有通过同步来排序。

顺序一致性模型

  1. 一个线程中的所有操作必须按照程序的顺序(即Java代码的顺序)来执行。
  2. 不管程序是否同步,所有线程都只能看到一个单一的操作执行顺序。即在顺序一致性模型中,每个操作必须是原子性的,且立刻对所有线程可见。

JMM的具体实现方针是:在不改变(正确同步的)程序执行结果的前提下,尽量为编译期和处理器的优化打开方便之门。

禁止volatile变量与普通变量重排序;

happens-before

  1. 如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。

  2. 两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须要按照happens-before关系指定的顺序来执行。如果重排序之后的执行结果,与按happens-before关系来执行的结果一致,那么JMM也允许这样的重排序。

    如果操作A happens-before操作B,那么操作A在内存上所做的操作对操作B都是可见的,不管它们在不在一个线程。

具体是怎么实现的:

在这里插入图片描述

LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后
续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
    
StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及
后续写入操作执行前,这个屏障会吧Store1强制刷新到内存,保证Store1的
写入操作对其它处理器可见。
    
LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后
续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
    
StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后
续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是
四种屏障中最大的(冲刷写缓冲器,清空无效化队列)。在大多数处理器的
实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能

volatile 的作用:

​ 在保证内存可见性这一点上,volatile有着与锁相同的内存语义,所以可以作为一个“轻量级”的锁来使用。但由于volatile仅仅保证对单个volatile变量的读/写具有原子性,而锁可以保证整个临界区代码的执行具有原子性。所以在功能上,锁比1 volatile更强大;在性能上,volatile更有优势。

6 sycronized 与 锁

首先需要明确的一点是:Java多线程的锁都是基于对象的,Java中的每一个对象都可以作为一个锁。

// 关键字在实例方法上,锁为当前实例
public synchronized void instanceLock() {
 // code
}
// 关键字在静态方法上,锁为当前Class对象
public static synchronized void classLock() {
 // code
}
// 关键字在代码块上,锁为括号里面的对象
public void blockLock() {
 Object o = new Object();
 synchronized (o) {
 // code
 }
}

锁的类型:

  1. 无锁状态

  2. 偏向锁状态

  3. 轻量级锁状态

  4. 重量级锁状态

Java 对象头
在这里插入图片描述

在这里插入图片描述

S: Compare and Swap

比较并设置。用于在硬件层面上提供原子性操作。在 Intel 处理器中,比较并交换通过指令cmpxchg实现。 比较是否和给定的数值一致,如果一致则修改,不一致则不修改。

在CAS中,有这样三个值:
V:要更新的变量(var)
E:预期值(expected)
N:新值(new)
    
public native
boolean compareAndSwapObject(Object o, long offset,Object expected, Object x);
boolean compareAndSwapInt(Object o, long offset,int expected,int x);
boolean compareAndSwapLong(Object o, long offset,long expected,long x);

自旋 : 不断尝试去获取锁,一般用循环来实现。

for(;;){
    if( cas() == true){
        ...
        break;
    }
}
自旋是需要消耗CPU的,如果一直获取不到锁的话,那该线程就一直处在自旋状态,白白浪费CPU资源。解决这个问题最简单的办法就是指定自旋的次数,例如让其循环10次,如果还没获取到锁就进入阻塞状态。

偏向锁

​ 当资源很少被其他线程访问的时候可以使用偏向锁。

​ 偏向锁会偏向于第一个访问锁的线程,如果在接下来的运行过程中,该锁没有被其他的线程访问,则持有偏向锁的线程将永远不需要触发同步。减少资源消耗。

如果发现为true,代表资源无竞争,则无需再走各种加锁/解锁流程。如果为false,代表存在其他线程竞争资源,那么就会走后面的流程。

在这里插入图片描述

轻量级锁

​ JVM会为每个线程在当前线程的栈帧中创建用于存储锁记录的空间,我们称为Displaced Mark Word。如果一个线程获得锁的时候发现是轻量级锁,会把锁的Mark Word复制到自己的Displaced Mark Word里面。

​ 然后线程尝试用CAS将锁的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示Mark Word已经被替换成了其他线程的锁记录,说明在与其它线程竞争锁,当前线程就尝试使用自旋来获取锁。

在这里插入图片描述

重量级锁

​ 重量级锁依赖于操作系统的互斥量(mutex) 实现的,而操作系统中线程间状态的转换需要相对比较长的时间,所以重量级锁效率很低,但被阻塞的线程不会消耗CPU。

CAS实现原子操作的三大问题

ABA问题

​ 在变量前面追加上版本号或者时间戳。

循环时间长开销大

​ 解决思路是让JVM支持处理器提供的pause指令,pause指令能让自旋失败时cpu睡眠一小段时间再继续自旋,从而使得读操作的频

​ 率低很多,为解决内存顺序冲突而导致的CPU流水线重排的代价也会小很多。

只能保证一个共享变量的原子操作

  1. 使用JDK 1.5开始就提供的 AtomicReference 类保证对象之间的原子性,把多个

变量放到一个对象里面进行CAS操作;

  1. 使用锁。锁内的临界区代码可以保证只有当前线程能操作。

线程池原理

ThreadPoolExecutor线程池构造参数

// 五个参数的构造函数
	public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue)
    // 六个参数的构造函数-1
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory)
    // 六个参数的构造函数-2
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler)
    // 七个参数的构造函数
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

int corePoolSize:该线程池中核心线程数最大值

​ 核心线程:线程池中有两类线程,核心线程和非核心线程。核心线程默认情况下会一直存在于线程池中,即使这个核心线程什么都 不干(铁饭碗),而非核心线程如果长时间的闲置,就会被销毁(临时工)。

int maximumPoolSize:该线程池中线程总数最大值 。

​ 该值等于核心线程数量 + 非核心线程数量。

long keepAliveTime:非核心线程闲置超时时长。

​ 非核心线程如果处于闲置状态超过该值,就会被销毁。如果设置allowCoreThreadTimeOut(true),则会也作用于核心线程。

TimeUnit unit:keepAliveTime的单位。

​ TimeUnit是一个枚举类型 ,包括以下属性:

​ NANOSECONDS : 1微毫秒 = 1微秒 / 1000 MICROSECONDS : 1微秒 =

​ 1毫秒 / 1000 MILLISECONDS : 1毫秒 = 1秒 /1000 SECONDS : 秒

​ MINUTES : 分 HOURS : 小时 DAYS : 天

BlockingQueue workQueue:阻塞队列,维护着等待执行的Runnable任务对象。

ThreadFactory threadFactory: 创建线程的工厂

RejectedExecutionHandler handler : 拒绝处理策略

线程池的使用:


threadPoolExecutor.execute(new MyRunnable());


// JDK 1.8 
public void execute(Runnable command) {
 	if (command == null)
 	throw new NullPointerException(); 
 	int c = ctl.get();
 	// 1.当前线程数小于corePoolSize,则调用addWorker创建核心线程执行任务
 	if (workerCountOf(c) < corePoolSize) {
 		if (addWorker(command, true))
 			return;
		c = ctl.get();
 	}
 	// 2.如果不小于corePoolSize,则将任务添加到workQueue队列。
 	if (isRunning(c) && workQueue.offer(command)) {
 		int recheck = ctl.get();
 		// 2.1 如果isRunning返回false(状态检查),则remove这个任务,然后执行拒绝策略。
 		if (! isRunning(recheck) && remove(command))
 			reject(command);
 		// 2.2 线程池处于running状态,但是没有线程,则创建线程
 		else if (workerCountOf(recheck) == 0)
 			addWorker(null, false);
 	}
 	// 3.如果放入workQueue失败,则创建非核心线程执行任务,
	// 如果这时创建非核心线程失败(当前线程总数不小于maximumPoolSize时),就会执行拒绝策略。
 	else if (!addWorker(command, false))
 		reject(command);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值