Java线程学习详解
线程基础
1. 线程的生命周期
1.1 新建状态:
- 使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
1.2 就绪状态:
- 当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
1.3 运行状态:
- 如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
1.4 阻塞状态:
- 如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
- 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
- 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
- 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
1.5 死亡状态:
- 一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
2. 线程的优先级和守护线程
2.1 线程的优先级
- 每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。
- Java 线程的优先级是一个整数,其取值范围是
1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )
。 - 默认情况下,每一个线程都会分配一个优先级
NORM_PRIORITY(5)
。
2.2 守护线程
- Java中有两种线程:用户线程和守护线程。可以通过isDeamon()方法来区别它们:如果返回false,则说明该线程是“用户线程”;否则就是“守护线程”。
- 用户线程一般用户执行用户级任务,而守护线程也就是“后台线程”,一般用来执行后台任务。
- 需要注意的是:JVM在“用户线程”都结束后会退出。
3. 创建线程
3.1 通过实现 Runnable 接口
- 步骤:
- 创建类实现 Runnable 接口
- 实现 run() 方法,线程实际运行的方法
- 实现 start() 方法,里面实例化线程对象(new Thread(this, threadName)),调用线程对象的 start() 方法
代码实现
```java
package com.ljw.thread;public class RunnableDemo {
public static void main(String[] args) { // 测试 RunnableDemo R = new RunnableDemo(); RunnableThread R1 = R.new RunnableThread("thread1"); R1.start(); RunnableThread R2 = R.new RunnableThread("thread2"); R2.start(); } class RunnableThread implements Runnable{ private String threadName; private Thread t; public RunnableThread(String name) { // TODO Auto-generated constructor stub threadName = name; System.out.println("创建线程 "+threadName); } @Override public void run() { System.out.println("正在运行线程:"+threadName); try { for(int i=10;i>0;i--) { System.out.println("线程:"+threadName+" 正在打印:"+i); Thread.sleep(50); } }catch(Exception e) { e.printStackTrace(); } System.out.println("线程:"+threadName+" 正在退出......"); } public void start() { System.out.println("开始线程 "+threadName); if(t == null) { t = new Thread(this, threadName); t.start(); } } }
}
```
3.2 通过继承 Thread 类本身
- 步骤:
- 创建类继承 Thread 类
- 下面与用Runnable接口一样
3.3 通过 Callable 和 Future 创建线程
- 步骤:
- 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
- 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
- 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
- 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。
- Callable接口与Runnable接口的区别:
- Callable中call方法可以有返回值,而Runnable中的run方法没有返回值
代码实现
```java
package com.ljw.thread;import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;public class CallableThreadTest implements Callable {
public static void main(String[] args)
{
CallableThreadTest ctt = new CallableThreadTest();
FutureTask ft = new FutureTask<>(ctt);
for(int i = 0;i < 10;i++)
{
System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);
if(i%2==0)
{
new Thread(ft,"有返回值的线程").start();
}
}
try
{
System.out.println("子线程的返回值:"+ft.get());
} catch (InterruptedException e)
{
e.printStackTrace();
} catch (Exception e)
{
e.printStackTrace();
}} @Override public Integer call() throws Exception { int i = 0; for(;i<10;i++) { System.out.println(Thread.currentThread().getName()+" "+i); } return i; }
}
```
4. synchronized关键字
4.1 概述
- synchronized关键字是为了解决共享资源竞争的问题,共享资源一般是以对象形式存在的内存片段,但也可以是文件、输入/输出端口,或者是打印机。
- 要控制对共享资源的访问,得先把它包装进一个对象。然后把所有要访问的这个资源的方法标记为synchronized。
- 如果某个任务处于一个对标记为synchronized的方法的调用中,那么在这个线程从该方法返回之前,其他所有要调用类中任何标记为synchronized方法的线程都会被阻塞。
4.2 基本原则
- 第一条:当一个线程访问某对象的
synchronized方法
或者synchronized代码块
时,其他线程对该对象的该synchronized方法
或者synchronized代码块
的访问将被阻塞。 - 第二条:当一个线程访问某对象的
synchronized方法
或者synchronized代码块
时,其他线程仍然可以访问该对象的非同步代码块。 - 第三条:当一个线程访问某对象的
synchronized方法
或者synchronized代码块
时,其他线程对该对象的其他的synchronized方法
或者synchronized代码块
的访问将被阻塞。
4.3 实例
- 两个相似的例子
- 实例1:实现接口Runnable
package com.ljw.thread; public class RunnableTest { public static void main(String[] args) { class MyRunnable implements Runnable{ @Override public void run() { synchronized (this) { for(int i=0;i<5;i++) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 正在进行打印 " +i); } } } } Runnable runnable = new MyRunnable(); Thread t1 = new Thread(runnable,"t1"); Thread t2 = new Thread(runnable,"t2"); t1.start(); t2.start(); } }
运行结果:
t1 正在进行打印 0 t1 正在进行打印 1 t1 正在进行打印 2 t1 正在进行打印 3 t1 正在进行打印 4 t2 正在进行打印 0 t2 正在进行打印 1 t2 正在进行打印 2 t2 正在进行打印 3 t2 正在进行打印 4
结果说明:run()方法中存在synchronized(this)代码块,而且t1和t2都是基于MyRunnable这个Runnable对象创建的线程。这就意味着,我们可以将synchronized(this)中的this看做是MyRunnable这个Runnable对象;因此,线程t1和t2共享“MyRunable对象的同步锁”。所以,当一个线程运行的时候,另外一个线程必须等待正在运行的线程释放MyRunnable的同步锁之后才能运行。- 实例2:继承Thread类
public class ThreadTest { public static void main(String[] args) { class MyThread extends Thread{ public MyThread(String name){ super(name); } @Override public void run() { synchronized(this){ for(int i=0;i<10;i++){ try { Thread.sleep(100); System.out.println(Thread.currentThread().getName()+" 正在进行打印 "+i); } catch (InterruptedException e) { e.printStackTrace(); } } } } } Thread t1 = new MyThread("t1"); Thread t2 = new MyThread("t2"); t1.start(); t2.start(); } }
运行结果:
t2 正在进行打印 0 t1 正在进行打印 0 t2 正在进行打印 1 t1 正在进行打印 1 t1 正在进行打印 2 t2 正在进行打印 2 t2 正在进行打印 3 t1 正在进行打印 3 t1 正在进行打印 4 t2 正在进行打印 4
对比结果:发现实例1的两个线程是一个结束后,另一个才运行,实例2的是交叉运行,在run()方法中都有synchronized(this),为什么结果不一样?分析:synchronized(this)中的this是指当前对象,即synchronized(this)所在类对应的当前对象。它的作用是获取获取当前对象的同步锁。对于实例2中的synchronized(this)中的this代表的是MyThread对象,t1和t2是两个不同的MyThread对象,因此t1和t2在执行synchronized(this)时获取的是不同对象的同步锁。对于实例1来说,synchronized(this)中的this代表的时候MyRunnable对象,t1和t2是共同一个MyRunnable对象,因此,一个线程获取了对象的同步锁,会造成另一个线程的等待。
4.4 synchronized方法和synchronized代码块
4.4.1 概述
synchronized方法
是用synchronized修饰方法,这是一种粗粒度锁;这个同步方法(非static方法)无需显式指定同步监视器,同步方法的同步监视器是this,也就是调用该方法的对象。synchronized代码块
是用synchronized修饰代码块,这是一种细粒度锁。线程开始执行同步代码块之前,必须先获得对同步监视器的锁定,任何时候只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对同步监视器的锁定。虽然Java允许使用任何对象作为同步监视器,但同步监视器的目的就是为了阻止两个线程对同一个共享资源进行并发访问,因此通常推荐使用可能被并发访问的共享资源充当同步监视器。
4.4.2 实例
public class SnchronizedTest {
public static void main(String[] args) {
class Demo {
// synchronized方法
public synchronized void synMethod() {
for(int i=0; i<1000000; i++)
;
}
public void synBlock() {
// synchronized代码块
synchronized( this ) {
for(int i=0; i<1000000; i++)
;
}
}
}
}
}
4.5 实例锁和全局锁
4.5.1 概述
- 实例锁:锁在某个实例对象上。如果该类是单例,那么该锁也是具有全局锁的概念。实例锁对应的就是synchronized关键字。
- 全局锁:该锁针对的是类,无论实例多少个对象,那么线程都共享该锁。全局锁对应的就是static synchronized(或者是锁在该类的class或者classloader对象上)。
4.5.2 实例
pulbic class Something {
public synchronized void isSyncA(){}
public synchronized void isSyncB(){}
public static synchronized void cSyncA(){}
public static synchronized void cSyncB(){}
}
假设,类Something有两个实例(对象)分别为x和y。分析下面4组表达式获取锁的情况。
- x.isSyncA()与x.isSyncB()
- 不能同时访问,因为都是访问对象x的同步锁
- x.isSyncA()与y.isSyncA()
- 可以同时访问,因为是访问不同对象(x和y)的同步锁
- x.cSyncA()与y.cSyncB()
- 不能同时访问,因为两个方法是静态的,相当于用Something.cSyncA()和Something.cSyncB()访问,是相同的对象
- x.isSyncA()与Something.cSyncA()
- 可以同时访问,因为访问不同对象
5. Volatile 关键字
5.1 Volatile原理
- Java语言提供了一种稍微同步机制,即volatile变量,用来确保将变量的更新操作通知其他线程
- 在访问volatile变量是不会执行加锁操作,因此也就不会重新执行线程阻塞,volatile变量是一种比synchronized关键字轻量级的同步机制
- 当一个变量被volatile修饰后,不但具有可见性,而且还禁止指令重排。volatile的读性能消耗与普通变量几乎相同,但是写操作就慢一些,因为它要保证本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行
6. 线程等待和唤醒
6.1 常用方法
- 在Object.java中,定义了wait(),notify()和notifyAll()等接口
- wait()方法的作用是让当前线程进入阻塞状态,同时会释放当前对象所持有的锁
- notify()唤醒当前对象上的等待线程,notifyAll()则是唤醒所有的线程
6.2 实例
package com.ljw.thread;
public class WaitDemo {
public static void main(String[] args) {
class ThreadTest extends Thread{
@Override
public void run() {
synchronized (this) {
System.out.println("开始运行线程 "+Thread.currentThread().getName());
System.out.println("唤醒线程notify()");
notify();
}
}
}
ThreadTest thread1 = new ThreadTest();
thread1.start();
synchronized (thread1) {
try {
System.out.println("主线程进入阻塞,释放thread对象的同步锁,wait()");
thread1.wait(); // wait()是让当前线程进入阻塞状态,wait()是在主线程中执行,
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("主线程继续进行");
}
}
7. 线程让步和休眠
7.1 线程让步
7.1.1 概述
- 在Java线程中,yield()方法的作用是让步,它能让当前线程由“运行状态”进入到“就绪状态”,可能让其它同级别的线程获得执行权,但不一定,可能它自己再次由“就绪状态”进入到“运行状态”
7.1.2 实例
package com.ljw.thread;
public class YieldTest {
public static void main(String[] args) {
class ThreadA extends Thread{
public ThreadA(String name){
super(name);
}
@Override
public synchronized void run() {
for(int i=0;i<5;i++){
System.out.println(" "+this.getName()+" "+i);
if(i%2 == 0){
Thread.yield();
}
}
}
}
ThreadA t1 = new ThreadA("t1");
ThreadA t2 = new ThreadA("t2");
t1.start();
t2.start();
}
}
运行结果(不唯一):
t1 0
t2 0
t1 1
t1 2
t2 1
t1 3
t2 2
t1 4
t2 3
t2 4
结果说明:
线程t1在能被2整除的时候,并不一定切换到线程2。这表明,yield()方法虽然可以让线程由“运行状态”进入到“就绪状态”;但是,它不一定会让其他线程获取CPU执行权(其他线程进入到“运行状态”)。即时这个“其他线程”与当前调用yield()的线程具有相同的优先级。
7.1.3 yield()和wait()比较
- wait()的作用是让当前线程由“运行状态”进入“阻塞状态”,而yield()是让当前线程由“运行状态”进入“就绪状态”
- wait()是会让线程释放它所持有的对象的同步锁,而yield()方法不会释放对象的同步锁。
7.2 线程休眠
7.2.1 概述
- sleep()方法定义在Thread类中,sleep()的作用是让当前线程休眠,即当前线程会从“远程状态”进入到“休眠(阻塞)状态”
- sleep()会指定休眠时间,线程休眠的时间会大于/等于该休眠时间
- 在线程重新被唤醒时,它会由“阻塞状态”变成“就绪状态”,从而等待CPU的调度执行。
7.2.2 sleep() 和 wait()的比较
- wait()的作用是让当前的线程由“运行状态”进入到“等待(阻塞)状态”的同时,也会释放同步锁- 但是sleep()的作用是让当前线程由“运行状态”进入到“休眠(阻塞)”状态,但不会释放锁。
8. 加入一个线程
8.1 概述
- 在一个线程T上调用另一个线程t的 join() 方法,相当于在T中加入线程t,要等t结束后(即t.isAlive为假), join() 后面的代码块才会执行。
- 可以在调用jion()时带上一个超时参数(单位可以是毫秒,或者纳秒),这样如果目标线程在这段时间到期时还没有结束的话,join()方法总能返回
9. 终止一个线程
9.1 概述
- interrupt()并不会终止处于“运行状态”的线程,它会将线程的中断标记设为true。
- 综合线程处于“阻塞状态”和“运行状态”的终止方式,比较通用的终止线程的形式如下:
@Override
public void run() {
try {
// 1. isInterrupted()保证,只要中断标记为true就终止线程。
while (!isInterrupted()) {
// 执行任务...
}
} catch (InterruptedException ie) {
// 2. InterruptedException异常保证,当InterruptedException异常产生时,线程被终止。
}
}
9.2 实例
public class InterruptBlock {
/**
* @param args
*/
public static void main(String[] args) {
class MyThread extends Thread{
public MyThread(String name){
super(name);
}
@Override
public void run() {
try {
int i=0;
while(!isInterrupted()){
Thread.sleep(100);
i++;
System.out.println(Thread.currentThread().getName()+ " ("+this.getState()+") loop "+i);
}
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(Thread.currentThread().getName()+ " ("+this.getState()+") catch InterruptedExecption");
}
}
}
try {
//新建
Thread t1 = new MyThread("t1");
System.out.println(t1.getName()+" ("+t1.getState()+" ) is new.");
System.out.println("luo1:"+t1.isInterrupted());
//启动
t1.start();
System.out.println(t1.getName()+" ("+t1.getState()+" ) is started.");
System.out.println("luo2:"+t1.isInterrupted());
//主线程休眠300ms,然后主线程给t1发“中断”指令
Thread.sleep(300);
t1.interrupt();
System.out.println("luo3:"+t1.isInterrupted());
System.out.println(t1.getName()+" ("+t1.getState()+" ) is interrupted.");
//主线程休眠300ms,然后查看t1的状态
Thread.sleep(300);
System.out.println(t1.getName()+" ("+t1.getState()+" ) is interrupted now .");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
运行结果:
t1 (NEW ) is new.
luo1:false
t1 (RUNNABLE ) is started.
luo2:false
t1 (RUNNABLE) loop 1
t1 (RUNNABLE) loop 2
luo3:true
t1 (RUNNABLE) loop 3
t1 (RUNNABLE ) is interrupted.
t1 (TERMINATED ) is interrupted now .
9.3 interrupt()和isInterrupted()的区别
- interrupt()和isInterrupted()都能够用于检测对象的“中断标记”。区别是:interrupt()除了返回中断标记之外,它还会清除中断标记(即将中断标记设为false);而isInterrupted()仅仅返回中断标记。
线程进阶
1. 线程池
- 示例
package com.ljw.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo {
public static void main(String[] args) { // 主线程
// 线程池 ---> Executors工具类(工厂类)
/*
* newFixedThreadPool(int threadCount) 创建固定数量的线程池
* newCachedThreadPool() 创建动态数量的线程池
*/
ExecutorService es = Executors.newFixedThreadPool(3);
Runnable task = new MyTask();
// 提交任务
es.submit(task);
es.submit(task);
es.shutdown(); // 关闭线程池,则表示不在接收新任务,不代表正在线程池的任务会停掉
}
}
class MyTask implements Runnable{
@Override
public void run() {
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+" MyTask "+i);
}
}
}
2. 线程安全与锁
2.1 重入锁和读写锁
package com.ljw.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
/**
* ReentrantLock类,重入锁:Lock接口的实现类,与synchronized一样具有互斥锁功能 lock() 和 unlock()
* ReentrantReadWriteLock类,读写锁:一种支持一写多读的同步锁,读写分离,分别分配读锁和写锁,在读操作远远高于写操作的环境中可以提高效率
* 互斥规则:
* 写--写:互斥,阻塞
* 读--写:互斥,阻塞
* 读--读:不互斥,不阻塞
*
*/
public class LockDemo {
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(20);
Student s = new Student();
// ReentrantLock rLock = new ReentrantLock(); // 用ReenTrantLock加锁运行时间20008ms
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); // 用读写锁分别对读写任务加锁运行时间3003ms
ReadLock rl = rwLock.readLock();
WriteLock wl = rwLock.writeLock();
// 写任务
Callable<Object> writeTask = new Callable<Object>() {
@Override
public Object call() throws Exception {
// rLock.lock();
wl.lock();
try {
Thread.sleep(1000);
s.setValue(100);
}finally {
// rLock.unlock();
wl.unlock();
}
return null;
}};
// 读任务
Callable<Object> readTask = new Callable<Object>() {
@Override
public Object call() throws Exception {
// rLock.lock();
rl.lock();
try {
Thread.sleep(1000);
s.getValue();
}finally {
// rLock.unlock();
rl.unlock();
}
return null;
}};
// 开始时间
long start = System.currentTimeMillis();
for(int i=0;i<2;i++) { // 写任务执行 2 次
es.submit(writeTask);
}
for(int i=0;i<18;i++) { // 读任务执行 18 次
es.submit(readTask);
}
es.shutdown(); // 停止线程池,不在接受新的任务,将现有任务全部执行完毕
while(true) {
if(es.isTerminated()) { // 当线程池中所有任务执行完毕,返回true,否则返回false
break;
}
}
// 执行到这里,说明线程池中所有任务都执行完毕,可以计算结束时间
System.out.println(System.currentTimeMillis()-start);
}
}
class Student {
private int value;
//读
public int getValue() {
return value;
}
//写
public void setValue(int value) {
this.value = value;
}
}
2.2 线程安全
2.2.1 Collections工具类
- Collections工具类中提供了多个可以获得线程安全集合的方法
static <T> Collection<T> synchronizedCollection(Collection<T> c)
//返回由指定集合支持的同步(线程安全)集合。
static <T> List<T> synchronizedList(List<T> list)
//返回由指定列表支持的同步(线程安全)列表。
static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)
//返回由指定地图支持的同步(线程安全)映射。
static <K,V> NavigableMap<K,V> synchronizedNavigableMap(NavigableMap<K,V> m)
//返回由指定的可导航地图支持的同步(线程安全)可导航地图。
static <T> NavigableSet<T> synchronizedNavigableSet(NavigableSet<T> s)
//返回由指定的可导航集支持的同步(线程安全)可导航集。
static <T> Set<T> synchronizedSet(Set<T> s)
//返回由指定集合支持的同步(线程安全)集。
static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m)
//返回由指定的排序映射支持的同步(线程安全)排序映射。
static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s)
//返回由指定的排序集支持的同步(线程安全)排序集。
2.2.2 CopyOnWriteArrayList
- 线程安全的ArrayList
- 读写分离,写加锁,读没锁,读写之间不互斥
- 使用方法与ArrayList无异
2.2.3 CopyOnWriteArraySet
- 基于 CopyOnWriteArrayList
2.2.4 ConcurrentHashMap
- 初始容量默认为16段(Segment),采用分段锁设计
- 不对整个Map加锁,只对每个Segment加锁
- 当多个对象访问同个Segment才会互斥
Java线程学习详解的更多相关文章
- Java线程池详解(二)
一.前言 在总结了线程池的一些原理及实现细节之后,产出了一篇文章:Java线程池详解(一),后面的(一)是在本文出现之后加上的,而本文就成了(二).因为在写完第一篇关于java线程池的文章之后,越发觉 ...
- java 线程状态 详解
线程被创建后,有一个生命周期,下图是线程的生命周期详解. java api java.lang.Thread.State 这个枚举中给出了六种线程状态,分别是: 线程状态 导致状态发生条件 NEW(新 ...
- Java线程池详解
一.线程池初探 所谓线程池,就是将多个线程放在一个池子里面(所谓池化技术),然后需要线程的时候不是创建一个线程,而是从线程池里面获取一个可用的线程,然后执行我们的任务.线程池的关键在于它为我们管理了多 ...
- Java线程池详解(一)
一.线程池初探 所谓线程池,就是将多个线程放在一个池子里面(所谓池化技术),然后需要线程的时候不是创建一个线程,而是从线程池里面获取一个可用的线程,然后执行我们的任务.线程池的关键在于它为我们管理了多 ...
- 【java线程系列】java线程系列之java线程池详解
一线程池的概念及为何需要线程池: 我们知道当我们自己创建一个线程时如果该线程执行完任务后就进入死亡状态,这样如果我们需要在次使用一个线程时得重新创建一个线程,但是线程的创建是要付出一定的代价的,如果在 ...
- Java线程池详解,看这篇就够了!
构造一个线程池为什么需要几个参数?如果避免线程池出现OOM?Runnable和Callable的区别是什么?本文将对这些问题一一解答,同时还将给出使用线程池的常见场景和代码片段. 基础知识 Execu ...
- Java线程池 详解(图解)
来源:www.jianshu.com/p/098819be088c 拓展: 手动创建 new ThreadPoolExecutor 的使用: https://segmentfault.com/a/11 ...
- Java 线程池详解
Executors创建线程池 Java中创建线程池很简单,只需要调用Executors中相应的便捷方法即可,比如Executors.newFixedThreadPool(int nThreads),但 ...
- Java线程池详解及实例
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/aa1215018028/article/ ...
随机推荐
- MySQL InnoDB如何保证事务特性
如果有人问你"数据库事务有哪些特性"?你可能会很快回答出原子性.一致性.隔离性.持久性即ACID特性.那么你知道InnoDB如何保证这些事务特性的吗?如果知道的话这篇文章就可以直接 ...
- Java 学习笔记之 异常法停止线程
异常法停止线程: public class RealInterruptThread extends Thread { @Override public void run() { try { for ( ...
- Pycharm 快捷键大全 2019.2.3
在Pycharm中打开Help->Keymap Reference可查看默认快捷键帮助文档,文档为PDF格式,位于安装路径的help文件夹中,包含MAC操作系统适用的帮助文档. 下图为2019. ...
- 网页布局——grid语法属性详解
grid目前兼容性目前还可以,主流浏览器对它的支持力度很大,ie9,10宣布它未来不久会对它有很好的支持,目前则需要使用过时的语法.我相信不久的将来grid将成为每一个前端工作人员必备的布局技能. 属 ...
- spring源码分析系列5:ApplicationContext的初始化与Bean生命周期
回顾Bean与BeanDefinition的关系. BeanFactory容器. ApplicationContext上下文. 首先总结下: 开发人员定义Bean信息:分为XML形式定义:注解式定义 ...
- Tomcat基本知识(一)
顶层架构先上一张Tomcat的顶层结构图(图A),如下: Tomcat中最顶层的容器是Server,代表着整个服务器,从上图中可以看出,一个Server可以包含至少一个Service,用于具体提供服务 ...
- MySQL基础(三)多表查询(各种join连接详解)
Mysql 多表查询详解 一.前言 二.示例 三.注意事项 一.前言 上篇讲到Mysql中关键字执行的顺序,只涉及了一张表:实际应用大部分情况下,查询语句都会涉及到多张表格 : 1.1 多表连接有哪些 ...
- django html母版
08.12自我总结 django母版 一.母版写的格式 在需要导入的地方写 {% block 名字定义 %} {% endblock %} 二.导入模板 {% extends 'FUCK.html' ...
- 让你如“老”绅士般编写 Python 命令行工具的开源项目:docopt
作者:HelloGitHub-Prodesire HelloGitHub 的<讲解开源项目>系列,项目地址:https://github.com/HelloGitHub-Team/Arti ...
- 自学web前端达到什么水平,才能满足求职的标准?
大多数野生程序员最棘手的问题就是如何依靠技术解决温饱,通俗来讲就是技术折现的问题. 如果是单纯出于兴趣,或者只是为了突击某一阶段或者某一项目技术壁垒,不跟就业挂钩的自学倒也是无关痛痒.但是当上岗成为自 ...