JAVA-多线程

1.线程概述

进程:在一个操作系统中,每个独立执行的程序都可以称之为一个线程。
操作系统会为每一个进程分配一段有限的CPU使用时间,CPU在这段时间中执行某个进程,然后在下一段时间切换到另一个进程中去执行。
线程:在一个进程中,可以有多个执行单元同时运行,这些执行单元可以看做程序执行的一条条线索,被称为线程。线程也是由CPU控制并轮流执行的。
当一个java程序启动后,就会产生一个进程,该进程中会默认创建有个线程用来执行main函数代码。

2.线程的创建

(1)Thread类实现多线程
步骤:创建一个Thread线程类的子类,同时重写Thread类的run方法;创建该子类的实例对象,并通过调用start方法启动线程。

class MyThread1 extends Thread{
	public MyThread1(String name){
		super(name);
	}
	public void run(){
		int i=0;
		while(i++<5){
			System.out.println(Thread.currentThread().getName());//用来获取当前线程对象
		}
	}
}
public class Example{
	public static void main(String[] args){
		MyThread1 thread1=new MyThead1("thread1");//创建实例对象
		thread1.start();//启动线程
	}
}

(2)Runnable接口实现多线程
步骤:创建一个Runnable接口的实现类,同时重写接口中的run方法;创建Runnable接口的实现类对象;使用Thread有参构造方法创建线程实例,并将Runnable接口的实现类的实例对象作为参数传入;调用线程实例的start方法启动线程。

class MyThread2 implements Runnable{
	public void run(){
		int i=0;
		while(i++<5){
			System.out.println(Thread.currentThread().getName());//用来获取当前线程对象
	}
}
public class Example{
	public static void main(String[] args){
		MyThread2 myThread2=new MyThread2();
		Thread thread=new Thread(myThread2,"thread");
		thread1.start();
	}
}

(3)Callable接口实现多线程
用来满足既能创建多线程又可以有返回值的需求。
步骤:创建一个Callable接口的实现类,同时重写call方法;
创建Callable接口的实现类对象;
通过FutureTask线程结果处理类的有参构造方法来封装Callable接口实现类对象;
使用参数为FutureTask类对象的Thread有参构造方法创建Thread线程实例;
调用线程实例的start方法来启动线程。

import java.util.concurrent.*;
class MyThread3 implements Callable<Object>{
	public Object call() throws Exception{
		int i=0;
		while(i++<5){
			System.out.println(Thread.currentThread().getName());//用来获取当前线程对象
	}
	return i;
}
public class Example{
	public static void main(String[] args){
		//创建Callable接口的实现类对象
		MyThread3 myThread3=new MyThread2();
		//使用FutureTask封装Callable接口
		FutureTask<Object>ft1=new FutureTask<>(myThread3);
		Thread thread3=new Thread(ft1,"thread3");
		thread1.start();
		//通过FutureTask对象的方法管理返回值
		ft1.get();//返回值是6
	}
}

(4)三种方法的对比分析
通过实现Runnable和Callable接口的好处:
适合多个线程去处理同一个共享资源的情况,把线程同程序代码、数据有效的分离,很好的体现了面向对象的设计思想
可以避免java单继承带来的局限性。
(5)后台线程
新创建的线程默认是前台线程,如果某个线程对象在启动之前调用了setDaemon语句,这个线程就变成了一个后台线程。

3.线程的生命周期和状态转换

(1)NEW(新建状态)
创建一个线程对象后,该线程就处于新建状态,此时仅仅由JVM为其分配了内存。
(2)RUNNABLE(可运行状态)
调用start方法后。
READY(就绪状态):调用start方法后等待jvm的调度,此时线程并没有运行;
RUNNING(运行状态):线程对象获得JVM调度。
(3)BLOCKED(阻塞状态)
因为某些原因失去CPU的执行权。JVM不会给线程分配CPU,直至线程恢复为就绪状态。
线程进入阻塞状态的两种情况:
线程A运行过程中,试图获取同步锁,却被线程B获取,此时JVM把当前线程A存到对象的锁池中,线程A就会进入阻塞状态;
当线程运行过程中,发出I/O请求时。
(4)WAITING(等待状态)
当处于运行状态的线程调用了无时间参数的方法后,wait、join等,就会将当前运行中的线程转换成等待状态。
调用wait方法而处于等待状态中的线程,必须等待其他线程调用notify或者notifyAll方法唤醒当前等待中的线程。
调用join方法的,必须等待其他加入的线程中值。
(5)TOME_WAITING(定时等待状态)
运行线程调用了有时间参数限制的方法,如sleep(long millis)、wait(long timeout)、join(long millis)
调用wait方法而处于等待状态中的线程,必须等待其他线程调用notify或者notifyAll方法唤醒当前等待中的线程,或者直接等时间结束后
(6)TERMINATED(终止状态)
线程的run或者call方法正常执行完毕或者线程抛出一个未捕获的异常、错误,线程就进入终止状态。

4.线程的调度

Java虚拟机按照特定的机制为程序中的每个线程分配CPU的使用权。
线程调度有两种模型:
分时调度模型:让所有线程轮流获得CPU的使用权,并且平均分配每个线程占用的时间。
抢占式调度模型:让可运行池中所有就绪状态的线程争抢CPU的使用权,优先级高的线程获取的概率要大于优先级低的。
Java虚拟机默认使用抢占式调度模型。
(1)优先级
优先级用1~10之间的数字来表示。
也可以使用Thread类中提供的三个静态常量表示线程的优先级。

静态常量功能描述
static int MAX_PRIORITY表示线程的最高优先级,10
static int MIN_PRIORITY表示线程的最低优先级,1
static int NORM_PRIORITY表示线程的普通优先级,5

可以通过Thread类的setPriority(int newPriority)方法对其进行设置。其中的参数接受1~10的整数或者三个静态常量。
(2)线程休眠
使用sleep(long millis)可以人为的使正在执行的线程休眠,该方法会声明抛出InterruptedException异常,因此在调用该方法时应该捕获异常,或者声明抛出异常。

try{
	Thread.sleep(500);//ms
}
catch(InterruptedException e){
	e.printStackTrace();
}

(3)线程让步
通过yield方法实现,但该方法不会阻塞该线程,而是将线程转换成就绪状态。

class YieldThread extends Thread{
	public YieldThread(String name){
		super(name);
	}
	public void run(){
		for(int i=0,i<5,i++){
			if(i==2){
				System.out.println("线程让步");
				Thread.yield();
			}
		}
	}
}

(4)线程插队
使用join方法。当在线程中调用其他线程的join方法时,调用的线程将被阻塞,直至被该方法加入的线程执行完成后它才会继续执行。

5.多线程同步

限制某个资源在同一时刻只能被一个线程访问。
(1)线程安全:由多个线程同时处理共享资源所导致的。
(2)同步代码块
解决线程安全问题,保证处理共享资源的代码在任意时刻只能有一个线程访问。
同步代码块:当多个线程使用同一个资源时,可以将处理共享资源的代码放置在一个使用synchronized关键字来修饰的代码块中。

synchronized(lock){
}

lock是一个锁对象。当线程同步代码块时,首先会检查锁对象的标志位,默认情况下标志位为1,此时线程会执行同步代码块,同时将锁对象的标志位置0.当一个新线程执行到这段代码段时,由于锁对象的标志位是0,因此新线程会发生堵塞。等当前线程执行完同步代码块之后,锁对象的标志位置1,新线程才能执行同步代码块之中的内容。
锁对象可以是任一类型的对象。但是锁对象的创建不能放入到run方法中。
(3)同步方法
在方法面前使用synchronized关键之来修饰,被修饰的方法为同步方法,他能实现和同步代码块同样的功能。
同步方法也有锁,它的锁就是当前调用该方法的对象
java中静态同步方法的锁是该方法所在类的class对象
(4)同步锁
synchronized锁限制:无法中断一个正在等候获得锁的线程;无法通过轮询得到锁;
java从5开始增加了一个Lock锁,它功能相同但是可以让某个线程在持续获取同步锁失败后返回,不再继续等待。
步骤:
定义一个锁对象

final Lock lock=new ReentrantLock();

在加锁的代码块中加入:

lock.lock();

执行完代码块之后释放锁

lock.unlock();

(5)死锁问题
两个线程在运行时都在等待对方的锁,造成了死锁现象

6.多线程通信

线程通信的常用方法:

方法声明功能描述
void wait()使当前线程放弃同步锁并等待,直至其他线程进入此同步锁,并调用notify或notifyAll
void notify()唤醒此同步锁上等待的第一个调用wait方法的线程
void notifyAll()唤醒此同步锁上调用wait方法的所有线程

注:以上三个方法的调用都应该是同步锁对象,否则会抛出IllegalMonitorStartException异常。

import java.util.*;
public class Example{
	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){//程序执行100ms
				//同步商品的生产和消费
				synchronized(goods){
					if(goods.size()>0){
						tyr{
							goods.wait();//有商品,生产线程就处于停滞状态
						}
						catch(InterruptedException e){
							e.printStackTrace();
						}
					}
					else{
						goods.add(++num);
					}
				}
			}	
		})
		Thread thread2=new Thread(()->{
			int num=0;
			synchronized(goods){
				if(goods.size()<=0){
					goods.notify();//商品不足唤醒生产者生产
				}
				else{
					goods.remove(++num);
				}
			}
		})
		thread1.start();
		thread2.start();
	}
}

7.线程池

在大规模应用程序中,创建、分配和释放多线程对象会产生大量内存管理开销。采用java提供的线程池来创建多线程,可以进一步优化。
(1)Executor接口实现线程管理
步骤:
创建一个Runnable接口或者Callable接口的实现类,同时重写run或者call方法;
创建Runnable接口或者Callable接口的实现类对象
使用Executors线程执行器类来创建线程池
使用ExecutorService执行器服务类的submit方法将Runnable或者Callable接口的实现类对象提交到线程池进行管理
线程任务执行完成后,使用shutdown方法来关闭线程池
Executors创建线程的方法:

方法声明功能描述
ExecutorService newCachedThreadPool()创建一个可扩展线程池的执行器。适用于启动许多短期任务的应用程序
ExecutorService newFixedThreadPool(int nThreads)创建一个固定线程数量线程池的执行器。可以很好的控制多线程任务
ExecutorService newSingleThreadExecutor()在特殊需求下创建一个只执行一个任务的单个线程
ScheduledExecutorService newScheduledThreadPool(int corePoolSize)创建一个定长线程池,支持定时及周期性任务执行
import java.util.concurrent.*;
class MyThread implements Callable<Object>{
	public Object call() throws Exception{
		int i=0;
		while(i++<5){
			System.out.println(Thread.currentThread().getName());
		}
		return i;
	}
}
public class Example{
	public static void main(String[] args){
		MyThread mythread =new Mythread();//创建callable接口类的实现对象
		ExecutorService executor=Executors.newCachedThreadPool();//使用线程执行器类创建可扩展的线程池
		Future<Object>result=executor.submit(mythread);//将callable接口类的实现对象交到线程池进行管理
		executor.shutdown();//关闭线程池
	}
}

(2)CompletableFuture类实现线程池管理
在使用Callable接口实现多线程时,会用到FutureTask类对线程执行结果进行管理和获取。但该类在获取结果时采用的是阻塞或者轮询的方式,违背多线程编程的初衷且耗费过多资源。
CompletableFuture类简化异步编程的复杂性。
CompletableFuture对象创建的4个静态方法:

import java.util.concurrent.*;
public class Example{
	public static void main(String[] args){
		CompletableFuture<Integer>completableFuture=
			CompletableFuture.supplyAsync(()->{
			int sun=0,i=1;
			while(i++<5){
				sum+=i
			}
			return sum;
			})
	}
}

runAsync和supplyAsync方法的本质区别就是获取的CompletableFuture对象是否带有计算结果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值