第13章 多线程

课程回顾

文件类: File
创建文件 createNewFile
删除文件 delete
判断文件是否存在: exists
获取名称: getName
获取父路径: getParent
获取绝对路径: getAbsolutePath
获取文件列表: listFile, listRoot,
是否可读: canRead
是否可写: canWrite
获取文件长度: length 字节数

文件读写:
①字节流: FileInputStream FileOutputStream BufferedInputStream BufferedOutputStream( 8192byte )
write( byte[] ) flush 刷出, close , read( byte[] )
②字符流: 操作的是字符
FileWriter 输出流 FileReader 输入流 BufferedReader BufferedWriter
write( char[] ) read( char[] ) readLine ready(就绪)
③ObjectInputStream ObjectOutputStream 对象输入输出流 序列化到磁盘, 反序列化(从磁盘文件转成对象) writeObject readObject Serialzable 标记接口; 读取注意结束标记 ; 写个null
④: 转换流 InputStreamReader 字节流—字符流 OutputStreamWriter 字符流—字节流
⑤: PrintWriter System.out.println out就是PrinterWriter 注意 flush刷出缓冲区
⑥: DataInputStream DataOutputStream 主要提供操作各种数据类型的方法 可以读写各种数据类型

线程的概念

进程: 正在运行中的程序; 一个进程包括多个线程, 线程是程序执行的最小单元;
进程: 高速公路, 线程是单个行车道; 双向8车道
在这里插入图片描述
在这里插入图片描述

迅雷多线程下载:
在这里插入图片描述

再从JVM的内存分布图看一下线程的结构:
在这里插入图片描述
在这里插入图片描述

胶片电影: 24帧/秒
查看java进程的线程数:
在这里插入图片描述

PID : process id 进程ID,进程编号

VisualVM 可视化的java虚拟机
在这里插入图片描述
观察自己项目的线程数 :
在这里插入图片描述

说明:
①: Finalizer线程: 垃圾回收线程 final finally finalize三个区别?

 protected void finalize() throws Throwable { }  //Object方法定义的

②: 自己程序的线程

main 线程 主线程
Thread-0 线程 子线程

③: RMI remote method invoke 远程方法调用
接收远程程序的网络连接;

创建线程的方式

java提供一个类封装线程的信息和操作; Thread类, java.lang.Thread ;

public class Thread extends Object implements Runnable
  • 创建一个新的执行线程有两种方法。 一个是将一个类声明为Thread的子类。 这个子类应该重写run类的方法Thread 。 然后可以分配并启动子类的实例。 例如,计算大于规定值的素数的线程可以写成如下:
  • 另一种方法来创建一个线程是声明实现类Runnable接口。 那个类然后实现了run方法。 然后可以分配类的实例,在创建Thread时作为参数传递,并启动。

继承Thread类

重写Thread类的run方法: run方法是线程执行的核心方法.但是启动线程是start方法; 如果调用run方法就是普通的方法调用,看不到多线程的效果.

package com.hanker;

public class MyThread extends Thread{

	@Override
	public void run() {
		System.out.println("开始执行我的线程:" + getName());
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("子线程结束");
	}
}
//================================
package com.hanker;

public class MyThreadTest {

	public static void main(String[] args) {
		System.out.println("main方法的线程: " + Thread.currentThread().getName());
		MyThread myThread = new MyThread();
		myThread.start();//启动线程
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("主线程结束");
	}

}

run方法是线程执行的主体: 写核心业务代码, 下载, 上传, 更新, 数据导出(同步), 耗时的业务开一个子线程去执行. 网络连接, 查杀病毒. 提交订单主业务, 发送一个手机验证码, 发送一个手机短信确认订单可以放在子线程. 物流信息做一个独立的线程.

实现 Runnable 接口

Runnable接口定义:

@FunctionalInterface
public interface Runnable {
    /**
     没有返回值
	  不能抛出异常
     */
    public abstract void run();
}

案例:

package com.hanker;

public class MyThread2 implements Runnable{

	@Override
	public void run() {
		System.out.println("开始网络下载..............");
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("数据下载完成..........");
	}
}
//=======================
package com.hanker;

public class MyThreadTest2 {

	public static void main(String[] args) {
		System.out.println("主线程:" + Thread.currentThread().getName());
		//本质线程还是Thread, 只不过MyThread2提供的是线程运行的主体,
		MyThread2 myThread2 = new MyThread2();
		Thread  th = new Thread(myThread2);
		th.start();
		System.out.println("主线程结束");
	}
}

实现Callable接口

@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     * 计算一个结果,如果不能执行业务,抛出一个异常,  
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

Callable接口和Runnable接口:

Callable可以有返回值,可以抛出异常, Runnable没有返回值,不能抛出异常
这个接口的运行都需要借助Thread类执行.

通过线程池创建子线程

见线程池模块

总结:
继承Thread类
①如果继承Thread类: 可以使用Thread类提供的方法
②如果继承Thread类: 不能继承其他类,因为java是单继承
③如果继承Thread类: 不能实现数据共享

实现Runnable接口
①: 还可以继承其他类
②: 不能使用Thread提供的方法
③: 可以实现数据共享

实现Callable接口
①:还可以继承其他类
②:不能是使用Thread类的方法
③: 可以获取线程的返回值

线程安全问题

数据共享问题:

package com.hanker2;

public class MyThread1 extends Thread{
	private int ticket = 5;//总票数
	
	public MyThread1(String name) {
		super(name);
	}
	@Override
	public void run() {
		String name  = getName();
		for(int i=0; i<5; i++)
			System.out.println(name+"成功卖票一张,剩余:"+ --ticket);
	}
}
//======================================
package com.hanker2;

public class MyThread1Test {

	public static void main(String[] args) {
		MyThread1 t1 = new MyThread1("迪丽热巴");
		MyThread1 t2 = new MyThread1("古力娜扎");
		MyThread1 t3 = new MyThread1("欧阳娜娜");

		//线程执行的顺序和start启动顺序不一定一致的;start只说明线程可以运行
		//具体运行那个线程看操作系统的调度
		t1.start();
		t2.start();
		t3.start();
	}
}

在这里插入图片描述
以上代码卖了15张票, 没有实现数据共享, 创建了三个独立的线程,但是创建三个对象,每个对象都有5张票.这是不符合业务的问题.可以通过实现Runnable接口解决问题.

package com.hanker2;

public class MyThread2 implements Runnable{
	private int ticket = 5;//总票数

	@Override
	public void run() {	
		String name = Thread.currentThread().getName();
		for(int i=0; i<5; i++) {
			if(ticket >0) {
				System.out.println(name+"成功卖票一张,剩余:"+ --ticket);
			}
		}
	}
}
//========================================
package com.hanker2;

public class MyThread2Test {

	public static void main(String[] args) {
		//只创建一个目标对象
		MyThread2 myThread = new MyThread2();
		//创建三个线程
		Thread t1 = new Thread(myThread,"迪丽热巴");
		Thread t2 = new Thread(myThread,"古力娜扎");
		Thread t3 = new Thread(myThread,"欧阳娜娜");
		//线程执行的顺序和start启动顺序不一定一致的;start只说明线程可以运行
		//具体运行那个线程看操作系统的调度
		t1.start();
		t2.start();
		t3.start();
	}
}

在这里插入图片描述

出现线程安全问题:
在这里插入图片描述

如何解决线程安全问题:
synchronized关键字, Lock接口及其实现类

synchronized: 同步

①同步方法
就是给该方法加上同步锁, 在一个时间点只能有一个线程访问.其他线程进不来. 整个方法就是单线程的, 这样就保证了线程的安全问题.

package com.hanker2;

public class MyThread3 implements Runnable{
	private int ticket = 5;//总票数

	@Override
	public synchronized void run() {	
		String name = Thread.currentThread().getName();
		for(int i=0; i<5; i++) {
			if(ticket >0) {
				System.out.println(name+"成功卖票一张,剩余:"+ --ticket);
			}
		}
	}
}
//==============================
package com.hanker2;

public class MyThread3Test {

	public static void main(String[] args) {
		//只创建一个目标对象
		MyThread3 myThread = new MyThread3();
		//创建三个线程
		Thread t1 = new Thread(myThread,"迪丽热巴");
		Thread t2 = new Thread(myThread,"古力娜扎");
		Thread t3 = new Thread(myThread,"欧阳娜娜");
		//线程执行的顺序和start启动顺序不一定一致的;start只说明线程可以运行
		//具体运行那个线程看操作系统的调度
		t3.start();
		t2.start();
		t1.start();
	}
}

同步方法虽然可以解决线程安全问题,但是整个方法都是单线程的; 会有效率问题. 真实业务我们最好只对可能出现线程安全的代码加锁, 而不是整个方法. 整个方法的粒度是相对比较大的. 所以就引入第二种方式,同步代码块. 顾明思议只对容易出现线程安全的局部的代码加锁.这样会相对的提高代码的执行效率.

②同步代码块

package com.hanker2;
//2. 同步代码块 锁是this当前对象, 其实任何对象都可以作为锁对象
public class MyThread4 implements Runnable{
	private int ticket = 5;//总票数

	@Override
	public  void run() {	
		String name = Thread.currentThread().getName();
		
		for(int i=0; i<5; i++) {
			synchronized(this) {
				if(ticket >0) {
					System.out.println(name+"成功卖票一张,剩余:"+ --ticket);
				}
			}
		}
	}
}
//================================
package com.hanker2;

public class MyThread4Test {

	public static void main(String[] args) {
		//只创建一个目标对象
		MyThread4 myThread = new MyThread4();
		//创建三个线程
		Thread t1 = new Thread(myThread,"迪丽热巴");
		Thread t2 = new Thread(myThread,"古力娜扎");
		Thread t3 = new Thread(myThread,"欧阳娜娜");
		//线程执行的顺序和start启动顺序不一定一致的;start只说明线程可以运行
		//具体运行那个线程看操作系统的调度
		t3.start();
		t2.start();
		t1.start();
	}
}

执行效果:
在这里插入图片描述

synchronized 对象锁和class锁

Lock接口及其实现类

lock: 锁

package com.hanker2;

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

//3. 使用lock解决线程安全问题
public class MyThread5 implements Runnable{
	private int ticket = 5;//总票数
	private Lock lock =  new ReentrantLock();//可重入锁
	@Override
	public  void run() {	
		String name = Thread.currentThread().getName();
		
		for(int i=0; i<5; i++) {
				try {
					lock.lock();//加锁
					if(ticket >0) {
						System.out.println(name+"成功卖票一张,剩余:"+ --ticket);
					}
				}finally {
					lock.unlock();//解锁
				}
			
		}
	}
}
//=========================================
package com.hanker2;

public class MyThread5Test {

	public static void main(String[] args) {
		//只创建一个目标对象
		MyThread5 myThread = new MyThread5();
		//创建三个线程
		Thread t1 = new Thread(myThread,"迪丽热巴");
		Thread t2 = new Thread(myThread,"古力娜扎");
		Thread t3 = new Thread(myThread,"欧阳娜娜");
		//线程执行的顺序和start启动顺序不一定一致的;start只说明线程可以运行
		//具体运行那个线程看操作系统的调度
		t3.start();
		t2.start();
		t1.start();
	}
}

线程的常用方法

sleep() //休眠
currentThread() //获取当前线程
getId() //获取线程的Id号
getName()//获取线程的名字
getPriority() //返回此线程的优先级。 
getState() //返回此线程的状态。 
getThreadGroup() //返回此线程所属的线程组。 
interrupt() //中断这个线程。 
interrupted() //测试当前线程是否中断。 
isAlive() //测试这个线程是否活着。     
isDaemon() //测试这个线程是否是守护线程。 
isInterrupted() //测试这个线程是否被中断。     
join() //等待这个线程死亡。 
setDaemon(boolean on) //将此线程标记为 daemon线程或用户线程。 
setName(String name) //线程的名称更改为等于参数 name 。     
setPriority(int newPriority) //此线程的优先级。     
sleep(long millis) //休眠指定时间
start() //此线程开始执行; Java虚拟机调用此线程的run方法。   
yield() //对调度程序的一个暗示,即当前线程愿意产生当前使用的处理器。 让给其他线程执行

线程的优先级

进程的优先级:
在这里插入图片描述

/**
* 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;
package com.hanker2;

public class MyThread5Test {

	public static void main(String[] args) {
		//只创建一个目标对象
		MyThread5 myThread = new MyThread5();
		//创建三个线程
		Thread t1 = new Thread(myThread,"迪丽热巴");
		Thread t2 = new Thread(myThread,"古力娜扎");
		Thread t3 = new Thread(myThread,"欧阳娜娜");
		//16进制 0--9 A(10) B(11) C(12) D(13) E(14) F(15)
		t1.setPriority(Thread.MAX_PRIORITY);
		t2.setPriority(2);
		t3.setPriority(Thread.MIN_PRIORITY);
		//线程执行的顺序和start启动顺序不一定一致的;start只说明线程可以运行
		//具体运行那个线程看操作系统的调度
		t2.start();
		t1.start();
		t3.start();
	}
}

守护线程

只要有前台线程执行,守护线程会一直执行, 如果没有前端线程,守护立即结束.

package com.hanker3;

public class MyThread1 extends Thread {

	@Override
	public void run() {
		System.out.println("子线程开始执行..."+getName());
		
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println("子线程结束执行...id="+getId()+"优先级:"+getPriority());
	}
}
//=======================
package com.hanker3;

public class MyThread1Test {

	public static void main(String[] args) {
		System.out.println("主 线程开始执行:"+Thread.currentThread().getName());
		MyThread1 t1 = new MyThread1();
		t1.setDaemon(true);//设置为守护线程
		t1.start();
		System.out.println("子线程是否存活111:"+t1.isAlive());//true
		System.out.println("子线程是否守护线程:"+t1.isDaemon());
		try {
			Thread.sleep(8000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("子线程是否存活222:"+t1.isAlive());//false
		System.out.println("主线程结束执行:"+Thread.currentThread().getState());
	}
}

join 加入

如果主线程想要获取子线程的返回值, 主线程就不能先结束; 所以要等待子线程先结束,再获取返回值.

package com.hanker3;

public class MyThread2 implements Runnable {
	private Integer num1;
	private Integer num2;
	
	public MyThread2(Integer num1, Integer num2) {
		super();
		this.num1 = num1;
		this.num2 = num2;
	}

	public void run() {
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {			
			e.printStackTrace();
		}
		int result = num1 + num2;
		System.out.println("子线程====="+result);
	}
}
//==========================
package com.hanker3;

public class MyThread2Test {
	//主线程先结束执行, 子线程还在计算, 如何等待子线程结束
	public static void main(String[] args) throws Exception {
		System.out.println("主线程开始执行===========");
		MyThread2 t2 = new MyThread2(100,300);
		Thread myThread = new Thread(t2);//匿名对象
		myThread.start();//启动线程
		myThread.join();
		System.out.println("主线程结束执行===========");
	}

}

线程的让步yield

package com.hanker3;

public class MyThreadA extends Thread {

	public MyThreadA(String name) {
		super(name);
	}
	@Override
	public void run() {
		for(int i=0; i<10; i++) {
			System.out.println("子线程:"+getName()+",i="+i);
			if(i == 5) {
				yield();//线程让步
			}
		}
	}
}
//==============================
package com.hanker3;

public class MyThreadB extends Thread {

	public MyThreadB(String name) {
		super(name);
	}
	@Override
	public void run() {
		for(int i=0; i<10; i++) {
			System.out.println("子线程:"+getName()+",i="+i);
			if(i == 8) {
				yield();//线程让步
			}
		}
	}
}
//===================================
package com.hanker3;

public class MyThreadABTest {

	public static void main(String[] args) {
		MyThreadA ta = new MyThreadA("线程A");
		MyThreadB tb = new MyThreadB("线程B");
		ta.start();
		tb.start();
	}
}

线程的状态

线程的状态是线程又开始执行到死亡的过程. 主要包括5个状态, 这5个状态都在Thread类中定义:

public enum State {
	NEW,   //新建
    RUNNABLE,//就绪
    BLOCKED,//阻塞
    WAITING,//等待
    TIMED_WAITING,//计时等待
    TERMINATED;//结束
}

在这里插入图片描述

package com.hanker3;

public class MyThreadState extends Thread {

	@Override
	public void run() {
		System.out.println("状态2: "+ getState());
	}
}
//===========================
package com.hanker3;

public class MyThreadStateTest {

	public static void main(String[] args) {
		MyThreadState myThread = new MyThreadState();
		System.out.println("状态1: " + myThread.getState());
		myThread.start();
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("状态3:" + myThread.getState());
	}

}

在这里插入图片描述
等待和计时等待

package com.hanker3;

public class MyThreadStateWait extends Thread {

	@Override
	public void run() {
		synchronized (this) {
			try {
				//wait(); //线程状态:WAITING
				wait(5000);//线程状态:TIMED_WAITING
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
	}
}
//=====================================
package com.hanker3;

public class MyThreadStateWaitTest {

	public static void main(String[] args) {
		MyThreadStateWait myThread = new MyThreadStateWait();
		myThread.start();
		try {
			Thread.sleep(2000);//等待子线程开始执行 
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("线程状态:" + myThread.getState());

	}

}

blocked阻塞状态

package com.hanker3;
/**
 * 模拟业务操作 
 */
public class EmpService {

	//添加员工信息
	public synchronized void addEmp() {		
		System.out.println("开始添加员工信息...."+System.currentTimeMillis());
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("结束添加员工信息...."+System.currentTimeMillis());
	}
}
//=======================================
package com.hanker3;
//线程1
class EmpThread1 extends Thread{
	private  EmpService empService;
	public EmpThread1(EmpService empService,String name) {
		super(name);
		this.empService = empService;
	}
	@Override
	public void run() {
		System.out.println(getName());
		empService.addEmp();
	}	
}
//线程2
class EmpThread2 extends Thread{
	private  EmpService empService;
	public EmpThread2(EmpService empService,String name) {
		super(name);
		this.empService = empService;
	}
	@Override
	public void run() {
		System.out.println(getName());
		empService.addEmp();
	}	
}
public class EmpServiceTest {

	public static void main(String[] args) throws Exception {
		System.out.println("主线程开始执行....");
		EmpService empService = new EmpService();
		EmpThread1 t1 = new EmpThread1(empService,"线程1");
		EmpThread1 t2 = new EmpThread1(empService,"线程2");
		t1.start();
		Thread.sleep(2000);
		t2.start();
		Thread.sleep(1000);
		System.out.println("线程2的状态:"+t2.getState());
		System.out.println("主线程结束执行....");
	}

}

在这里插入图片描述

volatile关键字

要想理解volatile关键字,得先了解下JAVA的内存模型,Java内存模型的抽象示意图如下:
在这里插入图片描述

从图中可以看出:

①每个线程都有一个自己的本地内存空间–线程栈空间???线程执行时,先把变量从主内存读取到线程自己的本地内存空间,然后再对该变量进行操作
②对该变量操作完后,在某个时间再把变量刷新回主内存
因此,就存在内存可见性问题,看一个示例程序:

public class RunThread extends Thread {

    private boolean isRunning = true;

    public boolean isRunning() {
        return isRunning;
    }

    public void setRunning(boolean isRunning) {
        this.isRunning = isRunning;
    }

    @Override
    public void run() {
        System.out.println("进入到run方法中了");
        while (isRunning == true) {
        }
        System.out.println("线程执行完成了");
    }
}

public class Run {
    public static void main(String[] args) {
        try {
            RunThread thread = new RunThread();
            thread.start();
            Thread.sleep(1000);
            thread.setRunning(false);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Run.java 第28行,main线程 将启动的线程RunThread中的共享变量设置为false,从而想让RunThread.java 第14行中的while循环结束。如果,我们使用JVM -server参数执行该程序时,RunThread线程并不会终止!从而出现了死循环!!

原因分析:

现在有两个线程,一个是main线程,另一个是RunThread。它们都试图修改 第三行的 isRunning变量。按照JVM内存模型,main线程将isRunning读取到本地线程内存空间,修改后,再刷新回主内存。而在JVM 设置成 -server模式运行程序时,线程会一直在私有堆栈中读取isRunning变量。因此,RunThread线程无法读到main线程改变的isRunning变量从而出现了死循环,导致RunThread无法终止。**这种情形,在《Effective JAVA》中,将之称为“活性失败”**解决方法,在第三行代码处用 volatile 关键字修饰即可。这里,它强制线程从主内存中取 volatile修饰的变量。

volatile private boolean isRunning = true;

扩展一下,当多个线程之间需要根据某个条件确定 哪个线程可以执行时,要确保这个条件在 线程 之间是可见的。因此,可以用volatile修饰。综上,volatile关键字的作用是:使变量在多个线程间可见(可见性)****二,volatile关键字的非原子性所谓原子性,就是某系列的操作步骤要么全部执行,要么都不执行。比如,变量的自增操作 i++,分三个步骤:
①从内存中读取出变量 i 的值
②将 i 的值加1
③将 加1 后的值写回内存
这说明 i++ 并不是一个原子操作。因为,它分成了三步,有可能当某个线程执行到了第②时被中断了,那么就意味着只执行了其中的两个步骤,没有全部执行。关于volatile的非原子性,看个示例:

public class MyThread extends Thread {
    public volatile static int count;

    private static void addCount() {
        for (int i = 0; i < 100; i++) {
            count++;
        }
        System.out.println("count=" + count);
    }

    @Override
    public void run() {
        addCount();
    }
}

public class Run {
    public static void main(String[] args) {
        MyThread[] mythreadArray = new MyThread[100];
        for (int i = 0; i < 100; i++) {
            mythreadArray[i] = new MyThread();
        }

        for (int i = 0; i < 100; i++) {
            mythreadArray[i].start();
        }
    }
}

MyThread类第2行,count变量使用volatile修饰
Run.java 第20行 for循环中创建了100个线程,第25行将这100个线程启动去执行 addCount(),每个线程执行100次加1
期望的正确的结果应该是 100*100=10000,但是,实际上count并没有达到10000
原因是:volatile修饰的变量并不保证对它的操作(自增)具有原子性。(对于自增操作,可以使用JAVA的原子类AutoicInteger类保证原子自增)
比如,假设 i 自增到 5,线程A从主内存中读取i,值为5,将它存储到自己的线程空间中,执行加1操作,值为6。此时,CPU切换到线程B执行,从主从内存中读取变量i的值。由于线程A还没有来得及将加1后的结果写回到主内存,线程B就已经从主内存中读取了i,因此,线程B读到的变量 i 值还是5
相当于线程B读取的是已经过时的数据了,从而导致线程不安全性。**这种情形在《Effective JAVA》中称之为“安全性失败”**综上,*仅靠volatile不能保证线程的安全性。(原子性)

三,volatile 与 synchronized 的比较

volatile主要用在多个线程感知实例变量被更改了场合,从而使得各个线程获得最新的值。它强制线程每次从主内存中讲到变量,而不是从线程的私有内存中读取变量,从而保证了数据的可见性。关于synchronized,可参考:JAVA多线程之Synchronized关键字–对象锁的特点
比较:
①volatile轻量级,只能修饰变量。synchronized重量级,还可修饰方法
②volatile只能保证数据的可见性,不能用来同步,因为多个线程并发访问volatile修饰的变量不会阻塞。
synchronized不仅保证可见性,而且还保证原子性,因为,只有获得了锁的线程才能进入临界区,从而保证临界区中的所有语句都全部执行。多个线程争抢synchronized锁对象时,会出现阻塞。
四,线程安全性
线程安全性包括两个方面,①可见性。②原子性。
从上面自增的例子中可以看出:仅仅使用volatile并不能保证线程安全性。而synchronized则可实现线程的安全性。

等待唤醒机制

在多线程交互的时候用到等待唤醒机制, Object类, wait , wait( 时间 ) , notify , notifyAll 通知所有的线程.等待唤醒都要在同步代码中, 必须有锁.; 如果没有锁,代码可以正常执行,不需要等待,不用等待就不用唤醒.
只通知一个线程:

package com.hanker4;

public class ThreadWait extends Thread {

	private Object lock ;
	public ThreadWait(String name,Object lock) {
		super(name);
		this.lock = lock;
	}
	
	@Override
	public void run() {
		System.out.println(getName()+"开始等待:"+System.currentTimeMillis());
		synchronized (lock) {
			try {			
				lock.wait();							
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println(getName()+"结束等待:"+System.currentTimeMillis());
	}
}
//=================================================
package com.hanker4;

public class ThreadNotify extends Thread {

	private Object lock;
	public ThreadNotify(String name,Object lock) {
		super(name);
		this.lock = lock;
	}
	
	@Override
	public void run() {
		System.out.println(getName()+"开始通知:"+System.currentTimeMillis());
		synchronized (lock) {
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			lock.notify();//通知等待的线程			
		}		
		System.out.println(getName()+"结束结束:"+System.currentTimeMillis());
	}
	
}
//================================
package com.hanker4;

public class ThreadTest {

	public static void main(String[] args) throws InterruptedException {
		Object lock = new Object();
		
		ThreadWait  wait = new ThreadWait("等待线程",lock);
		wait.start();
		
		ThreadNotify notify=new ThreadNotify("唤醒线程",lock);
		notify.start();

	}

}

通知多个线程:

//等待线程
package com.hanker4;

public class ThreadWait extends Thread {

	private Object lock ;
	public ThreadWait(String name,Object lock) {
		super(name);
		this.lock = lock;
	}
	
	@Override
	public void run() {
		System.out.println(getName()+"开始等待:"+System.currentTimeMillis());
		synchronized (lock) {
			try {			
				lock.wait();							
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println(getName()+"结束等待:"+System.currentTimeMillis());
	}
}

//==============通知线程=========
package com.hanker4;

public class ThreadNotifyAll extends Thread {

	private Object lock;
	public ThreadNotifyAll(String name,Object lock) {
		super(name);
		this.lock = lock;
	}
	
	@Override
	public void run() {
		System.out.println(getName()+"开始通知:"+System.currentTimeMillis());
		synchronized (lock) {
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			lock.notifyAll();//通知等待的线程			
		}		
		System.out.println(getName()+"结束结束:"+System.currentTimeMillis());
	}
	
}
//============测试类=============
package com.hanker4;

public class ThreadTestNotifyAll {

	public static void main(String[] args) throws InterruptedException {
		Object lock = new Object();
		
		ThreadWait  wait = new ThreadWait("等待线程-1",lock);
		wait.start();
		

		ThreadWait  wait2 = new ThreadWait("等待线程-2",lock);
		wait2.start();
		

		ThreadWait  wait3 = new ThreadWait("等待线程-3",lock);
		wait3.start();
		
		ThreadNotifyAll notifyThread = new ThreadNotifyAll("通知所有线程",lock);
		notifyThread.start();

	}

	private static void test1() {
		Object lock = new Object();
		
		ThreadWait  wait = new ThreadWait("等待线程-1",lock);
		wait.start();
		

		ThreadWait  wait2 = new ThreadWait("等待线程-2",lock);
		wait2.start();
		

		ThreadWait  wait3 = new ThreadWait("等待线程-3",lock);
		wait3.start();
		//普通notify, 从执行效果可以看出通知是随机
		ThreadNotify notify=new ThreadNotify("唤醒线程",lock);
		notify.start();
		/**
		 * 等待线程-2开始等待:1577758057211
		等待线程-1开始等待:1577758057211
		等待线程-3开始等待:1577758057212
		唤醒线程开始通知:1577758057212
		等待线程-1结束等待:1577758060212
		唤醒线程结束结束:1577758060212
		 */
	}

}

执行结果:
在这里插入图片描述

死锁: 背景是两个线程,分别想获取对方的锁, 独木桥, 大家都不让路. 倚天剑和屠龙刀, 屠龙刀里面有武穆遗书 ; 倚天剑有九阴真经; 要想获取这两个秘籍要互相砍, 出现嵌套锁

案例:

package com.hanker4;
//存钱线程
class ThreadSave extends Thread{
	private Account account;
	public ThreadSave(Account account,String name) {
		super(name);
		this.account = account;
	}
	@Override
	public void run() {
		System.out.println(getName()+"开始执行");
		//调用存钱方法
		int money = account.save(100);
		System.out.println("存钱成功: "+money);
		System.out.println(getName()+"结束执行");
	}
}
//取钱线程
class ThreadTake extends  Thread{
	private Account  account;
	public ThreadTake(Account account,String name) {
		super(name);
		this.account = account;
	}
	public void run() {
		System.out.println(getName()+"开始执行");
		//调用取钱方法
		int money = account.take(200);
		System.out.println("取钱成功: "+money);
		System.out.println(getName()+"结束执行");
		
	}
	
}

public class Account {
	private int balance = 1000;//余额
	private Object lock1 = new Object();//第一把锁
	private Object lock2 = new Object();//第二把锁
	
	//存钱
	public int save(int money) {
		//保险柜有两个门
		synchronized(lock1) {
			System.out.println("存钱进入第一个门");
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized(lock2) {
				System.out.println("存钱进入第二个门");
				balance += money;
			}			
		}		
		return balance;
	}
	
	
	//取钱
	public int take(int money) {
		synchronized(lock2) {
			System.out.println("取钱进入第二个门");
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized (lock1) {
				System.out.println("取钱进入第一个门");
				balance -= money;
			}
			
		}
		return balance;
	}
	
}
//=========================
package com.hanker4;

public class AccountTest {

	public static void main(String[] args) {
		Account account = new Account();
		ThreadSave ts = new ThreadSave(account,"存钱线程");
		ThreadTake tt  =new ThreadTake(account,"取钱线程");
		
		ts.start();
		tt.start();
	}
}

执行效果:
在这里插入图片描述

如何解决线程死锁的问题: 保证锁的顺序一致,

![19](images/19.jpg)//取钱线程也是从锁1开始
public int take(int money) {
    synchronized(lock1) {
        System.out.println("取钱进入第二个门");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (lock2) {
            System.out.println("取钱进入第一个门");
            balance -= money;
        }

    }
    return balance;
}

在这里插入图片描述

生产者和消费者机制

厨师负责做菜, 服务员负责上菜, 厨师做好菜放在窗口上, 服务员从窗口取菜. 如果窗口没有菜服务员要等待. 厨师做好菜要通知服务员.
在这里插入图片描述

案例:

package com.hanker5;
class ThreadSet implements Runnable{
	private Resource r;
	public ThreadSet(Resource r) {
		this.r = r;
	}
	@Override
	public void run() {
		while(true) {
			try {
				r.set("酸菜鱼");
			} catch (Exception e) {
				e.printStackTrace();
			}
		}		
	}	
}
class ThreadGet implements Runnable{
	private Resource r;
	public ThreadGet(Resource r) {
		this.r = r;
	}
	@Override
	public void run() {
		while(true) {
			try {
				r.get();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
	}
	
}

//表示资源类
public class Resource {

	private String [] data = new String[1];//操作的空间(窗口)
	private int count;//计数器
	private Object lock = new Object();
	
	//生产方法
	public void set(String name) throws Exception {
		synchronized (lock) {
			//窗口是否为空
			if(data[0]!=null) {
				lock.wait();//等待
			}
			data[0] = name + count;//生产数据
			System.out.println("生产者产生数据:" + data[0]);
			count++;
			Thread.sleep(1000);
			lock.notify();
		}		
	}
	
	//消费方法
	public void get() throws Exception {
		synchronized (lock) {
			if(data[0] == null) {
				lock.wait();//没有数据,等待
			}
			System.out.println("消费者消费数据:" + data[0]);
			data[0] = null;//置为空,
			count++;
			Thread.sleep(1000);
			lock.notify();//通知生产者线程
		}
	}
}
//========================
package com.hanker5;

public class Test1 {

	public static void main(String[] args) {
		Resource r = new Resource();
		ThreadSet ts = new ThreadSet(r);
		ThreadGet tg = new ThreadGet(r);
		
		new Thread(ts).start();
		new Thread(tg).start();
	}
}

执行效果:
在这里插入图片描述

多个生产者和多个消费者线程

package com.hanker6;
class ThreadSet implements Runnable{
	private Resource r;
	public ThreadSet(Resource r) {
		this.r = r;
	}
	@Override
	public void run() {
		while(true) {
			try {
				r.set("酸菜鱼");
			} catch (Exception e) {
				e.printStackTrace();
			}
		}		
	}	
}
class ThreadGet implements Runnable{
	private Resource r;
	public ThreadGet(Resource r) {
		this.r = r;
	}
	@Override
	public void run() {
		while(true) {
			try {
				r.get();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
	}
	
}

//表示资源类
public class Resource {

	private String [] data = new String[1];//操作的空间(窗口)
	private int count;//计数器
	private Object lock = new Object();
	
	//生产方法
	public void set(String name) throws Exception {
		synchronized (lock) {
			//窗口是否为空
			if(data[0]!=null) {
				lock.wait();//等待
			}
			data[0] = name + count;//生产数据
			String tname = Thread.currentThread().getName();
			System.out.println(tname+"产生数据:" + data[0]);
			count++;
			Thread.sleep(1000);
			lock.notifyAll();//通知所有等待线程
		}		
	}
	
	//消费方法
	public void get() throws Exception {
		synchronized (lock) {
			if(data[0] == null) {
				lock.wait();//没有数据,等待
			}
			String tname = Thread.currentThread().getName();
			System.out.println(tname+"消费数据:" + data[0]);
			data[0] = null;//置为空,
			Thread.sleep(1000);
			lock.notifyAll();//通知所有等待线程
		}
	}
}

//===========================
package com.hanker6;
//多个生成者,多个消费者
public class Test1 {

	public static void main(String[] args) {
		Resource r = new Resource();
		ThreadSet ts = new ThreadSet(r);
		ThreadGet tg = new ThreadGet(r);
		
		new Thread(ts,"生产者线程1").start();
		new Thread(ts,"生产者线程2").start();
		new Thread(tg,"消费者线程1").start();
		new Thread(tg,"消费者线程2").start();
	}
}

执行效果:
在这里插入图片描述

从中可以看出问题: 生产者生成一个数据, 结果通知两个消费者去消费,所以第二个消费者为空.
解决办法一: 追加自旋锁

package com.hanker7;
class ThreadSet implements Runnable{
	private Resource r;
	public ThreadSet(Resource r) {
		this.r = r;
	}
	@Override
	public void run() {
		while(true) {
			try {
				r.set("酸菜鱼");
			} catch (Exception e) {
				e.printStackTrace();
			}
		}		
	}	
}
class ThreadGet implements Runnable{
	private Resource r;
	public ThreadGet(Resource r) {
		this.r = r;
	}
	@Override
	public void run() {
		while(true) {
			try {
				r.get();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}		
	}	
}

//表示资源类
public class Resource {
	private String [] data = new String[1];//操作的空间(窗口)
	private int count;//计数器
	private Object lock = new Object();
	
	//生产方法
	public void set(String name) throws Exception {
		synchronized (lock) {
			//窗口是否为空
			while(data[0]!=null) {
				lock.wait();//等待
			}
			data[0] = name + count;//生产数据
			String tname = Thread.currentThread().getName();
			System.out.println(tname+"产生数据:" + data[0]);
			count++;
			Thread.sleep(1000);
			lock.notifyAll();//通知所有等待线程
		}		
	}
	
	//消费方法
	public void get() throws Exception {
		synchronized (lock) {
			while(data[0] == null) {
				lock.wait();//没有数据,等待
			}
			String tname = Thread.currentThread().getName();
			System.out.println(tname+"消费数据:" + data[0]);
			data[0] = null;//置为空,
			Thread.sleep(1000);
			lock.notifyAll();//通知所有等待线程
		}
	}
}
//=====================
package com.hanker7;
//多个生成者,多个消费者 
//通过自旋锁解决多次消费同一个数据的问题
public class Test1 {

	public static void main(String[] args) {
		Resource r = new Resource();
		ThreadSet ts = new ThreadSet(r);
		ThreadGet tg = new ThreadGet(r);
		
		new Thread(ts,"生产者线程1").start();
		new Thread(ts,"生产者线程2").start();
		new Thread(tg,"消费者线程1").start();
		new Thread(tg,"消费者线程2").start();
	}
}

执行效果:
在这里插入图片描述

解决办法二: 可重入锁

使用Condition条件通知, 理想办法是:生产者唤醒消费者, 消费者线程唤醒生产者.

ReentrantLock(重入锁):效果synchronized一样,都可以同步执行,lock方法获得锁,unlock方法释放锁
Condition类的awiat方法和Object类的wait方法等效
Condition类的signal方法和Object类的notify方法等效
Condition类的signalAll方法和Object类的notifyAll方法等效
synchronized是可以实现加锁功能;但是语义不明确,并没有看到上锁和开锁的代码,jdk1.5新增加Lock接口及其实现类.

在这里插入图片描述

读锁: 只是读取数据,不会引发并发异常, 可以多线程执行,
写锁: 写数据, 如果多线程执行有可能会引发异常, 两个写锁是互斥的,同时只能执行一个上锁的代码块.

package com.hanker8;

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

class Producer implements Runnable{
	private Resource r;
	public Producer(Resource r) {
		this.r = r;
	}
	@Override
	public void run() {
		while(true) {
			try {
				r.set("酸菜鱼");
			} catch (Exception e) {
				e.printStackTrace();
			}
		}		
	}	
}
class Consumer implements Runnable{
	private Resource r;
	public Consumer(Resource r) {
		this.r = r;
	}
	@Override
	public void run() {
		while(true) {
			try {
				r.get();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}		
	}	
}

//表示资源类
public class Resource {
	private String [] data = new String[1];//操作的空间(窗口)
	private int count;//计数器
	private Lock lock = new ReentrantLock();//可重入锁
	private Condition con_producer = lock.newCondition();//生成者通知
	private Condition con_consumer = lock.newCondition();//消费者通知
	
	//生产方法
	public void set(String name) throws Exception {
		lock.lock();//上锁
		try {
			//窗口是否为空
			while(data[0]!=null) {
				con_producer.await();//等待
			}
			data[0] = name + count;//生产数据
			String tname = Thread.currentThread().getName();
			System.out.println(tname+"产生数据:" + data[0]);
			count++;
			Thread.sleep(1000);
			con_consumer.signalAll();//通知消费者线程
		}finally {
			lock.unlock();
		}	
	}
	
	//消费方法
	public void get() throws Exception {
		lock.lock();//上锁
		try{			
			while(data[0] == null) {
				con_consumer.await();//没有数据,等待
			}
			String tname = Thread.currentThread().getName();
			System.out.println(tname+"消费数据:" + data[0]);
			data[0] = null;//置为空,
			Thread.sleep(1000);
			con_producer.signalAll();//通知所有生产者线程
		}finally {
			lock.unlock();
		}
	}
}
//=================================
package com.hanker8;
//多个生成者,多个消费者 
//通过可重入锁解决多次消费同一个数据的问题
public class Test1 {

	public static void main(String[] args) {
		Resource r = new Resource();
		Producer ts = new Producer(r);
		Consumer tg = new Consumer(r);
		
		new Thread(ts,"生产者线程1").start();
		new Thread(ts,"生产者线程2").start();
		new Thread(tg,"消费者线程1").start();
		new Thread(tg,"消费者线程2").start();
	}
}

执行效果:
在这里插入图片描述

线程池

我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?在Java中可以通过线程池来达到这样的效果。今天我们就来详细讲解一下Java的线程池,首先我们从最核心的ThreadPoolExecutor类中的方法讲起,然后再讲述它的实现原理,接着给出了它的使用示例,最后讨论了一下如何合理配置线程池的大小。

公交公司一般都会为一路车准备多辆汽车;

线程池的分类

所有实现了ExecutorService接口(Executor的子接口)的实现类都是线程池,可以分为三大类

  • ForkJoinPool
  • ScheduledThreadPoolExecutor
  • ThreadPoolExecutor

具体的线程池,在工具类Executors中预创建了六小类

实现了ThreadPoolExecutor类:

  • ExecutorService newCachedThreadPool():无界线程池
  • ExecutorService newFixedThreadPool():有界线程池
  • ExecutorService newSingleThreadExecutor():单一线程池

实现了ScheduledThreadPoolExecutor类:

  • ScheduledExecutorService newSingleThreadScheduledExecutor()
  • ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

实现了ForkJoinPool类:

  • ExecutorService newWorkStealingPool()

当然我们也可以自定义线程池

常用线程池(ThreadPoolExecutor)示例

现在有一个任务WorkTask

package com.hanker9;

public class WorkTask implements  Runnable {

	@Override
	public void run() {
		int random =(int) (Math.random()*10);
		try {
			Thread.sleep(random * 1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()+"结束执行 ..");
	}
}
//========================
package com.hanker9;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CachedThreadPoolTest {
	public static void main(String[] args) {
		//创建一个无界线程池
		ExecutorService pool = Executors.newCachedThreadPool();
		System.out.println(pool.getClass().getName());
		//向线程池添加线程
		for(int i=0; i<20; i++) {
			pool.execute(new WorkTask());
		}
		//关闭线程池
		pool.shutdown();
	}
}

无界线程池,最多可创建Integer.MAX_VALUE个线程,运行结果没有重复的线程号.
在这里插入图片描述

线程池的添加过程:

//1.线程池的实现类: 
java.util.concurrent.ThreadPoolExecutor
pool.execute(new WorkTask()); //添加线程,调用下面方法
 public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
     int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))    //添加线程 
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

addWork方法

private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;   //线程被封装成Worker对象
        try {
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w); //关键代码: workers是什么?   可以推断是集合
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

workers的定义:

/**
* Set containing all worker threads in pool. Accessed only when
* holding mainLock.
*/
private final HashSet<Worker> workers = new HashSet<Worker>();

创建有界线程池:

public class FixedThreadPoolTest {
	public static void main(String[] args) {
		//创建一个有界线程池
		ExecutorService exec = Executors.newFixedThreadPool(3);//查看源码
		for(int i=0;i<20;i++){
			exec.execute(new WorkTask());
		}
		exec.shutdown();		
	}
}

在这里插入图片描述

3个线程执行20个任务
源码分析: Executors.newFixedThreadPool(3)调用如下方法

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads,   //3
                                      nThreads,   //3
                                      0L,         //0
                                      TimeUnit.MILLISECONDS,//时间单位: 毫秒
                                      new LinkedBlockingQueue<Runnable>()//阻塞链表队列
                                     );
    }

跟踪构造方法:

public ThreadPoolExecutor(    int corePoolSize,    //核心线程池大小=3
                              int maximumPoolSize, //最大线程池大小=3
                              long keepAliveTime,  //存活时间: 0秒 
                              TimeUnit unit,       //时间单位: 毫秒
                              BlockingQueue<Runnable> workQueue) {  //阻塞队列
        this(corePoolSize, 
             maximumPoolSize, 
             keepAliveTime, 
             unit, 
             workQueue,
             Executors.defaultThreadFactory(), 
             defaultHandler);//又调用另一个构造方法
    }

继续跟踪构造方法:

public ThreadPoolExecutor(    int corePoolSize,  //3
                              int maximumPoolSize,//3
                              long keepAliveTime,//0
                              TimeUnit unit,//毫秒
                              BlockingQueue<Runnable> workQueue,//队列数据结构
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

SingleThreadExecutor

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class SingleThreadExecutorTest {
	public static void main(String[] args) {
		ExecutorService exec = Executors.newSingleThreadExecutor();
		for(int i=0;i<20;i++){
			exec.execute(new WorkTask());
		}
		exec.shutdown();
	}
}

始终1个线程执行所有任务
在这里插入图片描述

ThreadPoolExecutor类分析

三种线程池(返回ThreadPoolExecutor)构造方法

以下3种线程池均实现了ThreadPoolExecutor类

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

ThreadPoolExecutor中的成员变量

以上3中线程池都是使用了ThreadPoolExecutor中的一种构造方法:

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
                     long keepAliveTime, TimeUnit unit, 
                        BlockingQueue<Runnable> workQueue) 

①int corePoolSize:核心线程数,即使空闲也仍保留在池中的线程数
②int maximumPoolSize:最大线程数
③BlockingQueue workQueue:阻塞队列,在执行任务之前用于保存任务的队列
④long keepAliveTime:保持激活时间,当线程数大于核心数时,这是多余的空闲线程在终止之前等待新任务的最大时间
⑤TimeUnit unit:keepAliveTime的时间单位

核心线程是指线程池一启动的时候分配的线程,只要任务数量<核心线程数的任务都会使用核心线程去执行,并且执行完是不回收的;如果任务数量>核心线程数量,就会进入阻塞队列,入队之后队列里的任务就由非核心线程来执行;非核心线程如果在空闲后的keepAliveTime内还没活就会被回收。

三种线程池(返回ThreadPoolExecutor类)分析

newCachedThreadPool:核心线程数为0,最大线程数为Integer.MAX_VALUE,所以称之为无界线程池;假设一次执行20个任务,由于corePoolSize为0,所以20个任务全会进入阻塞队列BlockingQueue,启动新线程执行队列中的任务,最多可以启动20个任务,如果20个任务都执行完毕,从线程闲置时开始倒计时60s,超时则关闭线程。

FixedThreadPool:因为核心线程数是传入且固定的,所以称为有界线程池,一般在后台执行一些辅助性的任务,最大线程数与核心线程数相等;假设核心线程数为3,一次执行20个任务:先启动3个线程,剩下17个任务会进入BlockingQueue排队;因为核心线程数数=最大线程数,所以keepAliveTime这个参数是没有意义的。

SingleThreadExecutor:线程池中只有1个线程,超过1个任务进入阻塞队列,与FixedThreadPool类似,只不过nThread=1。比如说服务端需要有一个线程不断监听客户端发送来的socket,一旦出现异常会新建一个线程来替换。

我们可以来模拟一下监听任务ListeningTask并出现异常

package com.hanker9;
import javax.management.RuntimeErrorException;
 
public class ListeningTask implements Runnable{
	private int i;
	public ListeningTask(int i){
		this.i = i;
	}
	public void run() {
		System.out.println("thread"+Thread.currentThread().getId()+" is started...task" + i);
		try {
			Thread.sleep(1000);
		} catch (Exception e) {
		}
		if(i==3){
			throw new RuntimeErrorException(null,"exception...");
		}
		System.out.println("thread"+Thread.currentThread().getId()+" is over...task"+i);
	}
}

主方法

package com.hanker9;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class test {
	public static void main(String[] args) {
		ExecutorService exec = Executors.newSingleThreadExecutor();
		for(int i=0;i<20;i++){
			exec.execute(new ListeningTask(i));
		}
		exec.shutdown();
	}
}

运行结果
在这里插入图片描述

可见一开始全由thread8来执行,出现异常后程序并没有终止,由另一个线程thread10接管任务,这就是SingleThreadExecutor在监听上的用武之地

execute与submit方法

Executor接口中有一个方法
在这里插入图片描述

而其子接口ExecutorService中有3个重载的submit方法
在这里插入图片描述

submit是基方法Executor.execute(Runnable)的延伸,通过创建并返回一个Future类对象可用于取消执行和/或等待完成。

Future类中的方法
在这里插入图片描述

用submit方法举个例子,获取多线程运行结果

package com.hanker9;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
 
 
public class test {
	public static void main(String[] args) {
		ExecutorService exec = Executors.newCachedThreadPool();
		for(int i=0;i<20;i++){
			Future<String> f = exec.submit(new TaskResult(i));
			try {
				String v=f.get();
				System.out.println(v);
			} catch (Exception e) {
			}
		}
	}
}
 
class TaskResult implements Callable<String>{
	private int i;
	public TaskResult(int i){
		this.i=i;
	}
	public String call(){
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return Thread.currentThread().getId()+"'s result is "+i;
	}
}

运行结果,这是execute方法无法实现的
在这里插入图片描述

ForkJoinPool

背景:ForkJoinPool的优势在于,可以充分利用多cpu,多核cpu的优势,把一个任务拆分成多个“小任务”,把多个“小任务”放到多个处理器核心上并行执行;当多个“小任务”执行完成之后,再将这些执行结果合并起来即可。这种思想值得学习。
Fork/Join 框架:就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行 join 汇总。
在这里插入图片描述

/* @since 1.7
 *  @author Doug Lea
 */
public abstract class ForkJoinTask<V> implements Future<V>, Serializable {
...
}
  • 采用 “工作窃取”模式(work-stealing):当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。
    在这里插入图片描述

  • 相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的处理方式上.在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态。而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行。那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行.这种方式减少了线程的等待时间,提高了性能。

Java7 提供了ForkJoinPool来支持将一个任务拆分成多个“小任务”并行计算,再把多个“小任务”的结果合并成总的计算结果。ForkJoinPool是ExecutorService的实现类,因此是一种特殊的线程池。使用方法:创建了ForkJoinPool实例之后,就可以调用ForkJoinPool的submit(ForkJoinTask task) 或invoke(ForkJoinTask task)方法来执行指定任务了。

其中ForkJoinTask代表一个可以并行、合并的任务。ForkJoinTask是一个抽象类,它还有两个抽象子类:RecusiveAction和RecusiveTask。其中RecusiveTask代表有返回值的任务,而RecusiveAction代表没有返回值的任务。

下面的UML类图显示了ForkJoinPool、ForkJoinTask之间的关系:
在这里插入图片描述

案列一:通过多线程分多个小任务进行打印数据 无返回值的

package com.hanker9;
 
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.TimeUnit;
 
/**
 * RecursiveAction  无返回值的
 */
public class PrintTask extends RecursiveAction {
 
	private static final long serialVersionUID = 1L;
	private static final int INDEX = 50;
	private int start;
	private int end;
 
	public PrintTask(int start, int end) {
		this.start = start;
		this.end = end;
	}

	@Override
	protected void compute() {
		if (end - start < INDEX) {
			for (int i = start; i < end; i++) {
				System.out.println(Thread.currentThread().getName() + "----"+ i);
			}
		} else {
			int middle = (end + start) / 2;
			PrintTask taskLeft = new PrintTask(start, middle);
			PrintTask taskRight = new PrintTask(middle, end);
			//taskLeft.invoke();执行给定的任务,在完成后返回其结果。
			//并行执行两个“小任务”
			/*			
			taskLeft.fork();
			taskRight.fork();
			*/
			invokeAll(taskLeft, taskRight);//执行给定的任务
		}
	}
 
	public static void main(String[] args) throws InterruptedException {
		PrintTask task = new PrintTask(0, 300);
		ForkJoinPool pool = new ForkJoinPool();
		pool.submit(task);
		pool.awaitTermination(2, TimeUnit.SECONDS);//阻塞2秒
		pool.shutdown();
	}
}

运行结果

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

本机电脑cpu4核,通过上面观察线程名称,可以看出4个cpu都在进行

案列二:通过多线程分多个小任务进行数据累加 返回结果集

package com.hanker9;
import java.util.Random;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.TimeUnit;

public class PrintTask2 {
	public static void main(String[] args) throws Exception {
		int[] arr = new int[200];
		Random r = new Random();
		int tempSum = 0;// 普通总数
		for (int i = 0; i < arr.length; i++) {
			tempSum += (arr[i] = r.nextInt(10));
		}
		System.out.println("普通总数结果为:" + tempSum);
 
		ForkJoinPool pool = new ForkJoinPool();
		MyTask task = new MyTask(0, arr.length, arr);
		Future<Integer> sum = pool.submit(task);
		// get 如果需要,等待计算完成,然后检索其结果。
		System.out.println("多线程的执行结果:" + sum.get());
		pool.awaitTermination(2, TimeUnit.SECONDS);
		pool.shutdown(); // 关闭线程池
	}
}
 
class MyTask extends RecursiveTask<Integer> {
	private static final long serialVersionUID = 1L;
	private static final int INDEX = 50;// 每个小任务执行50个
	private int start;
	private int end;
	private int arr[];
	public MyTask(int start, int end, int[] arr) {
		this.start = start;
		this.end = end;
		this.arr = arr;
	}
	@Override
	protected Integer compute() {
		int sum = 0;
		if (end - start < INDEX) {
			for (int i = start; i < end; i++) {
				sum += arr[i];
			}
			return sum;
		} else {
			int middle = (end + start) / 2;
			MyTask taskLeft2 = new MyTask(start, middle, arr);
			MyTask taskRight2 = new MyTask(middle, end, arr);
			/*invokeAll(taskLeft2, taskRight2);*/
			taskLeft2.fork();
			taskRight2.fork();
			int leftValue = taskLeft2.join();// 当计算完成时返回计算结果。
			int rightValue = taskRight2.join();
			return leftValue + rightValue;
		}
	}
}

运行结果:
在这里插入图片描述

发布了91 篇原创文章 · 获赞 43 · 访问量 14万+

猜你喜欢

转载自blog.csdn.net/kongfanyu/article/details/103810371