1.线程概述
1.1线程与进程
操作系统可以同时执行多个任务,每个任务就是进程;进程可以同时执行多个任务,每个任务就是线程。
并行性:指同一时刻,有多条指令在多个处理器上同时执行;(多条指令,多个处理器,每一个处理器处理一个指令)
并发性:指同一时刻,只有一条指令执行,但多个进程指令被快速轮换执行,使得宏观上具有多个进程同时被执行的效果;
进程的三个特征:
独立性:进程是系统中独立存在的实体,它可以拥有自己独立的资源,每一个进程都拥有自己私有的地址空间,在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间;
动态性:进程具有自己的不同生命周期和各种不同的状态,也就是说在进程中加入了时间的概念;而程序是一个静态的指令集合;
并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会相互影响;
多线程扩展了多进程的概念:使得一个进程可以同时并发处理多个任务,线程是进程的执行单元。就像进程在操作系统中的地位一样,线程在程序中是独立、并发的执行流。
关系:
线程是进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程。线程可以拥有自己的堆栈,自己的程序计数器和自己的局部变量,但不拥有系统资源,它与父进程的其他线程共享该进程所拥有的全部资源。因为多个线程共享父进程中的全部资源。其次线程在进程中是独立运行的,它不知道是否还存在其他线程,因此线程的执行时抢占式的,也就是说,当前运行的线程在任何时候都可能被挂起,以便另一个线程可以运行。
---------------------------------------------------------
2.线程的创建和启动
第一种:继承 Thread,重写 run() 方法,调用线程对象的 start() 方法开始线程;
public class FirstThread extends Thread{
//Note that: 这里的成员变量为实例变量;
//注意输出结果:看两个线程是不是共享这个i;
private int i;
@Override
public void run() {
for(;i<30;i++){
System.out.println(this.getName()+" : "+i);
}
}
public static void main(String[] args) {
for(int i=0;i<30;i++){
System.out.println(Thread.currentThread().getName()+" : "+i);
if(i==20){
new FirstThread().start();
new FirstThread().start();
}
}
}
}
输出结果:
main : 0
。。。
main : 24
Thread-0 : 0
Thread-0 : 1
Thread-1 : 0
Thread-1 : 1
Thread-1 : 2
Thread-1 : 3
main : 25
main : 26
main : 27
Thread-1 : 4
Thread-0 : 2
Thread-0 : 3
Thread-0 : 4
Thread-0 : 5
Thread-1 : 5
main : 28
main : 29
Thread-1 : 6
Thread-0 : 6
Thread-0 : 7
Thread-1 : 7
Thread-0 : 8
。。。
Thread-0 : 28
Thread-1 : 22
Thread-0 : 29
。。。
Thread-1 : 28
Thread-1 : 29
从输出结果中可以看到:Thread-0 和 Thread-1 两个线程输出的 i 变量是不连续的;因为 i 变量是FirstThread的 实例变量,程序每次创建线程对象时都需要创建一个FirstThread对象,所以Thread-1 和 Thread-2
不能共享该实例变量;
第二种方法:实现Runnalbe接口 重写run 方法;创建Ruannable实现类的实例,并以此实例作为Thread的target来创建Thread对象; 调用线程对象的start()方法;
public class SecondThread implements Runnable {
//Note that: 这里依然是定义一个实例变量i;
//注意观察俩个线程是否共享这个数据;
private int i;
@Override
public void run() {
for(;i<30;i++){
System.out.println(Thread.currentThread().getName()+" : "+i);
}
}
private static void sameTarget() {
for(int i=0;i<40;i++){
System.out.println(Thread.currentThread().getName()+" : "+i);
if(20==i){
//Note that: two Thread but using same target;
SecondThread target = new SecondThread();
new Thread(target,"线程 1 ").start();
new Thread(target,"线程 2 ").start();
}
}
}
private static void differTarget() {
for(int i=0;i<40;i++){
System.out.println(Thread.currentThread().getName()+" : "+i);
if(20==i){
//Note that: new two Thread and each uses different target
new Thread(new SecondThread(), "线程 -1 ").start();
new Thread(new SecondThread(), "线程 -2 ").start();
}
}
}
public static void main(String[] args) {
sameTarget();
differTarget();
}
}
输出结果:
main : 0
main : 1
...
main : 31
main : 32
线程 1 : 0
线程 1 : 1
main : 33
线程 2 : 0
线程 2 : 3
main : 34
main : 35
线程 1 : 2
线程 1 : 5
main : 36
main : 37
main : 38
main : 39
main : 0
main : 1
线程 2 : 4
...
线程 2 : 17
...
main : 22
线程 2 : 26
线程 2 : 28
main : 23
线程 -1 : 0
线程 -1 : 1
线程 1 : 27
线程 -1 : 2
线程 -1 : 3
main : 24
线程 2 : 29
线程 -2 : 0
线程 -2 : 1
main : 25
线程 -1 : 4
线程 -1 : 5
main : 26
线程 -2 : 2
线程 -2 : 3
main : 27
线程 -1 : 6
线程 -1 : 7
线程 -1 : 8
...
main : 37
main : 38
线程 -2 : 4
线程 -2 : 5
main : 39
线程 -1 : 9
...
线程 -1 : 27
线程 -2 : 20
线程 -1 : 28
线程 -2 : 21
线程 -1 : 29
线程 -2 : 22
...
线程 -2 : 29
从输出结果中可以看出:在 sameTarget方法中当我们使用同一个target时,虽然创建了两个Thread实例,但是他们共享该线程类的实例变量,因为在这种情况下,
程序所创建的Runnable对象只是线程的target,而多个线程可以共享一个target,所以自然多个线程可以共享同一个线程类(实际上应该是线程的target类)的实例变量;这样自然也可以理解 differTarget方法中输出的结果;
第三种方法:创建实现了Callable接口的类,并实现call()方法;使用FutureTask类来包装Callable对象,同时封装了call的返回值;使用FutureTask对象作为Thread对象的target创建并启用新线程;从FutureTask中get出返回值;
public class ThridThread implements Callable<Integer> {
private int sum;
@Override
public Integer call() throws Exception {
System.out.println("子线程计算开始...");
for(int i=0;i<1000;i++){
sum+=i;
}
System.out.println("子线程计算结束...结果为:"+sum);
return sum;
}
public static void main(String[] args){
//创建线程池
ExecutorService es = Executors.newSingleThreadExecutor();
//创建callable对象
ThridThread task = new ThridThread();
//提交任务并获取结果
Future<Integer> future = es.submit(task);
//关闭线程池
es.shutdown();
// ThridThread callable = new ThridThread();
// FutureTask<Integer> futureTask = new FutureTask<Integer>(callable);
// new Thread(futureTask).start();
try {
Thread.sleep(1000);
System.out.println("main线程正在运行。。。");
if(future.get()!=null)
System.out.println("子线程的运行结果为:"+future.get());
System.out.println("main线程运行结束。。。");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
---------------------------------------------------------
3.线程的生命周期
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态,在线程的生命周期中,它要经历,新建(new),就绪(Runnable),运行(Running),阻塞(Blocked),死亡(Dead)5种状态。尤其是当线程启动以后,它不可能一直“霸占”CPU独自执行,所以CPU需要在多条线程之间切换,于是线程状态也会多次在运行、就绪之间切换。
3.1新建和就绪状态
new关键字来创建了一个线程之后,该线程就处于新建状态,此时他和其他的Java对象一样,仅仅由Java虚拟机为其分配内存,并初始化其成员变量的值。
start,当线程对象调用了start()方法之后,该线程处于就绪状态,JVM会为其创建方法调用栈和程序计数器,处于这个状态的线程并没有开始运行,只是表示该线程可以运行了,至于该线程何时开始运行,取决于JVM里线程调度器的调度;
注意:如果直接调用线程对象的run()方法,则run()方法立即就会被执行,而且在run()方法返回之前其它线程无法并发执行-也就是说,如果直接调用线程对象的run方法,系统把线程对象当成一个普通对象,而run()方法也就是一个普通方法,而不是线程执行体。
3.2运行和阻塞状态
运行状态:如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态,如果一个计算机只有一个CPU,那么在任何时刻只有一个线程处于运行状态。当然,在一个多处理其的机器上,将会有多个线程并行执行,当线程数大于处理器数时,依然会存在多个线程在同一个CPU上轮换的现象。
阻塞状态:当一个线程开始运行后,它不可能一直处于运行状态,线程在运行过程中需要被中断,目的是让其它线程获得执行的机会,
当发生如下情况时,线程将会进入阻塞状态:
- 线程调用sleep()方法主动放弃所占用的处理器资源,
- 线程调用了一个阻塞式的IO方法,在该方法返回之前,该线程被阻塞;
- 线程试图获取一个同步监视器,但该同步监视器正被其它线程所持有,
- 线程在等待某个通知;
- 调用suspend方法,容易死锁,不适用;
3.3 线程死亡
- run方法执行完成,线程死亡;
- 线程抛出一个未被捕获的Exception或者Error
- 直接调用线程的stop方法,容易导致死锁,不推荐使用;
---------------------------------------------------------
4.控制线程
4.1 join线程
Thread提供了一个让一个线程等待另一个线程完成的方法:join方法,当在某个程序执行流中调用其它线程的join方法时,调用线程将被阻塞,直到被join方法加入的join线程被执行完成为止。
public class JoinThreadCase implements Runnable {
private int i;
@Override
public void run() {
for(;i<20;i++){
System.out.println(Thread.currentThread().getName()+" : "+i);
}
}
public static void main(String[] args) {
//Note that: start thread 1
new Thread(new JoinThreadCase(),"Thread-1").start();
for(int i=0;i<30;i++){
System.out.println(Thread.currentThread().getName()+" : "+i);
if(20==i){
JoinThreadCase jtc = new JoinThreadCase();
Thread jtcTread = new Thread(jtc,"Thread-2");
jtcTread.start();
try {
//在main线程中调用了jtcThread的join方法,被调线程(main)将会阻塞,
jtcTread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
运行结果:
main : 0
main : 1
main : 2
...
main : 18
main : 19
main : 20
Thread-1 : 0
Thread-1 : 1
...
Thread-1 : 9
Thread-1 : 10
Thread-1 : 11
Thread-2 : 0
Thread-2 : 1
...
Thread-2 : 9
Thread-2 : 10
Thread-1 : 12
Thread-1 : 13
Thread-2 : 11
Thread-1 : 14
Thread-1 : 15
Thread-1 : 16
Thread-1 : 17
Thread-1 : 18
Thread-1 : 19
Thread-2 : 12
Thread-2 : 13
Thread-2 : 14
Thread-2 : 15
Thread-2 : 16
Thread-2 : 17
Thread-2 : 18
Thread-2 : 19
main : 21
main : 22
main : 23
main : 24
main : 25
main : 26
main : 27
main : 28
main : 29
结果分析:在main线程中首先开启线程Thread-1,当i=20时,开启线程Thread-2,并且调用了Thread-2的join方法,因此当在某个程序流中(main流)调用其他线程(Thread-2)的join方法时,调用线程(main线程)被阻塞,直到被join的线程(Thread-2)执行完成为止。
4.2 Daemon线程
我们需要调用线程Thread的setDaemon(true)方法可以将指定的线程设置为后台线程。如果所有的前台线程都死亡了,后台线程也会自动消亡。
public class DaemonThreadCase implements Runnable {
private int i;
@Override
public void run() {
for(;i<1000;i++){
try {
Thread.sleep(1);
System.out.println(Thread.currentThread().getName()+" : "+i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
DaemonThreadCase dtc = new DaemonThreadCase();
Thread thread = new Thread(dtc,"后台线程");
//在start前设置为守护线程;
thread.setDaemon(true);
thread.start();
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+" : "+i);
}
}
}
结果:
main : 0
main : 1
main : 2
main : 3
main : 4
main : 5
main : 6
main : 7
main : 8
main : 9
后台线程 : 0
后台线程 : 1
后台线程 : 2
后台线程 : 3
后台线程 : 4
后台线程 : 5
后台线程 : 6
后台线程 : 7
后台线程 : 8
后台线程 : 9
后台线程 : 10
后台线程 : 11
后台线程 : 12
后台线程 : 13
后台线程 : 14
后台线程 : 15
后台线程 : 16
后台线程 : 17
后台线程 : 18
后台线程 : 19
后台线程 : 20
后台线程 : 21
后台线程 : 22
后台线程 : 23
后台线程 : 24
后台线程 : 25
后台线程 : 26
后台线程 : 27
后台线程 : 28
后台线程 : 29
后台线程 : 30
后台线程 : 31
后台线程 : 32
后台线程 : 33
后台线程 : 34
后台线程 : 35
后台线程 : 36
后台线程 : 37
从运行结果中我们可以发现 dtc为target的线程并没有执行到i=999就结束了,因为当时的main前台线程执行完成了,所有后台线程自动的结束了;
注意:前台线程死亡后,JVM会通知后台线程死亡,但从它接收指令到做出响应,需要一定时间,而且要将某个线程设置为后台线程,必须在该线程启动之前设置,也就是说,setDaemon(true)必须在start()方法之前调用,否则会引发IllegalThreadStateException异常。
4.3 线程睡眠 sleep 和线程让步 yield
当当前线程调用sleep方法进入阻塞状态后,在其睡眠时间段内,该线程不会获得执行的机会,即使系统中没有其他可执行的线程,处于sleep中的线程也不会执行,
yield方法和sleep方法有点相似,它也是Thread提供的一个静态方法,它也可以让当前正在执行的线程暂停,但它不会阻塞该线程,它只是将该线程转入就绪状态。因此可能出现当某个线程调用了yield方法暂停之后,线程调度器又将其调度出来重新执行;
sleep 和 yield的区别:
- sleep()方法暂停当前线程后,会给其他线程执行机会,不会理会其他线程的优先级;但yield方法会给优先级相同,或者优先级较高的线程执行机会;
- sleep()方法会将线程转入阻塞状态,直到经过阻塞时间后才会进入就绪状态;而yield不会讲线程转入阻塞状态,它只是强制当前线程进入就绪状态。
- sleep()方法声明抛出了InterruptedException异常,所以调用sleep()方法时要么捕获该异常,要么显示声明抛出该异常;而yield方法则没有声明任何异常;
- sleep()方法比yield方法有更好的移植性,建议用sleep方法;
-------------------------------------------------------------------------------------------------------------
5.线程同步问题;
第一种同步代码块:
为了解决多线程同步问题,Java的多线程支持引入了同步监视器(锁)来解决这个问题,使用同步监视器的通用方法就是同步代码块。格式如下
synchronized(obj){
//同步代码
}
虽然Java程序运行使用任何对象作为同步监视器,但想一下同步监视器的目的:阻止两个线程对同一个共享资源进行并发访问,因此通常推荐使用
可能被并发访问的共享资源(共享资源类名.class)充当同步监视器。
任何线程在进入同步代码块、同步方法之前,必须先获得对同步监视器的锁定,那么何时会释放对同步监视器的锁定呢?程序无法显示释放对同步监视器的锁定,
线程会在如下几种情况下释放对同步监视器的锁定:
- 当前线程的同步方法、同步代码块执行结束,当前线程即释放同步监视器;
- 当前线程在同步方法、同步代码块中遇到break、return终止了该方法、该代码块的继续执行,当前线程会释放同步监视器;
- 当前线程在同步方法、同步代码块中出现了未处理的Error或Exception,导致了该方法、该代码块异常结束时,当前线程会释放同步监视器;
- 当前线程在同步方法、同步代码块中执行了同步监视器对象的 wait() 方法,则当前线程暂停并且释放同步监视器;
在如下情况下,线程不会释放同步监视器:
- 线程执行同步代码块或者同步方法时,程序调用 Thread.sleep(), Thread.yield() 方法来暂停当前线程的执行,当前线程不会释放同步监视器;
- 线程执行同步代码块或者同步方法时,其他线程调用了该线程的suspend方法将该线程挂起,该线程不会释放同步监视器。当然,程序应该尽量避免使用这个方法;
第二种:同步锁Lock
使用显示定义同步锁对象Lock来实现同步,同步方法或者同步代码块使用与竞争资源相关的、隐式的同步监视器,并且强制要求加锁和释放锁要出现在一个块结构中,而且当获取多个锁时,它们必须以相反的顺序释放,且必须在与所有锁被获取时相同的范围内释放所有锁;
package com.qian.obj;
import java.util.concurrent.locks.ReentrantLock;
class Account{
private String accountNo;
private double balance;
private final ReentrantLock lock = new ReentrantLock();
public Account(String accountNo, double balance) {
super();
this.accountNo = accountNo;
this.balance = balance;
}
//使用synchronized 同步方法或者同步代码块;
public synchronized void drawMoney(double drawAmount) {
if(balance>=drawAmount){
System.out.println(Thread.currentThread().getName()+" 取钱成功 取钱金额为:"+drawAmount);
balance -= drawAmount;
System.out.println("\t余额为:"+balance);
}else{
System.out.println("余额不足");
}
}
//使用显示的锁;Lock
public void drawMoney2(double drawAmount) {
lock.lock();
try{
if(balance>=drawAmount){
System.out.println(Thread.currentThread().getName()+" 取钱成功 取钱金额为:"+drawAmount);
balance -= drawAmount;
System.out.println("\t余额为:"+balance);
}else{
System.out.println("余额不足");
}
}finally{
lock.unlock();
}
}
public String getAccountNo() {
return accountNo;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
public class SychronizedThreadCase implements Runnable {
private Account account;
private double drawAmount;
public SychronizedThreadCase(Account account, double drawAmount) {
this.account = account;
this.drawAmount = drawAmount;
}
@Override
public void run() {
account.drawMoney(drawAmount);
account.drawMoney2(drawAmount);
}
public static void main(String[] args) {
Account account = new Account("001", 1000);
double drawAmount = 800;
SychronizedThreadCase stc = new SychronizedThreadCase(account, drawAmount);
new Thread(stc,"Thread-1").start();
new Thread(stc, "Thread-2").start();
}
}
5.2死锁:
在系统中出现多个同步监视器的情况下,当两个线程相互等待对方释放同步监视器时就会发生死锁,Java虚拟机没有监制,也没有采取措施来处理死锁的情况,所有多线程编程时应该采取措施避免死锁出现;
---------------------------------------------------------------------------------------------------------------------------------
6.线程通信
通常在实现线程通信时,我们借助于Object类提供的wait(), notify(), notifyAll() 三个方法,这三个方法并不属于Thread类,而是属于Object类;但这三个方法必须由同步监视器对象来调用,这可分成以下两种情况:
- 对于使用synchronized修饰的同步方法,因为该类的默认实例this就是同步监视器,所以可以在同步方法中直接调用这三个方法;
- 对于使用synchronized修饰的同步代码块,同步监视器时synchronized后括号里的对象,所以必须使用该对象调用这三个方法;
wait(): 导致当前线程等待,直到其他线程调用该同步监视器的 notify() 方法或者 notifyAll() 方法来唤醒该线程;调用 wait() 方法的当前线程会释放对该同步监视器的锁定;
notify(): 唤醒在此同步监视器上等待的单个线程,如果所有线程都在此同步监视器上等待,则会唤醒其中一个线程。选择是任意的。
notifyAll(): 唤醒在此同步监视器上等待的所有线程;
当程序不适用synchronized关键字来保证同步,而是直接使用Lock对象来保证同步,则系统中不存在隐式的同步监视器,也就是不能使用 wait() notify() notifyAll()方法来进行线程通信;
当使用Lock对象来保证同步时,Java提供了一个Condition类来保持协调,
await(): 类似于隐式同步监制器上的wait()方法;导致当前线程等待,直到其他线程调用该Condition的 signal 方法来唤醒它, 或者是signalAll()方法;
signal(): 唤醒在此Lock对象上等待的单个线程,如果所有线程都在该Lock对象上等待,则会唤醒其中的一个,选择是任意的;
signalAll(): 唤醒在此Lock对象上等待的所有线程;
/**
* 实现功能:存入一笔,取出一笔
* 练习:隐式锁sychronized(wait notify notifyAll)
* 显示锁lock(await signal signalAll)
* @author Administrator
*/
class BankAccount{
private String accountNo;
private double balance;
//标识账户中是否有人刚存入;
private boolean flag = false;
//使用显示的Lock对象
private final Lock lock = new ReentrantLock();
//获取指定的Lock对象对应的Condition
private final Condition cond = lock.newCondition();
public BankAccount(String accountNo, double balance) {
super();
this.accountNo = accountNo;
this.balance = balance;
}
//取钱:using synchronized
public synchronized void drawMoney(double drawAmount){
try {
if (!flag) {//没有人存入
wait();
} else {
if(drawAmount <= balance){
System.out.println(Thread.currentThread().getName()+" 取出金额 "+drawAmount);
balance -= drawAmount;
System.out.println("\t 账户余额为:"+balance);
flag = false;
notifyAll();
}else{
System.out.println("\t 账户余额不足");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//取钱:using lock
public void drawMoney2(double drawAmount){
lock.lock();
try {
if (!flag) {//没有人存入
cond.await();
} else {
if(drawAmount <= balance){
System.out.println(Thread.currentThread().getName()+" 取出金额 "+drawAmount);
balance -= drawAmount;
System.out.println("\t 账户余额为:"+balance);
flag = false;
//唤醒其他线程;
cond.signalAll();
}else{
System.out.println("\t 账户余额不足");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}
//存钱:using synchronized
public synchronized void depositMoney(double depositAmount){
try{
if(flag){
wait();
}else{
System.out.println(Thread.currentThread().getName() + " 存入金额 "
+ depositAmount);
balance += depositAmount;
System.out.println("\t 账户余额为:" + balance);
flag = true;
notifyAll();
}
}catch(InterruptedException e){
e.printStackTrace();
}
}
//存钱:using lock;
public void depositMoney2(double depositAmount){
lock.lock();
try{
if(flag){
cond.await();
}else{
System.out.println(Thread.currentThread().getName() + " 存入金额 "
+ depositAmount);
balance += depositAmount;
System.out.println("\t 账户余额为:" + balance);
flag = true;
cond.signalAll();
}
}catch(InterruptedException e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
public String getAccountNo() {
return accountNo;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
public double getBalance() {
return balance;
}
}
/**
* 模拟取钱线程;
* @author Administrator
*/
class DrawThread extends Thread{
private BankAccount account;
private double drawAmount;
public DrawThread(String name,BankAccount account, double drawAmount) {
super(name);
this.account = account;
this.drawAmount = drawAmount;
}
public void run(){
for(int i=0;i<30;i++){
account.drawMoney2(drawAmount);
}
}
}
/**
* 模拟存钱线程;
* @author Administrator
*/
class DepositThread extends Thread{
private BankAccount account;
private double depositAmount;
public DepositThread(String name,BankAccount account, double depositAmount) {
super(name);
this.account = account;
this.depositAmount = depositAmount;
}
public void run(){
for(int i=0;i<20;i++){
account.depositMoney2(depositAmount);
}
}
}
public class ThreadCommunicationCase {
public static void main(String[] args) {
int balance=1000;
BankAccount account = new BankAccount("001", balance);
new DrawThread("Draw-1", account, 800).start();
new DrawThread("Draw-2", account, 800).start();
new DepositThread("Deposit-1", account, 800).start();
new DepositThread("Deposit-2", account, 800).start();
new DepositThread("Deposit-3", account, 800).start();
}
}
---------------------------------------------------------------------------------------------------------
7.线程池
由于系统启动一个新线程的成本比较高,因为它涉及到与操作系统的交互。在这种情况下,尤其是程序中需要创建大量生存期很短暂的线程时,使用线程池会大大提高性能;
基本工作思路:
线程池在启动时即创建大量空闲的线程,程序将一个 Runnable 对象 或 Callable对象传递给线程池,线程池就会启动一个线程来执行它们的run() 或者 call()方法,当run() 或 call()方法执行结束后,该线程并不会死亡,而是再次返回线程池中成为空闲线程,等待执行下一个Runnable对象的 run()或call() 方法;
其次使用线程池可以有效地控制系统中并发线程的数量,当线程中包含大量并发线程时,会导致系统性能剧烈下降,甚至JVM崩溃,而线程池的最大线程参数可以控制系统中并发线程数不会超过这个最大数;
从Jdk 1.5开始,Java 内建支持线程池。 Java5新增了一个Executors工厂类来产生线程池,该工厂类主要包含如下几个 静态方法 来创建线程池:
1. 返回线程池为 ExecutorService对象的静态方法:ExecutorService代表尽快执行线程的线程池(只要线程池中有空闲的线程,就立即执行线程任务),程序只要将一个Runnalbe 或者 Callable对象提交给线程池(ExecutorService)的submit()方法,该线程就会尽快执行该任务;
- newCachedThreadPool(): 具有缓存功能的线程池,系统根据需要创建线程;
- newFixedThreadPool(int nThreads): 创建一个具有固定线程数的线程池;
- newSingleThreadExexutor(): 创建一个具有单线程的线程数;
- newWorkStealingPool(int parallelism): 创建持有足够的线程的线程池来支持给的的并行级别;
- newWorkStealingPool(): 该方法时前一个方法的简单化,如果当前的机器只有4个CPU,则目标并行级别设置为4,也就是相当于前一个方法传入4作为参数;
2.返回 ScheduledExecutorService线程池;它是ExecutorService线程池的子类,代表可以在指定延迟后或周期性地执行线程任务的线程池,主要使用的是schedule方法;
newScheduledThreadPool(int corePoolSize): 指定数量的线程池;
- newSingleThreadScheduledExecutor()
使用线程池来执行线程任务的步骤如下:
- 调用Executors类的 静态工厂方法创建一个 ExecutorService对象,该对象代表一个线程池;
- 创建Runnable实现类或Callable实现类的实例,作为线程执行任务。
- 调用ExecutorService对象的submit() 方法来提交Runnable实现类或Callable实现类;
- 当不想提交任何任务时,调用ExecutorService对象的shutdown()方法来关闭线程池。
----------------------------------------------------------------------------------------------------------
8. Java8 增强的ForkJoinPool
为了充分利用多核CPU的优势,可以把一个任务拆分成多个“小任务”,把多个小任务放到多个处理器核心上并行执行;当多个“小任务”执行完成之后,再将这些执行结果合并起来即可;Java 7 提供了ForkJoinPool来支持将一个任务拆分成多个“小任务”并行计算,再把多个小任务的结果合并成一个总的计算结果。ForkJoinPool是 ExecutorService的实现类,因此是一种特殊的线程池。
----------------------------------------------------------------------------------------------------------
9. ThreadLocal类
它代表一个线程局部变量,通过把数据放在ThreadLocal中就可以让每一个线程创建一个该变量的副本,从而避免并发访问的线程安全问题。其功能是为每一个使用该变量的线程都提供一个变量值得副本,使每一个线程都可以独立地改变自己的副本,而不会和其他线程的副本发生冲突,从线程的角度看就像每一个线程都完全拥有这个变量一样;java5以后为其增加了泛型支持;
ThreadLocal类中的方法:
- T get(): 返回此线程局部变量中当前线程的值;
- void set(T value): 设置此线程局部变量中当前线程副本中的值;
- void remove(): 删除此线程局部变量中当前线程的值
- T initialValue(): 返回此线程局部变量的当前线程的“初始值”。
package com.qian.obj;
class Index{
//定义一个ThreadLocal类型的变量,该变量将是一个线程局部变量
//每个线程都会保留该变量的一个副本
private ThreadLocal<String> localName = new ThreadLocal<String>();
public Index(String name){
this.localName.set(name);
System.out.println("---"+ this.localName.get());
}
public String getName(){
return localName.get();
}
public void setName(String name){
this.localName.set(name);
}
}
public class ThreadLocalCase extends Thread {
private Index index;
public ThreadLocalCase(Index index, String name){
super(name);
this.index = index;
}
public void run(){
for(int i=0;i<10;i++){
if(i==6){
index.setName(getName());
}
System.out.println(index.getName()+"账户的i值:"+i);
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Index index = new Index("初始名");
new ThreadLocalCase(index, "Thread-2").start();
new ThreadLocalCase(index, "Thread-1").start();
}
}
演示结果:
---初始名
null账户的i值:0
null账户的i值:0
null账户的i值:1
null账户的i值:2
null账户的i值:3
null账户的i值:4
null账户的i值:5
Thread-1账户的i值:6
Thread-1账户的i值:7
Thread-1账户的i值:8
Thread-1账户的i值:9
null账户的i值:1
null账户的i值:2
null账户的i值:3
null账户的i值:4
null账户的i值:5
Thread-2账户的i值:6
Thread-2账户的i值:7
Thread-2账户的i值:8
Thread-2账户的i值:9
---------------------------------------------------------------------------------------------------------
9. 线程安全的集合类
Java集合中所讲的ArrayList LinkedList 等都是线程不安全的,也就是说,当多个线程并发向这些集合中存取数据时,就可能会破坏这些集合的数据完整性。Collections 提供的类方法把这些集合包装成线程安全的集合,例如:
<T> Collection<T> synchronizedCollection(Collection<T> c);
HashMap m = Collections.synchronizedMap(new HashMap());
从 java 5 开始,在 java.util.concurrent包下提供了大量支持高效并发访问的集合接口和实现类;
以Concurrent 开头的集合类,如:ConcurrentHashMap ConcurrentSkipListMap....
以CopyOnWrite开头的集合类,如 CopyOnWriteArrayList, CopyOnWriteArraySet....