多线程
一个进程可以有多个线程,这些线程之间并发执行;
Java中有三种方法实现多线程:
- 继承 Thread 类,重写 run() 方法;
- 实现 Runnable 接口 , 重写 run() 方法;
- 实现 Callable 接口 , 重写 call() 方法, 使用 Future 来 获取 call() 方法的返回结果;
Thread类
java.lang 包下的线程类, 实现多线程的方法:
- 创建一个多线程类 继承 Thread, 重写 run() 方法;
- 实例化子类,通过 start() 方法 启动线程;
代码示例:
// 创建一个类 继承 Thread
public class MyThread extends Thread{
// 创建子类的 有参构造方法;
public MyThread(String name){
super(name);
}
//重写 run()方法
public void run(){
int i = 0;
while(i++ < 5) {
System.out.println(Thread.currentThread().getName() + "的 run()方法在运行"); //currentThread()获得当前线程,getName()获得当前 //线程名
}
}
}
public class Application {
public static void main(String[] args) {
MyThread t1 = new MyThread("t1");
MyThread t2 = new MyThread("t2");
t1.start(); //启动线程 t1
t2.start(); //启动线程 t2
}
}
当启动 线程 t1 t2 时 ,他们会 随机的 调用 自己 run()方法 , 不会执行完t1 在 执行 t2 ;
Runnable接口
Runnable接口 实现多线程的方法:
- 创建一个 Runnable接口的实现类,重写 run()方法;
- 创建 Runnable接口的实现类对象;
- 用 Thread的有参构造方法创建线程实例, 并 将 Runnable的实现类的实例作为参数传入;
- 调用线程实例的start()方法;
// 创建 Runnable接口的实现类
public class MyRunnable implements Runnable {
@Override
//重写 run()方法
public void run() {
int i = 0;
while (i++<10){
System.out.println(Thread.currentThread().getName() + "正在执行run()方法");
}
}
}
public class Application {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable(); //创建实现类的对象
Thread t1 = new Thread(myRunnable ,"Thread1"); // 将Runnable 接口的实现类对象作为参数 创建 线程实例;
Thread t2 = new Thread(myRunnable,"Thread2");
Thread t3 = new Thread(myRunnable,"Thread3");
// Thread 的 有参构造方法 Thread(Runnable target , String name); target Runnable接口实现类对象, name 线程名
t1.start();
t2.start();
t3.start();
}
Callable接口
Thread 和 Runnable 中重写的 run() 都没有返回值 ,无法从多个线程中获取结果。为了解决这个问题 JDK5 开始,提供一个 Callable接口,来满足 既能实现多线程,又可以获得返回结果;
它和 Runnable接口实现多线程的方法一样,都是通过 Thread 的有参构造方法,传入 Runnable 接口类型的参数 创建线程; 不同的是 Callable 传入参数的是Runnable接口的子类 FutureTask , FutureTask 对象中封装了带有返回值的 Callable 接口实现类;
实现方法如下:
- 创建一个Callable接口的实现类,重写 Callable 接口中的 call() 方法;
- 创建Callable接口实现类对象;
- 通过FutureTask 线程结果处理类的有参构造方法来封装Callable接口实现类对象;
- 使用参数为FutureTask类对象的Thread有参构造函数方法创建Thread线程实例;
- 调用线程实例的 start()方法;
代码实例如下:
// 创建 Callable 接口的实现类, 该接口需要指定 类型 ,是一个泛型接口;
public class MyCallable implements Callable<Object> {
@Override
//重写call()方法 必须 抛异常
public Object call() throws Exception {
int i = 0;
while ( i++ < 5) {
System.out.println(Thread.currentThread().getName() + "线程的call()方法正在运行");
}
return i;
}
}
public class Application {
public static void main(String[] args) throws InterruptedException, ExecutionException { //抛异常
MyCallable myCallable = new MyCallable(); // 创建接口实现类对象
FutureTask<Object> objectFutureTask1 = new FutureTask<>(myCallable); //通过 FutureTask的有参构造 封装 Callable实现类对象
Thread thread1 = new Thread(objectFutureTask1, "Thread1"); //创建线程,参数为 FutureTask 对象
FutureTask<Object> objectFutureTask2 = new FutureTask<>(myCallable);
Thread thread2 = new Thread(objectFutureTask2, "Thread2");
thread1.start();
thread2.start();
System.out.println("thread1返回结果:" + objectFutureTask1.get()); //通过 FutureTask对象的到线程的返回结果;
System.out.println("thread2返回结果:" + objectFutureTask2.get());
}
}
Runnable 和 Callable 基本类似, 只是Callable 比 Runnable的功能更强大,可以处理异常 和 返回 线程结果;
Thread 类 和 Runnable ,Callable接口; 后两个接口适合去处理共享同一资源的情况,如卖票。
线程的调度
一个线程若想要执行,必须获得CPU的使用权,Java会按照某种机制给程序中的每个线程分配CPU的使用权,这种机制就是线程的调度;
线程的优先级
优先级越高的线程,其获得CPU执行的机会越大;
线程的优先级 从 0-10 ,越大优先级越高, 除了数字表示优先级, Thread类中提供三个静态常量表示优先级:
static int MAX_PRIORITY ; //线程的最高优先级,相当于 10;
static int MIN_PRIORITY ; //线程的最低优先级,相当于 1;
static int NORM_PRIORITY ; //线程的普通优先级,相当于 5;
可以通过 Thread 类的 setPriority(int newPriority) 方法 对线程的优先级进行设置; newPriority 为 1-10的整数 或者优先级常量;
线程休眠
可以人为的将正在执行的线程暂停,将CPU的使用权让给其他线程, 使用的是 静态 方法 sleep(long millis) ,该方法使正在执行的线程暂停一会儿,进入休眠等待状态; 该方法会声明抛出 InterruptedException异常。
public static void main(String[] args) throws InterruptedException, ExecutionException {
Thread thread1 = new Thread(()->{
for(int i = 0; i < 10; i++){
System.out.println(Thread.currentThread().getName() + "正在输出i: "+ i);
}
},"优先级较低的线程");
Thread thread2 = new Thread(()->{
for(int j = 0; j < 10; j++){
System.out.println(Thread.currentThread().getName() + "正在输出j: "+ j);
try {
if(j==2){
Thread.sleep(500); //在线程执行过程中进入睡眠状态,让其他线程先执行
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
},"优先级较高的线程");
thread1.setPriority(Thread.MIN_PRIORITY); //设置线程1的优先级为1
thread2.setPriority(10); //设置线程2的优先级为2
thread1.start();
thread2.start();
}
线程让步
线程让步是指 使正在运行的线程失去CPU使用权,可以实现线程暂停,让系统的调度器重新调度一次; 线程让步的方法为 yield();
线程插队
当某个线程调用其他线程的 join() 方法时,调用的线程将被阻塞,直到被 join()方法加入的线程执行完成后它才会继续执行;
public static void main(String[] args) throws InterruptedException, ExecutionException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 10; i++){
System.out.println(Thread.currentThread().getName() + "输出了" + i);
}
},"Thread"); //创建一个线程 thread
thread.start(); //开启 线程 thread
for (int i = 0;i < 10; i++){
if ( i == 5){
thread.join(); // join()方法 , thread线程插队 先执行 , main方法线程先挂起,thread执行完后,main才执行
}
System.out.println(Thread.currentThread().getName() + "输出了" + i);
}
}
多线程同步
多线程的并发执行可以提供程序的效率,但是当多个线程访问同一个资源时,也会引发一些安全问题;如要统计一个班学生数量,要是学生一直进进出出,则很难统计正确;
为了解决这种问题,需要实现线程同步,即 限制某个资源在同一时刻只能被一个进程访问;
同步代码块
Java中同步代码块用来实现多线程的同步 。当多个线程同时使用同一个共享资源时,可以将处理的共享资源的代码放置在一个 使用 synchronized 关键字中来修饰的代码块中,这段代码块被称作同步代码块,格式如下:
synchronized(lock){
//操作共享资源的代码块
}
/*
lock 是一个锁对象, 当线程执行同步代码块时,会先判断 锁对象的标志位 是否为 1,若为1 则执行同步代码块;同时将 锁对象的标志位置为0;为0时 ,其他线程都无法执行同步代码块,当执行同步代码块的线程 执行完后,会将 锁对象的标志位在设为1;
*/
下面一个模仿售票窗口案例 用到 同步代码块:
public class TicketWindow implements Runnable {
private int ticketNumber = 10; //定义十张票
Object lock = new Object(); //定义任意一个类型的对象作为 锁对象;
@Override //重写 run()方法
public void run() {
synchronized (lock){ //定义同步代码块
while (ticketNumber > 0) {
try {
Thread.sleep(100); //模拟售票耗时过程
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第"+ ticketNumber-- + "张票");
}
}
}
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
TicketWindow ticketWindow = new TicketWindow();
//开启四个线程 模拟售票窗口
new Thread(ticketWindow, "窗口1").start();
new Thread(ticketWindow, "窗口2").start();
new Thread(ticketWindow, "窗口3").start();
new Thread(ticketWindow, "窗口4").start();
}
同步代码块中的锁对象可以是任意类型的对象,但是多个线程的共享的多对象必须是相同的;
同步方法
在 方法前面 使用 synchronized 关键字 来修饰 可以使方法变成 同步方法,能实现和同步代码块一样的功能;
被 synchronized 修饰的方法 在某一时刻 只允许一个线程访问;
同步方法 实现 上述售票窗口案例:
public class TicketWindow implements Runnable {
private int ticketNumber = 10;
@Override
public void run() {
while(ticketNumber > 0){
saleTicket(); //调用同步方法
}
}
public synchronized void saleTicket() { //实现 同步方法;
if (ticketNumber > 0) {
try {
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第"+ ticketNumber-- + "车票");
}
}
}
同步锁
Ssynchronized 同步代码块和 和同步方法有一些限制,例如 无法中断一个正在等待获得所得线程,也无法通过轮询得到锁,如果不想等下去,也就没法得到锁;
JDK 5 提供了一个Lock 锁, 它可以让某个线程 在 持续获得同步锁失败后返回,不在继续等待;
Lock是一个接口 , 可以通过 他的 实现类 ReetranyLock 进行实例化;
ublic class TicketWindow implements Runnable {
private int ticketNumber = 10;
private final Lock lock = new ReentrantLock(); // 定义一个锁对象lock
@Override
public void run() {
while(ticketNumber > 0) {
lock.lock(); //对代码进行加锁
if (ticketNumber > 0) {
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "正在出售第" + ticketNumber-- + "票");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); //执行完代码释放锁
}
}
}
}
}
多线程通信
生产者消费者问题,生产者线程负责生产商品,消费者线程负责消费产品;
为了控制多个线程按照一定得顺序轮流执行,需要让线程间进行通信,保证线程任务得协调进行; Java得 Object类中提供了 wait() ,notify() , notifyAll() 等方法 用于 解决 线程间得通信问题;
三个方法任意一个实现类对象都可以调用
void wait() // 让当前线程放弃同步锁 并进入等待;知道其他线程进入次同步锁,并调用 notify() 和 notifyAll() 方法唤醒该线程为止
void notift() // 唤醒此 同步锁上 等待得 第一个 调用 wait() 方法得线程;
void notifyAll() // 唤醒此 同步锁上调用 wait() 方法得所有线程;
注意 : 调用上述三种方法得对象 必须 是 同步锁 对象,否则 会爆出 IllegalMonitorStateException异常
方法举例:(实现生产一件上商品,消费一件商品)
public static void main(String[] args) {
List<Object> goods = new ArrayList<>(); //创建一个集合 模拟存储生产得商品;
long start = System.currentTimeMillis(); //获取线程开启时 时间;
//创建一个 生产者线程
Thread thread1 = new Thread(()->{
int num = 0;
while (System.currentTimeMillis() - start <= 100) { // 生产线程 执行 100毫秒;
synchronized (goods) { //使用同步代码块 同步生产和消费
if (goods.size() > 0) {
try {
goods.wait(); // 若 集合 有商品,线程放弃同步锁进行等待
}catch (InterruptedException e){
e.printStackTrace();
}
}else {
goods.add("商品"+ ++num); // 没有商品 , 生产一件商品 加入 集合;
System.out.println("生产商品" + num);
}
}
}
},"生产者");
//创建一个 消费者线程
Thread thread2 = new Thread(()->{
int num = 0;
while (System.currentTimeMillis() -start <= 100){ //消费者线程执行100毫秒
synchronized (goods) {
if (goods.size() <=0 ){ //如果 商品量不足 ,唤醒生产者线程
goods.notify();
}else{
goods.remove("商品" + ++num);
System.out.println("消费商品" + num);
}
}
}
},"消费者");
//同时开启生产者消费者线程
thread1.start();
thread2.start();
}
线程池
在大规模得应用程序中,创建,分配和释放多线程对象会产生大量内存管理开销。为此,可以使用Java提供得线程池来创建多线程,进一步优化线程管理。
Executor接口
JDK5开始, 在java.util.concurrent包下增加了 Executor接口及其子类,允许使用线程池技术来管理线程并发问题;
Executor 接口 提供了一个常用得 ExecutorServic字接口 ,实现对线程池得管理;
通过Executor接口实现线程池管理得步骤:
- 创建 一个 实现 Runnable 或者 Callable 接口的实现类,重写 run() 或者 call() 方法;
- 创建 Runnable 或者 Callable 接口 的实现类对象;
- 使用Executor 线程执行器 创建线程池;
- 使用 ExecutorService 执行器服务类的 submit()方法将 Runnable 接口 或者 Callable 接口的实现类对象 提交到线程池进行管理;
- 线程任务执行完成后。使用 shutdown()方法关闭线程池;
Callable代码举例:
public class ExecutorExample implements Callable<Object> {
@Override
// 1 .重写 Callable中的call()方法;
public Object call() throws Exception {
int i = 0;
while (i++ < 5){
System.out.println(Thread.currentThread().getName()+ "正在执行call()方法");
}
return i;
}
}
ublic class Application {
public static void main(String[] args) throws InterruptedException, ExecutionException {
MyCallable myCallable = new MyCallable(); //创建 Callable接口的 实现类的 对象
ExecutorService pool = Executors.newCachedThreadPool(); //创建一个线程池;
Future<Object> submit0 = pool.submit(myCallable); //将线程放入线程池
Future<Object> submit1 = pool.submit(myCallable);
pool.shutdown(); //关闭线程池
System.out.println(submit0.get()); //得到线程的返回结果
System.out.println(submit1.get());
}
}
// 线程池是通过 Executors 的 newCachedThreadPoll() 方法创建的; Executor是jdk5中增加的线程执行器工具类
//Runnable 和 Callable类似 , 使用 Runnable 放入 线程池管理 可以使用 executor()方法,此方法没有返回值。