Java 多线程

1 进程和线程

进程是一个正在执行的程序,一个程序可以同时执行多个任务(线程)。
进程独占内从空间,保持各自的运行状态,相互之间不会干扰,进程是在并发执行程序过程中资源分配和管理的最小单位(资源分配的最小单位)。每个进程都有独立的地址空间,每启动一个进程,系统就会分配地址空间。
通常一个任务被称作一个线程,线程有时候会被称为轻量级的进程。它是程序执行的最小单位,一个进程可以有多个线程,多个线程之间共享进程的地址空间以及一些进程级别的其他资源,但是各个线程拥有独立的栈空间。
在这里插入图片描述
进程和线程的区别和联系?

  1. 进程是资源分配的最小单位,线程是程序执行(CPU调度)的最小单位。
  2. 进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。
    线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。
  3. 线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。
  4. 多进程程序更健壮,多线程程序只要有一个线程死掉,那么对于其共享资源的其他线程也会产生影响,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。

列举进程和线程的使用场景?

  1. 在程序中,如果需要频繁创建和销毁,则使用线程。因为进程创建和销毁开销很大(需要不停的分配资源),但是线程频繁的调用只是改变CPU的执行,开销小。
  2. 如果需要程序更加的稳定安全时,可以选择进程。如果追求速度,就选择线程。

2 并发和并行

并发:多个线程操作一个资源,不是同时执行,而是交替执行,单核cpu,只不过因为cpu的时间片很短,速度太快,看起来像同时执行
并行:真正的同时进行,多核cpu,每一个线程都可以使用一个单独的cpu资源运行。
在这里插入图片描述

QPS:每秒能够响应的请求数。
并发用户数:系统可以承载的最大用户量。
平均响应时间:并发数/平均响应时间=QPS。
吞吐量:单位时间内处理的请求数。

互联网系统架构中,如何提高系统的并发能力?
垂直扩展(硬件)

  1. 提升单机的处理能力
  2. 增强单机的硬件性能:增加CPU的核数、内存升级、磁盘扩容
  3. 提升系统的架构能力:使用Cache来提高效率

水平扩展(软件:集群、分布式)

  1. 集群:多个人做同一事(同时多顾几个厨师同时炒菜)
  2. 分布式:一个复杂的事情,拆分成几个简单的步骤,分别找不同的人去完成。
    1、站点层扩容:通过Nginx反向代理,实现高并发的系统,将服务部署在多个服务器上
    2、服务层扩容:通过RPC框架实现远程调用:Dubbo,Spring Clodud,将业务逻辑分拆成不同的RPC Client,Clident完成各自的不同的业务,如果并发量比较大,新增加RPC Client。
  3. 数据层扩容:一台数据库拆分成多态,分库分表,主从复制,读写分离。

3 创建线程

3.1 继承Thread类

重写run()

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

public class demo1 {
	public static void main(String[] args) {
		Thread thread = new MyThread();
		thread.start();
	}
}

3.2 继承Runnable接口

重写run()

class MyRunnable implements Runnable{
	@Override
	public void run() {
		System.out.println("implements Runnable to get a thread!");
	}
}

public class demo1 {
	public static void main(String[] args) {
		Thread thread1 = new Thread(new MyRunnable());
		thread1.start();
	}
}

3.3 匿名线程

new Thread("watchTV"){
	@Override
	public void run(){
		System.out.println("watchtv");
	}
}.start();

new Thread("eating"){
	@Override
	public void run(){
		System.out.println("eating");
	}
}.start();

3.4 继承Callable接口

重写call()方法

class MyCallable<T> implements Callable<T>{

	@Override
	public T call() throws Exception {
		return (T)"123";
	}
}
public class demo1 {
	public static void main(String[] args) {
		Callable<String> myCallable = new MyCallable<>();
		FutureTask<String> task = new FutureTask<>(myCallable);
		Thread thread = new Thread(task);
		thread.start();
		try {
			System.out.println(task.get());
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}
	}
}

注意:
start方法本身是用来启动一个线程的,并将其添加到一个线程组里,此时线程获取cpu资源后就会执行所定义的run方法。run方法本身是一个普通的方法,并不会启动新的线程。

Callable和Runnable的区别?

  1. Callable接口实现的是call方法,Runnable接口实现的是run方法。
  2. Callable的任务执行后有返回值,Runnable没有返回值。
  3. call()方法会抛出异常,run方法不能抛出异常。

4 守护线程

为了让进程退出后线程也能自动停止,引入了守护线程的概念。
守护线程能结束生命周期,但非守护线程不可以。

非守护线程

public class demo2 {
	public static void main(String[] args) {
		Thread thread = new Thread(){
			@Override
			public void run(){
				try {
					while(true){
						TimeUnit.SECONDS.sleep(1);
					}
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		};
		thread.start();
		try {
			TimeUnit.MILLISECONDS.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("main thread finished");
	}
}

结果:进程结束后子线程无法结束
在这里插入图片描述

设置子线程为守护线程

public class demo2 {
	public static void main(String[] args) {
		Thread thread = new Thread(){
			@Override
			public void run(){
				try {
					while(true){
						TimeUnit.SECONDS.sleep(1);
					}
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		};
		thread.setDaemon(true);// *********************
		thread.start();
		try {
			TimeUnit.MILLISECONDS.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("main thread finished");
	}
}

结果:进程结束,子线程也跟着结束了
在这里插入图片描述

5 线程的生命周期

NEW(新建)状态
new Thread()创建线程对象。

RUNNABLE(就绪)状态
线程对象此时调用start方法,此时在JVM进程中创建了一个线程,线程并不是一经创建就直接得到执行,需要等到操作系统的其他资源,比如:处理器。

BLOCKED(阻塞)状态
等到一个监视器锁进入到同步代码块或者同步方法中,代码块/方法某一个时刻只允许一个线程去执行,其他线程只能等待,这种情况下等待的线程会从RUNNABLE状态转换到BLOCKED状态Objcet.wait()。

WAITIMNG(等待)状态
调用Object.wait()/join()/LockSupport.park()等方法,此时线程从RUNNABLE转换到WAITING状态。

TIME_WAITING(睡眠)状态
调用带超时参数的THread.sleep(long millis)/Object.wait(long timeout)/join(long milles)/LockSupport.parkNanos()/LockSupport.parkUntil等方法都会使得当前线程进入到TIMED_WAITING状态。

TERMINATED(终止)状态
是线程的最终状态,有如下三种情况会进入到终止状态

  1. 线程正常运行结束
  2. 线程运行出错
  3. JVM crash

在这里插入图片描述

6 线程常用方法

start()
用来启动一个线程 将其添加一个线程组当中 此时线程就会处于Runnable就绪状态

Thread.sleep()
sleep方法使得当前按线程指定毫秒级的休眠,暂停执行,不会放弃monitor锁的使用权
Thread A:monitor lock sleep
Thread B:期望获取monitor lock

jdk1.5之后,引入枚举类型TimeUnit,对sleep方法对其进行了封装,省去了时间单位换算的步骤。

  1. TimeUnit.HOURS.sleep(3);
  2. TimeUnit.MINUTES.sleep(27);
  3. TimeUnit.SECONDS.sleep(8);

yield()
yield属于启发式的方法。
线程A.yield(),会提醒调度器线程A愿意放弃本次的cpu资源,如果cpu资源不紧张,处理器有可能会忽略这种提示。

yield()和sleep()的区别
sleep()不会占用cpu资源,而yield会被其他线程占用cpu资源。

join()
含义:thread B中调用threadA.join(),此时thread B进入到等待状态,
直到当前threadA结束自己的生命周期或者达到join方法的超时时间。

先输出线程1,再输出线程2,最后输出主线程

public class demo3 {
	public static void main(String[] args) {
		Thread thread1 = new Thread(){
			@Override
			public void run() {
				System.out.print("线程1:");
				for(int i = 0;i<5;i++){
					System.out.print(i+" ");
				}
				System.out.println();
			}

		};

		Thread thread2 = new Thread(){
			@Override
			public void run() {
				System.out.print("线程2:");
				for(int i = 5;i<10;i++){
					System.out.print(i+" ");
				}
				System.out.println();
			}
		};

		try {
			thread1.start();
			thread1.join();
			thread2.start();
			thread2.join();
			System.out.print("主线程:");
			for(int i = 10;i<15;i++){
				System.out.print(i+" ");
			}
			System.out.println();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

结果
在这里插入图片描述

实现线程中断的方法

  1. interrupt() 将java线程当中的中断状态位置为true
    thread A : sleep()/join()/wait throw InterruptedException 可中断方法
    以上方法都会使得当前进入阻塞状态,另外一个线程调用被阻塞线程的interrupt方法会
    打断当前的这种阻塞状态,抛出一个InterruptedException的异常,这样的方法称之为
    可中断方法。 并不是结束当前被阻塞线程的生命周期,只是打断了当前线程的阻塞状态。
    thread B : thread A对象.interrupt()
  2. isInterrupted() 判断中断状态位是否位true
public class demo4 {
	public static void main(String[] args) {
		Thread thread = new Thread(){
			@Override
			public void run() {
				while(true){
					try {
						TimeUnit.SECONDS.sleep(10);
					} catch (InterruptedException e) {
						System.out.println("thread被打断了!");
					}
				}
			}
		};
		thread.setDaemon(true);
		thread.start();
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("打断前:"+thread.isInterrupted());
		thread.interrupt();
		System.out.println("打断后:"+thread.isInterrupted());
	}
}

在这里插入图片描述

  1. interrupted() 判断中断状态位是否为true
    区别在于interrupted方法调用之后会擦除掉线程的interrupt标识
public class demo5 {
	public static void main(String[] args) {
		Thread thread = new Thread(){
			@Override
			public void run() {
				while(true){
					System.out.println(Thread.interrupted());
				}
			}
		};
		thread.setDaemon(true);
		thread.start();
		try {
			TimeUnit.MILLISECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		thread.interrupt();
	}
}

在这里插入图片描述

wait/notify/notifyAll
wait调用该synchornized同步代码块或方法当中,使得当前线程进入阻塞
notify/notifyAll唤醒当前的阻塞
Object类中的方法

7 线程优先级

每个线程都有优先级,优先级默认为5,优先级为1~10。
Java线程优先级继承于创建它的线程。

setPriority(int priority)
注意:优先级只能增加抢到cpu执行权的概率。

getPriority()用于获取线程优先级。

ThreadGroup用于对于一批线程进行管理

8 线程同步

场景:index = 0,为共享资源,多个线程同时执行index = index+1操作并输出,到50停止。
该执行语句会有三个步骤:取到index;+1;打印。在每个步骤的间隙产生进程切换时会产生如下问题:
1)某个数字会有重复
线程A:先取到index=0,cpu将执行权利交给了线程B。
线程B:index=index+1=0+1=1,输出1,cpu将执行权利交给了线程A。
线程A:+1=1,输出1。
2)某个数字被略过
线程A:index=index+1=0+1=1,cpu将执行权利交给了线程B
线程B:index=index+1=1+1=2,输出2,线程A再也无法获得当前cpu的使用权。
3)数字有可能超过最大值
线程A:取index=49,cpu将执行权利交给了线程B。
线程B:index=index+1=49+1=50,输出50。
线程A:再次去执行 index=50+1=51,超过范围。

显然多个线程同时操作共享资源,出现了数据不一致的问题。因为我们不清楚每一个线程什么时候开始执行什么时候执行结束,也就无法控制当前线程的最终结果。

8.1 并发编程的三大特性

8.1.1 原子性

原子操作是不可分割的操作,一个原子操作不会被其他线程打断,所以不需要同步。
int i = 10属于原子操作。i++不是一个原子操作。
多个原子操作合起来则不是一个原子操作,这时就需要进行同步。

8.1.2 可见性

当一个线程对共享变量进行了修改,那么其他线程可以立即看到修改后的最新值。

8.1.3 有序性

Java编译器会在运行期会优化代码的执行顺序,导致了代码的执行顺序未必就是开发者编写代码时的顺序。

临界资源:同一时刻只允许一个线程访问的资源。

临界区:访问临界资源的代码段。

8.2 synchronized关键字

防止线程干扰和内存一致性错误。

  1. 同步方法
public synchornized void sync(){
    //表示要访问这个成员方法必须获取当前方法所在类的this引用的锁
}
  1. 同步代码块
public final Object obj = new Object();
public void sync(){
    synchornized(obj){
        //需要保证独占性的资源
    }
}

8.2.1 Demo

练习:实现两个线程,线程A输出5,4,3,2,1,之后线程B再次输出5,4,3,2,1。

public class SynchornizedTest implements Runnable{
	@Override
	public synchronized void run() {
		for(int i = 5;i>=1;i--){
			System.out.print(i+"  ");
		}
		System.out.println();
	}

	public static void main(String[] args) {
		Runnable runnable = new SynchornizedTest();
		new Thread(runnable).start();
		new Thread(runnable).start();
	}
}

8.2.2 底层原理

Java对象在内存中的布局分为3个部分:对象头,实例数据,和对齐填充。
在这里插入图片描述

对象头

对象头的结构如下:

虚拟机位数头对象结构说明
32/64bitMark Word默认存储对象的hashCode、分代年龄、锁类型、所标志位等信息
32/64bitClass Metadata类型指针指向对象的类元数据,JVM通过这个指针确定对该对象是哪个类型的数据
32/64bitArray Length数组长度(如果当前对象为数组)

Mark Word 被设计为非固定的数据结构,以便在极小的空间内存储更多的信息,它会根据对象的状态复用自己的存储空间。其结构如下:
在这里插入图片描述
monitor:
每个Java对象天生就自带了一把看不见的锁,它可以视为是一种同步工具或者是一种同步机制。monitor是线程私有的数据结构,每一个线程都有一个可用monitor 列表,同时还有一个全局的可用列表,如上面所说每一个被锁住的对象都会持有一个monitor。monitor结构如下:

说明
Owner初始时为NULL表示当前没有任何线程拥有该monitor record,当线程成功拥有该锁后保存线程唯一标识,当锁被释放时又设置为NULL。
EntryQ关联一个系统互斥锁(semaphore),阻塞所有试图锁住monitor record而失败的线程。
RcThis表示blocked或waiting在该monitor record上的所有线程的个数
Nest用来实现重入锁的计数
HashCode保存从对象头拷贝过来的HashCode值(可能还包含GC age)。
Candidate用来避免不必要的阻塞或等待线程唤醒,因为每一次只有一个线程能够成功拥有锁,如果每次前一个释放锁的线程唤醒所有正在阻塞或等待的线程,会引起不必要的上下文切换(从阻塞到就绪然后因为竞争锁失败又被阻塞)从而导致性能严重下降。Candidate只有两种可能的值,0表示没有需要唤醒的线程,1表示要唤醒一个继任线程来竞争锁。
同步代码块

monitorenter
每一个对象都跟一个monitor相关联,一个monitor的lock在某一个时刻只能够被一个线程锁获得,在一个线程中想要获取monitor的所有权,接下里有如下几件事情发生:

  1. 如果montior的计数器为0,则意味者当前monitor的lock还没有被获得,某个线程获得之后对该计数器加一,该线程就是这个monitor的所有者了。
  2. 如果当前这个线程已经获取这把monitor lock,再次获取导致monitor计数器再次+1。
  3. 如果monitor已经被其他线程所拥有,则当前线程会被陷入阻塞状态直到monitor的计数器变为0,再次去尝试获取对monitor的所有权。

monitorexit
释放对monitor所有权,即对monitor的计数器-1,当计数器结果为0,那就意味着当前线程不再拥有对monitor的使用锁。

同步方法

class文件中静态常量池ACC_SYNCHRONIZED,表示当前的方法是同步方法。
synchronized可重入锁
可重入锁:同一个线程重复请求自己持有的锁对象,可以请求成功而不会发生死锁

8.3 锁升级

在jdk1.6之后引入了偏向锁和轻量级锁。简单来说,一个线程执行时,是偏向锁。两个线程争抢锁时,是轻量锁。更多线程争抢锁时,锁升级为重量级锁。

8.3.1 偏向锁

为什么要引入偏向锁?
在大多数情况下,锁之间不仅不存在竞争,而且总是会由同一线程多次获得,所以引入了偏向锁。

当一个线程获得了锁,锁就进入偏向模式。

CAS(Compare And Swap):改变mark word中的线程id,是一个原子操作。
其有三个操作数:

  • 需要读写的内存位置(V)
  • 进行比较的预期值(A)
  • 将写入的新值(B)

步骤:判断A和V是否一致,如果一致,将B写到V的位置。
如果CAS操作成功,那么说明当前线程成功获取偏向锁。如果失败,那么说明有其他线程抢先获取了这把锁,即存在线程竞争,此时,锁升级为轻量锁。

8.3.2 轻量锁

有两种情况可以获取到轻量锁:关闭了偏向锁功能;多个线程竞争偏向锁。

轻量级锁什么时候升级为重量级锁?

  1. JVM会在当前线程的栈帧中建立一个lock record的空间,用于存储当前的mark word副本。
  2. 尝试用CAS将对象的mark word中的指针指向lock record。
  3. 如果成功,当前线程获得锁。如果失败,表示其他线程在竞争锁,当前线程使用自旋来获取锁。当自旋次数达到一定次数时,这时又有一个线程3过来竞争这个锁对象,那么这个时候轻量级锁就会膨胀为重量级锁。
    注意
    为了避免无用的自旋,轻量级锁一旦膨胀为重量级锁就不会再降级为轻量级锁了;偏向锁升级为轻量级锁也不能再降级为偏向锁。一句话就是锁可以升级不可以降级,但是偏向锁状态可以被重置为无锁状态。

8.3.3 重量级锁

重量级锁把除了拥有锁的线程都阻塞,防止CPU空转。

注意:由操作系统负责线程的调度和状态切换。

8.4 Volatile关键字

保证可见性:
与Java内存模型相关

保证有序性:
volatile关键字修饰变量,修饰的变量会存在一个lock;的前缀,这个前缀相当于是一个内存屏障,这个内存屏障可以提供:

  1. 确保指令重排序时不会将后面的代码排到内存屏障之前
  2. 确保指令重排序时不会将前面的代码排到内存屏障之后
  3. 确保在执行到内存屏障修饰的指令时前面的代码已经全部执行完成
  4. 强制地将线程工作内存中的修改刷新到主内存当中
  5. 如果是写操作,则会导致其他线程工作内存当中的缓存数据失效

在Java中,每个线程都对应一个工作空间,会将volatile修饰的共享变量的副本存入工作空间中,
实例:
int x = 10;
int y = x;
volatile int z = 100; //内存屏障
int sum = y + z;

thread A : z++; z=101; -》刷新到主内存
thread B : z=100 -》从主内存中获取最新的数据

练习
创建10个线程对index++ 1000次,获取10个线程加加之后的结果,结果应为10000。

public class VolatileTest implements Runnable{
	private volatile static int index = 0;
	public void increIndex(){
		index++;
	}

	@Override
	public synchronized void run() {
		for(int i = 0;i<1000;i++){
			increIndex();
		}
	}

	public static void main(String[] args) throws InterruptedException {
		Runnable runnable = new VolatileTest();
		for(int i = 0;i < 10;i++){
			Thread thread = new Thread(runnable);
			thread.start();
			thread.join();
		}
		System.out.println(index);
	}
}

注意:volatile关键字不具备保证原子性的语义,只能够禁止指令重排序,保证共享变量修改
立即刷新至主内存。

9 悲观锁与乐观锁

悲观锁
总是假设最坏的情况,假设每一次去拿数据都默认别人会修改,所以每次拿数据都会上锁,这样就导致了其他线程想要拿数据就会阻塞,直到获取锁。synchornized关键字的实现是悲观锁。

悲观锁机制存在的问题:

  1. 多线程竞争下,加锁、解锁都会导致比较多的上下文切换和调度延时,引起性能问题。
  2. 一个线程池有锁会导致其他需要此锁的线程阻塞。
  3. 数据量大时,独占锁会导致效率低下。
  4. 可能导致优先级高的线程等待优先级低的线程释放锁,引起性能问题。

乐观锁
总是假设最好的情况,假设每次拿数据都默认别人不会修改,所以不会上锁,在更新时判断在此期间有没有人修改过这个数据。乐观锁适用于多读的场景,这样可以提高吞吐量。

乐观锁可以用CAS或版本号机制实现。

CAS
概念见上文。

Java对CAS的支持:
jdk1.5之后新增java.util.concurrent.atomic包下的AtomicXXX类。
建立在CAS的基础之上,在性能上都会有很大的提升。
例:在AtomicInteger中,有getAndIncrement方法,将i++这个操作封装为原子操作。

练习:运行1000个线程,使用乐观锁进行自增操作,观察结果。

public class test {
	private static int value1 = 0; //线程不安全
	private static AtomicInteger value2 = new AtomicInteger(0);//CAS
	public static void main(String[] args) {
		for(int i=0; i<1000; i++){
			new Thread(){
				@Override
				public void run() {
					try {
						TimeUnit.SECONDS.sleep(1);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					value1++;
					value2.getAndIncrement();

				}
			}.start();
		}
		//查看活跃线程数目
		//活跃数目要大于2,主要原因是idea工具的原因,会有一个monitor线程用于监控
		while(Thread.activeCount() > 2){
			Thread.yield();
		}
		System.out.println("线程不安全:"+value1);
		System.out.println("乐观锁:"+value2);
	}
}

在这里插入图片描述

unsafe是用来帮助Java访问操作系统底层资源的类,如分配释放、释放内存在这里插入图片描述
在这里插入图片描述
版本号机制
共享数据中增加一个字段version,表示该数据的版本号,如果当前的数据发生改变,版本号加1。对数据操作前先进行判断,如果当前version与之前拿到的version一致才会进行操作。

10 死锁

造成死锁的条件

  1. 互斥条件
    某个资源在某一时间段内只能由一个线程占用。
  2. 不可抢占条件
    线程所得到的资源在未使用完毕前,资源申请者不能强行夺取资源占有者手中的资源。
  3. 占有且申请条件
    线程至少已经占有一个资源,此时又申请新的资源。
  4. 循环且等待条件
    一个线程等待其他线程释放资源,其他线程又在等待另外的线程释放资源,直到最后一个线程等待第一个线程释放资源,这使得所有的线程锁住。

死锁的预防:打破产生死锁的四个必要条件其中一个或者多个

常见的死锁问题

  1. 交叉锁可能引起程序的死锁问题

  2. 内存不足
    并发请求系统内存,如果当前系统内存不足,也有可能出现死锁的问题。
    假设一个线程要获取30MB的内存才可执行完毕,而系统可用内存只有20MB。
    thread1 已经获取了10MB内存,thread2 已经获取了20MB内存,造成互相等待资源释放。

  3. 一问一答式的数据交换
    服务器开启某个端口,等待客户端去访问,客户端发起访问请求等到接收服务器端返回的资源,
    可能由于网络问题服务器端错过了客户端的请求,造成互相等待。

  4. 死循环引起的死锁

哲学家就餐问题
问题描述:现有5位哲学家围坐就餐。他们的左右手边都有一支筷子,而每个人都需要一双筷子才能就餐。现在所有的哲学家都握住了自己右手边的筷子,他们每人都占有了一份资源,却都在等待其他资源释放才能执行完毕,同时构成了环,所以造成了死锁。
在这里插入图片描述
代码示例

class Chopstick{
	protected String name;

	public Chopstick(String name) {
		this.name = name;
	}
}
public class PhilosopherProblem extends Thread{
	private String name;
	private Chopstick left;
	private Chopstick right;

	public PhilosopherProblem(String name, Chopstick left, Chopstick right) {
		this.name = name;
		this.left = left;
		this.right = right;
	}


	@Override
	public void run() {
		synchronized (left){
			System.out.println(name+"拿到了"+left.name+"号筷子");
			synchronized (right){
				System.out.println(name+"拿到了"+right.name+"号筷子");
				System.out.println(name+"正在吃");
				try {
					TimeUnit.MILLISECONDS.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println(name+"释放了"+left.name+"号筷子和"+right.name+"号筷子");
		}

	}

	public static void main(String[] args) {
		//五只筷子
		Chopstick chopstick1 = new Chopstick("1");
		Chopstick chopstick2 = new Chopstick("2");
		Chopstick chopstick3 = new Chopstick("3");
		Chopstick chopstick4 = new Chopstick("4");
		Chopstick chopstick5 = new Chopstick("5");
		//五位哲学家
		new PhilosopherProblem("1",chopstick1,chopstick2).start();
		new PhilosopherProblem("2",chopstick2,chopstick3).start();
		new PhilosopherProblem("3",chopstick3,chopstick4).start();
		new PhilosopherProblem("4",chopstick4,chopstick5).start();
		new PhilosopherProblem("5",chopstick5,chopstick1).start();
	}
}

结果可能出现:

在这里插入图片描述
解决方案:

class ChopSticks{
	//key表示筷子的编号,value表示筷子可用状态,false是可用,true是不可用
	protected static HashMap<Integer, Boolean> map = new HashMap<>();

	static{
		map.put(0, true);
		map.put(1, true);
		map.put(2, true);
		map.put(3, true);
		map.put(4, true);
	}
}
class Philosopher implements Runnable{
	public synchronized void getChopsticks(){
		String currentName = Thread.currentThread().getName();
		int leftChop = currentName.charAt(currentName.length()-1)-'0';
		int rightChop = (leftChop+1) % 5; //leftChop+1;
		while(!ChopSticks.map.get(leftChop) || !ChopSticks.map.get(rightChop)){
			//有一个为true表示当前这个筷子正在被其他哲学家所使用
			//当前线程需要阻塞等待
			try {
				this.wait(); //释放锁
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		ChopSticks.map.put(leftChop, false);
		ChopSticks.map.put(rightChop, false);
		System.out.println(Thread.currentThread().getName() + " got the chopsticks " + leftChop +
				" and "+rightChop);
	}

	public synchronized void freeChopsticks(){
		String currentName = Thread.currentThread().getName();
		int leftChop = currentName.charAt(currentName.length()-1)-'0';
		int rightChop = (leftChop+1) % 5; //leftChop+1;
		ChopSticks.map.put(leftChop, true);
		ChopSticks.map.put(rightChop, true);
		this.notifyAll();//唤醒等待当前这双筷子的哲学家
	}

	@Override
	public void run() {
		//获得左右两边筷子
		getChopsticks();
		System.out.println(Thread.currentThread().getName() + " is eating ");
		try {
			TimeUnit.MILLISECONDS.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//释放手中的筷子
		freeChopsticks();
	}
}

public class TestDemo10 {
	public static void main(String[] args) {
		ChopSticks chopSticks = new ChopSticks();
		Runnable philosopher = new Philosopher();
		for(int i=0; i<5; i++){
			new Thread(philosopher,"thread" + String.valueOf(i)).start();
		}
	}
}

结果

在这里插入图片描述
银行家算法
问题描述:现有五位商人(线程)向三位银行家(系统)借钱(资源),每位商人都有需要钱的总数(最大资源数)、已经借到的数目(已分配资源数)、还需要的钱数(还需要的资源数)。现在规定,商人必须从三位银行家那借到所有钱款,处理完成后才能归还,如果没有全部借到则不能归还。银行家的资金有限,为了保证自己借出去的钱可以解决商人的问题且如数归还,必须计划一个借款顺序。例如,当前线程可用资源:3,3,2,如果系统全给线程1,线程1没有得到所有需要的资源,就会进入等待,但是系统也因为资源没有释放无法给其他线程给而陷入等待,则会造成死锁问题。现不妨假设先把资源交给线程2,完全足够,线程2又会释放一些资源,则可以继续分配。在确认分配完成前,需要进行安全检测,必须计算出分配完资源后,至少存在一个方案可以分配完剩余的所有资源,那么称此次分配是安全的。

线程最大资源数已分配资源数还需要的资源数
线程18,5,30,1,18,4,2
线程23,2,22,0,01,2,2
线程39,0,23,0,26,0,0
线程42,2,22,1,10,1,1
线程54,3,30,0,24,3,1

代码示例

public class test {
	//可用资源
	private static int[] avaliable = new int[]{3,3,2};

	//每个线程最大资源数
	private static int[][] max = new int[][]{{8,5,3}, {3,2,2}, {9,0,2},{2,2,2}, {4,3,3}};

	//每个线程已分配的资源
	private static int[][] alloction = new int[][]{{0,1,1}, {2,0,0}, {3,0,2}, {2,1,1}, {0,0,2}};

	//每个线程需要的资源数
	private static int[][] need = new int[][]{{8,4,2}, {1,2,2}, {6,0,0}, {0,1,1}, {4,3,1}};

	public static void showData(){
		System.out.println("线程编号    最大需求     已分配      还需要");
		for(int i=0; i<5; i++){
			System.out.print(i+"        ");
			for(int j=0; j<3; j++){
				System.out.print(max[i][j]+"   ");//i表示线程号 j表示资源数
			}
			for(int j=0; j<3; j++){
				System.out.print(alloction[i][j]+"   ");//i表示线程号 j表示资源数
			}
			for(int j=0; j<3; j++){
				System.out.print(need[i][j]+"   ");//i表示线程号 j表示资源数
			}
			System.out.println();
		}
	}

	//分配资源 requestNum表示所请求的线程号 request[]当前线程所请求的资源数
	public static boolean allocate(int requestNum, int request[]){
		if(request[0] > need[requestNum][0] || request[1] > need[requestNum][1] || request[2] > need[requestNum][2]){
			System.out.println("请求的资源数目超过了当前这个线程还需要的资源数目");
			return false;
		}

		if(request[0] > avaliable[0] || request[1] > avaliable[1] || request[2] > avaliable[2]){
			System.out.println("目前没有足够的资源分配,必须等待");
			return false;
		}

		//预分配资源给请求的线程
		for(int i=0; i<3; i++){
			//可分配的资源-请求资源数量
			avaliable[i] = avaliable[i] - request[i];
			//已经分配的资源alloction + 请求资源数目
			alloction[requestNum][i] = alloction[requestNum][i] + request[i];
			//还需要资源数need - 请求的资源数
			need[requestNum][i] = need[requestNum][i] - request[i];
		}
		//进行安全性检查,true表示剩余资源能够满足其余线程的资源请求,false表示无法满足
		boolean flag = checkSafe();
		if(flag){
			for(int j=0; j<3; j++){
				avaliable[j] = avaliable[j] + alloction[requestNum][j];
			}
			System.out.println("能够安全分配");
			return true;
		}else{
			//不能够通过安全性检查,撤销之前预分配的资源
			System.out.println("不能够安全分配");
			for(int i=0; i<3; i++){
				//可分配的资源+请求资源数量
				avaliable[i] = avaliable[i] + request[i];
				//已经分配的资源alloction - 请求资源数目
				alloction[requestNum][i] = alloction[requestNum][i] - request[i];
				//还需要资源数need + 请求的资源数
				need[requestNum][i] = need[requestNum][i] + request[i];
			}
			return false;
		}
	}

	public static boolean checkSafe(){
		/**
		 * 预分配操作:
		 * 循环遍历其余线程,查看可用资源是否能够满足其余线程的资源请求,
		 * 如果满足则进行下一次的遍历,如果不满足直接判断下一个线程
		 * 如果有可分配方案,则此次分配是一个安全分配。
		 */
		int i = 0;
		int[] avaliable1 = new int[avaliable.length];
		for(int a = 0;a<avaliable1.length;a++){
			avaliable1[a] = avaliable[a];
		}
		boolean[] finish = new boolean[5];
		while(i < 5){
			if(finish[i] == false && need[i][0] <= avaliable1[0] && need[i][1] <= avaliable1[1] && need[i][2] <= avaliable1[2]){
				System.out.print("分配成功的线程为:"+i+"     ");

				//执行成功之后还需要将所有资源释放
				for(int j=0; j<3; j++){
					avaliable1[j] = avaliable1[j] + alloction[i][j];
				}
				System.out.println();
				finish[i] = true;//表示当前线程已经执行完
				i = 0;
			}else{
				i++;
			}
		}
		//while循环结束之后,所有finish标识都为true,表示所有线程都已经执行完
		for(int m=0; m<5; m++){
			if(finish[m] == false){
				return false;
			}
		}
		return true;
	}

	public static void main(String[] args) {
		showData();
		System.out.println("当前系统可用资源:");
		for(int i=0; i<3; i++){
			System.out.print(avaliable[i] + "  ");
		}
		System.out.println();
		//请求线程资源存放的数组
		int[] request = new int[3];
		int requestNum;
		String source[] = new String[]{"A", "B", "C"};
		Scanner s = new Scanner(System.in);
		String choice = new String();
		while(true){
			System.out.print("请输入要请求的线程编号:");
			requestNum = s.nextInt();
			System.out.println("请输入要请求的资源数目:");
			for(int i=0; i<3; i++){
				System.out.print(source[i]+"资源的数目:");
				request[i] = s.nextInt();
			}
			//分配资源
			allocate(requestNum, request);
			System.out.print("是否再次请求分配(y/n):");
			choice = s.next();
			if(choice.equals("n")){
				break;
			}
			showData();
			System.out.println("当前系统可用资源:");
			for(int i=0; i<3; i++){
				System.out.print(avaliable[i] + "  ");
			}
		}
	}
}

结果测试
在这里插入图片描述

线程通信

本节将详细说明synchronzied加锁的线程的Object类的wait/notify/notifyAll方法。

  1. wait()/notify()/notifyAll()是final native的方法,不能够被重写
  2. 通过某一个对象调用它的wait()方法,obj.wait(),表示使得调用这个方法
    的线程阻塞,并且当前线程必须要拥有当前这个对象的monitor lock。
  3. 调用某一个对象的notify()方法,obj.notify(),表示唤醒其中一个调用
    了obj.wait()方法的线程,如果存在多个线程,当前这个方法只能唤醒一个。
  4. 调用某一个对象的notifyAll()方法,obj.notifyAll(),表示唤醒所有调
    用了obj.wait()方法的线程去争抢锁。

为什么这三个方法不是Thread类中声明的方法,而是Object类中声明的
方法?

线程与锁是多对一的关系。每个对象都拥有对象锁(monitor lock),当某一个线程等待某一个对象的锁,需要使用对象去操作,即表明需要获取当前对象锁的线程等待/唤醒。

public class TestDemo12 {
    public static void main(String[] args) {
        String lock  = new String("test");
        try {
            lock.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        /**
         * 问题:IllegalMonitorStateException,没有用到监视器锁,所以会出现
         * 异常
         */

        String lock  = new String("test");
        synchronized (lock){
            try {
                lock.wait();
                System.out.println("the current running thread is "+Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        /**
         * wait()之后使得当前线程阻塞,wait方法之后的代码是不会执行的,释放当前所拥有的monitor lock
         */

        String lock  = new String("test");
        new Thread("A"){
            @Override
            public void run() {
                synchronized (lock){
                    try {
                        lock.wait();
                        System.out.println("the current running thread is "+Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            }
        }.start();

        new Thread("B"){
            @Override
            public void run() {
                synchronized (lock){
                    System.out.println("开始notify time: "+System.currentTimeMillis());
                    lock.notify();
                    System.out.println("结束notify time: "+System.currentTimeMillis());
                }
            }
        }.start();
        /**
         * 结果:开始->结束->running
         * notify()方法执行后并不会立即释放锁
         */
         
        String lock  = new String("test");
        Thread thread = new Thread("A"){
            @Override
            public void run() {
                synchronized (lock){
                    try {
                        lock.wait();
                        System.out.println("the current running thread is "+Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        System.out.println("the thread has been interrupted and the state is "+Thread.currentThread().isInterrupted());
                    }
                }
            }
        };
        thread.start();
        thread.interrupt();

        /**
         * wait是可中断方法,可中断方法会收到中断异常InterruptedException,同时interrupt标识也会被擦除
         */
    }
}

生产者与消费者问题:
现有一生产者和一万个消费者,还有一个阻塞队列。生产者将生产的东西放入队列中,消费者来取走。队列满了,生产者无法继续;队列空了,消费者无法继续。需用到wait和notify函数来控制,不会造成互相等待。

class BlockingQueue<E>{
	private final LinkedList<E> queue = new LinkedList<>();
	private static int max; //表示阻塞队列存储元素的最大个数
	private static final int DEFAULT_MAX_VALUE = 10;

	public BlockingQueue(){
		this(DEFAULT_MAX_VALUE);
	}

	public BlockingQueue(int max){
		this.max = max;
	}

	//生产数据
	public void put(E value){
		synchronized (queue){
			//判断当前队列是否有位置存放新生产的数据
			while(queue.size() >= max){
				System.out.println(Thread.currentThread().getName() +" :: queue is full");
				try {
					//没有位置,当前生产数据的线程需要阻塞
					queue.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName()+":: the new value " +value+" has been produced");
			queue.addLast(value);
			queue.notifyAll();
		}
	}

	//消费数据
	public E take(){
		synchronized (queue){
			//判断当前队列是否存在可消费的数据
			while(queue.isEmpty()){
				System.out.println(Thread.currentThread().getName() +" :: queue is empty");
				try {
					//不存在,则调用消费数据的线程阻塞
					queue.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			E result = queue.removeFirst();
			queue.notifyAll();

			System.out.println(Thread.currentThread().getName()+":: the value " +result + " has been consumed");
			return result;
		}
	}
}

public class ProducerAndConsumerDemo {
	public static void main(String[] args) {
		BlockingQueue<Integer> queue = new BlockingQueue<Integer>();
		new Thread("Producer"){
			@Override
			public void run() {
				while(true){
					queue.put((int)(Math.random() * 1000));
				}
			}
		}.start();

		for(int i = 0;i<100000;i++){
			new Thread("Consumer"+String.valueOf(i)){
				@Override
				public void run() {
					while(true){
						queue.take();

						try {
							TimeUnit.MILLISECONDS.sleep(1);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
				}
			}.start();
		}
	}
}

但是以上代码有个问题是:当消费者很多时,消费者很容易唤醒其他消费者,从而造成资源浪费。改进方案:消费者定向唤醒生产者。

ReentrantLock

tryLock(timeout) 可定时的
lockInterruptibly() 可响应中断
tryLock() 可轮询

ReetrantLock的使用

Lock lock = new ReentrantLock();
lock.lock();//加锁
try{
   //共享代码
}finally{
   lock.unlock(); //释放锁
}

ReentrantLock释放锁是显式的,有可能会忘记清除锁,使用起来更加"危险"。

ReentrantLock是否是可重入锁?
XXXXXXXXXXXXXXXXXXXXXXX

ReetrantLock的实现

构造函数

// 默认创建非公平锁,即谁运气好,谁先获得cpu使用权,哪个线程就先获取执行
public ReentrantLock()

//传参true,就创建公平锁,即第一次获取锁的线程,在下一次执行也会优先获取锁
public ReentrantLock(boolean fair)

AbstractQueuedSynchronizer

AQS队列,先进先出。
底层:Node相连的双向链表。
获取锁

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
  1. tryAcquire 以独占锁的模式去获取当前的锁 可能成功也可能失败
  2. addWaiter 把当前的线程包装成一个节点Node插入到队列当中
  3. acquireQueued 尝试获取选择一个线程,获取锁
    获取锁首先判断当前节点前驱是否为head,这是获取锁的资格
    如果成功获取锁,则设置当前节点置为head节点;如果未成功获取锁,则根据
    前驱节点判断当前线程是否要阻塞,shouldParkAfterFailedAcquire方法
    在前驱状态不为signal的情况下会循环尝试获取锁,如果为signal则需要阻塞,
    等待前驱节点释放锁之后将其唤醒。

释放锁:

Condition

实现线程间的通信
await():将满足当前Condition条件的线程加入到等待队列,释放其中的锁。
signal():唤醒一个在等待队列中的线程。
signalAll():唤醒所有在等待队列中的线程。

Condition con = lock.newCondition();
con.await()

注意事项:

  1. 使用await()必须要使用lock.lock()加锁,否则会抛出IllegalMonitorStateException。
  2. 使用await()使得当前线程阻塞,await()之后的代码不会执行。
  3. await()方法调用之后会释放当前的lock。
  4. signal()方法唤醒一个线程,但是该方法执行之后不会立即释放锁,出了锁的作用域才会将锁释放掉。
  5. await方法是可中断方法,可中断方法会收到中断异常InterruptException异常。

synchronized关键字下的 对象.wait/notify(All)方法 只有一个等待通道,无法根据逻辑区分线程,而Reentrant下的每个lock可以定义多个Condition对象,意味着有多个条件,即多个线程等待队列,需要让不同 Condition对象.await/signal(All)方法 才能唤醒其他等待队列上的线程。

练习:有三个线程A,B,C,需要线程交替打印ABCABCABCABC…, 打印10次,借助await/signal/signalAll实现。
0 1 2 3 4 5 6 7 8 9
A B C A B C A B C A
A:count % 3 = 0
B:count % 3 = 1
C:count % 3 = 2

public class ReentrantLockTest {
	private static final Lock lock = new ReentrantLock(true);
	private static final Condition A = lock.newCondition();
	private static final Condition B = lock.newCondition();
	private static final Condition C = lock.newCondition();
	private static int count = 0;
	public static void main(String[] args) {
		Thread threadA = new Thread(){
			@Override
			public void run() {
				lock.lock();
				try {
					for(int i = 0;i<10;i++){
						while(count % 3 != 0){
							A.await();
						}
						System.out.print("A");
						count++;
						B.signalAll();
					}
				}catch (InterruptedException e) {
					e.printStackTrace();
				}finally {
					lock.unlock();
				}
			}
		};

		Thread threadB = new Thread(){
			@Override
			public void run() {
				lock.lock();
				try {
					for(int i = 0;i<10;i++){
						while(count % 3 != 1){
							B.await();
						}
						System.out.print("B");
						count++;
						C.signalAll();
					}
				}catch (InterruptedException e) {
					e.printStackTrace();
				}finally {
					lock.unlock();
				}
			}
		};

		Thread threadC = new Thread(){
			@Override
			public void run() {
				lock.lock();
				try{
					for(int i = 0;i<10;i++){
						while(count % 3 != 2){
							C.await();
						}
						System.out.print("C ");
						count++;
						A.signalAll();
					}
				}catch (InterruptedException e) {
					e.printStackTrace();
				}finally {
					lock.unlock();
				}
			}
		};
		threadA.start();
		threadB.start();
		threadC.start();
	}
}

练习:使用ReentrantLock中Condition实现生产者消费者模型

读写锁

ReadWriteLock lock = new ReentrantReadWriteLock();
共享读,但只能一个写,即读读不互斥、读写互斥、写写互斥。
读远远大于写的高并发情况下,一般会使用读写锁。

public class ReentrantReadWriteLockTest {
	ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
	private int num = 0;

	public void read(){
		lock.readLock().lock();
		try{
			for(int i = 0;i<3;i++){
				System.out.println(Thread.currentThread().getName()+"正在读");
			}
		}finally {
			lock.readLock().unlock();
		}
	}

	public void write(int newNum){
		lock.writeLock().lock();
		try {
			System.out.println(Thread.currentThread().getName()+"正在写"+num);
			num = newNum;
			System.out.println(Thread.currentThread().getName()+"写完了"+num);
		}finally {
			lock.writeLock().unlock();
		}
	}

}
class Main{
	public static void main(String[] args) {
		ReentrantReadWriteLockTest rwLock = new ReentrantReadWriteLockTest();
		for(int i = 1;i<=5;i++){
			new Thread("read"+i){
				@Override
				public void run() {
					rwLock.read();
				}
			}.start();
		}

		for(int i = 1;i<=5;i++){
			new Thread("write"+i){
				@Override
				public void run() {
					rwLock.write(new Random().nextInt(10));
				}
			}.start();
		}
	}
}

结果
在这里插入图片描述

线程安全的集合

Collections.synchronizedXXX() 方法可以将线程非安全的集合以组合的方式封装为线程安全的集合(给get, set, add 等操作都加了 mutex 对象锁)。

Vector:ArrrayList 方法加上synchronized关键字,读读互斥,读写互斥。
HashTable:HashMap 方法加上synchronized关键字,读读互斥,读写互斥。
CopyOnWriteArrayList非快速失败机制的代表。读读不互斥,写时拷贝一份新的数组对其操作,结束后将原来集合指向新的集合来完成写操作。源码里使用ReentrantLock可重入锁来保证不会有多个线程同时拷贝一份数组。

ConcurrentHashMap:高效,因为其锁的力度小。
属性介绍:

  1. table 哈希桶 默认是一个大小为16的数组
  2. nextTable 扩容时新生成的数组,大小为原数组的2倍
  3. sizeCtl 控制table的初始化和扩容
    -1 代表table正在初始化。
    -N 代表有N-1线程对map进行并发操作。
    如果table未初始化,代表table需要初始化的大小。
    如果table已经初始化完成,代表table的容量,默认是table大小的0.75倍。
  4. Node 保存key,value,hash值。
  5. ForwadingNode 特殊Node节点,当map发生扩容,ForwadingNode会发挥作用。

线程池

在jdk1.5之后,Executor接口用于创建线程池。
多线程计数最大限度地发挥了多喝处理器地计算能力,线程的数量和系统性能是抛物线的关系。线程数量过多,反而性能下降。

为什么要引入线程池?

  1. 创建和销毁线程的时间的和可能会远远大于线程的执行时间。
  2. 线程也需要占用内存空间,大量的线程占用的内存资源会比较多,可能导致OOM异常。
  3. 大量的线程回收会给GC带来很大压力。
  4. 大量的线程会抢占cpu的资源,cpu不停的进行各个线程的上下文切换。

线程池就是事先创建若干个可执行的线程放入一个池(容器)中,有任务需要执行时,从池子中获取一个线程执行任务,不用自行创建,执行完后不需要销毁线程而是将当前的线程归还到线程池当中,从而减少创建和销毁线程所带来的性能的开销。

线程池的优势:线程资源重复利用,降低系统资源的消耗,提高响应速度,提高线程的可管理性,线程池可以统一的调优和监控。
在这里插入图片描述
一个线程池的必备要素:

  1. 线程数量管控功能
  2. 任务队列:用于缓存提交的任务。
  3. 任务拒绝策略:任务队列是一个有界的队列,线程数量达到上限并且任务队列已满时,应拒绝任务提交者的请求。
  4. 线程工厂(加工当前线程)
  5. 任务队列需要限制值:limit。
    在这里插入图片描述

线程池的使用

四种线程池

  1. 单一数量的线程池
class MyCallable1 implements Callable<Integer> {
	@Override
	public Integer call() throws Exception {
		System.out.println(Thread.currentThread().getName()+"::hello world");
		try {
			TimeUnit.MILLISECONDS.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()+"::hello world,goodbye");
		return (int)(Math.random()*100);
	}
}
public class ExecutorTest1 {
	public static void main(String[] args) throws ExecutionException, InterruptedException {
		ExecutorService pool = Executors.newSingleThreadExecutor();
		MyCallable1 myCallable1 = new MyCallable1();
		Future<Integer> result = pool.submit(myCallable1);
		System.out.println(result.get());
		pool.shutdown();
	}
}
  1. 固定数量的线程池
ExecutorService pool = Executors.newFixedThreadPool(4);
  1. 周期性的线程池
class MyRunnable1 implements Runnable{
	@Override
	public void run() {
		System.out.println("任务执行时间:"+new Date().getSeconds());
		System.out.println(Thread.currentThread().getName()+"::hello world");
		try {
			TimeUnit.MILLISECONDS.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()+"::hello world,goodbye");
	}
}
public class ExecutorTest1 {
	public static void main(String[] args) throws ExecutionException, InterruptedException {
		ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);
		MyRunnable1 myRunnable1 = new MyRunnable1();
		System.out.println("任务提交时间:"+new Date().getSeconds());
		pool.schedule(myRunnable1,3,TimeUnit.SECONDS); //延迟3秒执行的任务
		pool.shutdown();
	}
}

在这里插入图片描述

  1. 可缓存的线程池
class MyCallable1 implements Callable<Integer> {
	private CountDownLatch latch;

	public MyCallable1(CountDownLatch latch){
		this.latch = latch;
	}
	@Override
	public Integer call() throws Exception {
		System.out.println(Thread.currentThread().getName()+"::hello world");
		latch.countDown();
		return (int)(Math.random()*100);
	}
}
public class ExecutorTest1 {
	public static void main(String[] args) throws ExecutionException, InterruptedException {
		ExecutorService pool = Executors.newCachedThreadPool();
		CountDownLatch latch = new CountDownLatch(30);
		MyCallable1 myCallable1 = new MyCallable1(latch);
		for (int i = 0; i < 30; i++) {
			pool.submit(myCallable1);
		}
		try {
			latch.await();
		}catch (InterruptedException e){
			e.printStackTrace();
		}
		System.out.println("main thread end");
		pool.shutdown();
	}
}

在这里插入图片描述
可以看到有重复,当有任务提交时,优先重复使用线程池中的空闲线程,但是如果线程池中没有空闲线程则会创建线程。

提交
execute() 和submit() 的区别

  1. void execute(Runnable command),无返回值。
  2. Future submit(Runnable task) / (Runnable task,T result) / (Callable task),有返回值。提交实现Runable的task也返回null。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值