相关概念:
- 进程:进程是程序的基本执行实体,一个软件运行就是一个进程
- 线程:是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程之中的实际单位
- 例如,一个软件的互相独立,同时运行的功能,这个软件可以看作进程,各个功能可以看作是线程
CPU可以在多个程序之间进行切换,把等待的时间进行充分利用起来
一个进程当中,可以有多条线程,但一个进程当中,至少有一个线程
并发:在同一时间,有多个任务在单个CPU上交替执行,单核心,单CPU编程
并行:在同一时间,有多个任务在多个CPU上同时执行,多核心,多CPU编程
并发和并行是有可能同时发生的
每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程
每一个线程启动之后,都会创建一个处于自己的线程栈
多线程的实现方式
- 继承Thread类的方式
- 实现Runnable接口的方式
- 利用Callable和Future接口的方式
继承Thread类的方式:
将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例
public static void main(String[] args) {
/*
* 多线程的第一种启动方式:
* 自己定义的类继承Thread
* 重写Run方法
* 创建子类的对象,并启动线程
*
*
* */
Student student = new Student();
student.setName("线程1");
student.start();
Student student1 = new Student();
student1.setName("线程2");
student1.start();
public class Student extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+"HelloWorld");
}
}
}
实现Runnable接口的实现方式
Thread.currentThread()——获取当前执行线程的对象
public static void main(String[] args) {
/*
* 多线程启动的第二种方式
* 创建一个子类实现Runnable接口
* 重写Run方法
* 创建子类对象,表示多线程要执行的任务
* 创建Thread对象,并把要执行的任务传入Thread当中,Thread就表示线程
* 开启线程
*
*
* */
User user = new User();
Thread thread = new Thread(user);
thread.setName("线程1");
thread.start();
Thread thread1 = new Thread(user);
thread1.setName("线程2");
thread1.start();
}
public class User implements Runnable {
@Override
public void run() {
//获取当前线程的对象
Thread thread = Thread.currentThread();
for (int i = 0; i < 100; i++) {
System.out.println(thread.getName()+"HelloWorld");
}
}
}
利用Callable和Future方式实现
可以获取多线程运行的结果
实现Callable的时候会有泛型,泛型里的数据类型就是多线程执行的结果
/*
* 多线程实现的第三种方式
* 1. 创建一个子类,实现Callable接口,并重写call方法
* 2. 创建子类对象,表示多线程要执行的任务
* 3. 创建一个FutureTask类,来管理多线程返回的结果
* 4. 创建Thread,表示线程
*
*
* */
public static void main(String[] args) throws ExecutionException, InterruptedException {
Animal animal = new Animal();
FutureTask<Integer>futureTask = new FutureTask<>(animal);
Thread thread = new Thread(futureTask);
thread.start();
Integer integer = futureTask.get();
System.out.println(integer);
}
public class Animal implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum=0;
for (int i = 0; i < 100; i++) {
sum = sum+i;
}
return sum;
}
}
三种实现方法的优缺点
优点 | 缺点 | |
继承Thread类 | 编程较简单,可以直接使用Thread的方法 | 可扩展性差,不能够继承其他类 |
实现Runnable接口 | 扩展性强,实现接口的同时,还能继承其他类 | 编程复杂,不能直接使用Thread中的方法 |
实现Callable接口 | 扩展性强,实现接口的同时,还能继承其他类 | 编程复杂,不能直接使用Thread中的方法 |
常见的成员方法
方法名称 | 说明 |
String getName() | 返回此线程的名字 |
void setName(String name) | 设置线程的名字,可以用构造方法设置名字 |
static Thread currentThread() | 获取当前线程的对象 |
static void sleep(long time) | 让线程休眠指定的时间,单位:毫秒 |
setPriority(int newPriority) | 设置线程优先级,默认的优先级是5 |
final int getPriority() | 获取线程的优先级 |
final void setDaemon(boolean on) | 设置为守护线程 |
public static void yield() | 礼让线程 |
public static void join() | 插入线程 |
当JVM虚拟机启动之后,会自动启动多条线程
其中有一条线程叫做main线程
他的作用就是执行main方法里面的代码,并执行里面的代码
在Java中使用的是抢占式调度的规则来执行进程的,优先级越高抢到cpu的概率越大
守护线程:当其他的非守护线程执行完之后,守护线程也跟着陆续结束,可能不会马上结束,但也没有存在的必要了
礼让线程: 出让当前CPU的执行权,尽可能地平均执行
插入线程:插入到当前线程之前
线程的生命周期
同步代码块
变量加上static表示:该类的所有对象共享这个变量
同步代码块:将操作共享的数据代码锁起来
- 锁默认打开,有一个线程进去了,锁自动关闭
- 里面的代码全部执行完毕,线程出来,锁自动打开
- 锁对象一定要是唯一的
- 保持唯一性的关键就是在变量前面加static关键字,这样就算类中创建其他的锁对象,也表明这个变量是共享的
- 可以采用当前类的字节码文件作为锁对象,因为在一个路径在只能存在一个这样名称的字节码文件,保证了唯一性
synchronized(锁对象){
操作共享数据的代码
}
同步方法
- 同步方法就是锁住方法里面的所有代码
- 锁对象不能自己指定
- 非静态的是this
- 静态的是当前类的字节码文件对象
修饰符 synchronized 返回值类型 方法名(方法参数列表){
}
当使用Runnable来实现多线程时,可以不用定义共享数据,因为只会创建一次对象
但是如果用继承Thread的方式实现多线程时,必须定义共享数据,会多次创建对象
Lock锁
为了更清晰的表示如何加锁和释放锁,JDK5以后提供了新的锁对象Lock
Lock实现提供比使用synchronized方法和语句可以更广泛的对锁操作
Lock中提供了获得锁和手动释放锁的方法
void lock()——获得锁
void unlock()——手动上锁,释放锁
Lock是接口不能够被实例化, 对他的实现类ReentrantLock来实例化
ReentrantLock的构造方法
ReentrantLock()——创建一个ReentrantLock的实例
锁对象在写的时候,当使用Runnable来实现多线程时,可以不用定义共享数据,因为只会创建一次对象
但是如果用继承Thread的方式实现多线程时,必须定义共享数据,会多次创建对象
关锁的方法无论怎么样都要执行,所以可以把关锁的方法写到finally结构体当中
死锁
多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
java 死锁产生的四个必要条件:
- 1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
- 2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
- 3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
- 4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。
生产者消费者模式(等待唤醒机制)
void wait()——当前线程等待,直到被其他线程唤醒
void notify()——唤醒一个线程
void notifyAll()——唤醒所有线程
在调用wait和notifyAll方法的时候应该使用锁对象调用
wait表示让当前线程和锁对象进行绑定
notifyAll表示唤醒与这个锁有关的所有线程
package myThread;
public class Cookie extends Thread{
@Override
public void run() {
//循环
//同步代码块
//判断共享数据是否到了末尾,到了末尾会怎么样执行
//判断共享数据是否到了末尾,没到末尾会怎么样执行
while(true){
synchronized (Desk.lock){
//判断共享数据是否到了末尾
if (Desk.count==0){
//到了末尾,直接跳出
break;
}else {
//没到末尾会怎么执行
//先判断桌子上食物状态
//如果是0,等待,
if (Desk.footFlag==0){
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else {
//如果是1,就开吃,吃完后唤醒厨师做食物,开吃,修改食物状态,修改共享数据
Desk.count--;
System.out.println("吃货正在吃面条,还能吃"+Desk.count+"碗!!!");
Desk.lock.notifyAll();
Desk.footFlag=0;
}
}
}
}
}
}
package myThread;
public class Foodie extends Thread{
@Override
public void run() {
while (true){
synchronized (Desk.lock){
//判断共享数据是否到了末尾
if (Desk.count==0){
break;
}else {
//先判断食物的状态
//如果是1就等待
//如果是0就做一碗,然后唤醒吃货吃
if (Desk.footFlag==1){
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else {
System.out.println("厨师做了一碗面条");
Desk.footFlag=1;
Desk.lock.notifyAll();
}
}
}
}
}
}
package myThread;
public class Desk {
//表示桌子上的食物状态,如果是0就表示没有,如果是1就表示有
public static int footFlag = 0;
//创建一个锁对象
public static Object lock = new Object();
//表示吃货还能吃几碗
public static int count = 10;
}
package myThread;
public class ThreadTest05 {
public static void main(String[] args) {
Cookie cookie = new Cookie();
Foodie foodie = new Foodie();
cookie.setName("厨师");
foodie.setName("吃货");
cookie.start();
foodie.start();
}
}
等待唤醒机制(阻塞队列方式实现)
阻塞队列相当于连接生产者和消费者的通道,生产者和消费者必须使用同一个阻塞队列
先放进去的先拿出来
阻塞队列的继承结构
阻塞队列就相当于一个单列集合,可以用迭代器迭代遍历
阻塞队列的实现类
BlockingQueue是一个接口,有两个实现类
ArrayBlockingQueue:底层是数组,有界
LinkedBlockingQueue:底层是链表,无界,但不是真正的无界,最大值为int的最大值
阻塞队列实现等待唤醒机制
阻塞队列不用手动控制线程什么时候该被阻塞,什么时候该被唤醒,简化了操作
防止队列容器溢出,防止数据丢失。
put和take方法的底层是有锁的,在写生产者和消费者的同步代码块时就不用再写锁了
package myThread;
import java.util.concurrent.ArrayBlockingQueue;
public class Cook extends Thread{
private ArrayBlockingQueue<String> arrayBlockingQueue;
public Cook(ArrayBlockingQueue<String> arrayBlockingQueue) {
this.arrayBlockingQueue = arrayBlockingQueue;
}
@Override
public void run() {
while (true){
try {
arrayBlockingQueue.put("面条");
System.out.println("厨师做好了面条");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
package myThread;
import java.util.concurrent.ArrayBlockingQueue;
public class Food extends Thread{
private ArrayBlockingQueue<String> arrayBlockingQueue;
public Food(ArrayBlockingQueue<String> arrayBlockingQueue) {
this.arrayBlockingQueue = arrayBlockingQueue;
}
@Override
public void run() {
while(true){
try {
String take = arrayBlockingQueue.take();
System.out.println(take);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
package myThread;
import java.util.concurrent.ArrayBlockingQueue;
public class ThreadTest06 {
public static void main(String[] args) {
//创建阻塞队列的对象
ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(1);
Cook cook = new Cook(arrayBlockingQueue);
Food food = new Food(arrayBlockingQueue);
cook.start();
food.start();
}
}
线程的六种状态
多线程案例
package myThread;
public class ThreadTest07 {
public static void main(String[] args) {
Ticket01 ticket01 = new Ticket01();
Ticket01 ticket011 = new Ticket01();
ticket01.setName("窗口1");
ticket011.setName("窗口2");
ticket01.start();
ticket011.start();
}
}
package myThread;
public class Ticket01 extends Thread{
private static int number = 1000;
@Override
public void run() {
//循环
while(true){
//同步代码块
synchronized (Ticket01.class){
//判断共享数据是否到了末尾,到了末尾执行什么代码
if (number==0){
break;
}else {
//如果没到末尾
number--;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+"正在卖票,还剩"+number+"张票");
}
}
}
}
}
package myThread;
public class ThreadTest08 {
public static void main(String[] args) {
Gift gift = new Gift();
Thread thread = new Thread(gift);
Thread thread1 = new Thread(gift);
thread.setName("线程1");
thread1.setName("线程2");
thread.start();
thread1.start();
}
}
package myThread;
public class Gift implements Runnable{
private int giftNumber = 100;
@Override
public void run() {
while(true){
synchronized (Gift.class){
//判断共享数据是否到了末尾
if (giftNumber==0){
break;
}else {
//如果共享数据没到末尾
giftNumber--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+"正在送礼物,还剩"+giftNumber+"件礼物");
}
}
}
}
}
package myThread;
public class ThreadTest09 {
public static void main(String[] args) {
getNumber getNumber = new getNumber();
Thread thread = new Thread(getNumber);
Thread thread1 = new Thread(getNumber);
thread.setName("线程1");
thread1.setName("线程2");
thread.start();
thread1.start();
}
}
package myThread;
public class getNumber implements Runnable{
int i=1;
@Override
public void run() {
while (i<=100){
synchronized (getNumber.class){
//判断共享数据是否到了末尾
if (i>100){
break;
}else {
//如果共享数据没到末尾
if (i%2==1){
System.out.println(Thread.currentThread().getName()+"打印数字"+i);
}
i++;
}
}
}
}
}
package myThread;
public class ThreadTest10 {
public static void main(String[] args) {
redEnvelope redEnvelope = new redEnvelope();
Thread thread1 = new Thread(redEnvelope);
Thread thread2 = new Thread(redEnvelope);
Thread thread3 = new Thread(redEnvelope);
Thread thread4 = new Thread(redEnvelope);
Thread thread5 = new Thread(redEnvelope);
thread1.setName("线程1");
thread2.setName("线程2");
thread3.setName("线程3");
thread4.setName("线程4");
thread5.setName("线程5");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread5.start();
}
}
package myThread;
import java.util.Random;
public class redEnvelope implements Runnable {
//剩余包的数量
private int Envelope = 3;
//包中有多少钱
private double money = 100;
private double MIN = 0.01;
@Override
public void run() {
synchronized (redEnvelope.class) {
//判断共享数据是否到了末尾
if (Envelope == 0) {
System.out.println(Thread.currentThread().getName() + "没抢到");
} else {
double prize = 0;
//表示红包最后一个包,剩余钱都是金额
if (Envelope == 1) {
prize = money;
} else {
Random random = new Random();
//最大金额为99.98
double bounds = money - (Envelope - 1) * 0.01;
prize = random.nextDouble(bounds);
if (prize < MIN) {
prize = MIN;
}
}
Envelope--;
money = money - prize;
System.out.println(Thread.currentThread().getName() + "抢到了红包,抢了" + prize + "块钱");
}
}
}
}
package myThread;
import java.util.ArrayList;
import java.util.Collections;
public class ThreadTest11 {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);
RafflePool rafflePool = new RafflePool(list);
Thread thread1 = new Thread(rafflePool);
Thread thread2 = new Thread(rafflePool);
thread1.setName("线程1");
thread2.setName("线程2");
thread1.start();
thread2.start();
}
}
package myThread;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;
public class RafflePool implements Runnable{
private ArrayList<Integer> arrayList;
public RafflePool(ArrayList<Integer> arrayList) {
this.arrayList = arrayList;
}
@Override
public void run() {
while(true){
synchronized (RafflePool.class){
//判断共享数据是否到了末尾
if(arrayList.size()==0){
System.out.println("奖池中没有奖项,奖池空了");
break;
}else{
Collections.shuffle(arrayList);
Random random= new Random();
int index = random.nextInt(arrayList.size());
int prize = arrayList.remove(index);
System.out.println(Thread.currentThread().getName()+"获得了"+prize+"元大奖");
}
}
}
}
}
package myThread;
import java.util.ArrayList;
import java.util.Collections;
public class ThreadTest12 {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700);
RafflePool01 rafflePool01 = new RafflePool01(list);
RafflePool01 rafflePool02 = new RafflePool01(list);
RafflePool01 rafflePool03 = new RafflePool01(list);
RafflePool01 rafflePool04 = new RafflePool01(list);
RafflePool01 rafflePool05 = new RafflePool01(list);
rafflePool01.setName("线程1");
rafflePool02.setName("线程2");
rafflePool03.setName("线程3");
rafflePool04.setName("线程4");
rafflePool05.setName("线程5");
rafflePool01.start();
rafflePool02.start();
rafflePool03.start();
rafflePool04.start();
rafflePool05.start();
}
}
package myThread;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;
public class RafflePool01 extends Thread{
//定义共享数据
private ArrayList<Integer> arrayList;
public RafflePool01(ArrayList<Integer> arrayList) {
this.arrayList = arrayList;
}
@Override
public void run() {
ArrayList<Integer> boxList = new ArrayList<>();
while (true){
synchronized (RafflePool01.class){
//判断共享数据是否到了末尾
if (arrayList.size()==0){
System.out.println(boxList);
break;
}else {
Collections.shuffle(arrayList);
Random random = new Random();
int index = random.nextInt(arrayList.size());
int prize = arrayList.remove(index);
boxList.add(prize);
}
}
}
}
}
线程池
线程池是用来存放线程的。
当我们提交一个任务的时候,线程池会自动创建一个线程来执行任务,执行完之后把线程放回线程池,下次再执行任务的时候,直接拿出来用
如果提交任务的时候,没有空闲线程,也无法创建线程,任务就会排队等待
创建线程池对象
Executors:线程池的工具类通过调用方法返回不同类型的线程池对象
public static ExecutorService newCachedThreadPool( )//创建一个没有上限的线程池
public static ExecutorService newFixedThreadPool(int nThreads)//创建有上限的线程池
线程池多大合适
CPU密集运算:最大并行数+1
I/O密集运算:最大并行数*期望CPU利用率*总时间(CPU计算时间+等待时间)/CPU计算时间
自定义线程对象
自定义线程对象中共有7个参数
- 核心线程数量——>(不能小于0)
- 线程池中的最大容量——>(最大数量>=核心线程数量)
- 空闲时间(值)——>(不能小于0)
- 空闲时间(单位)——>(用TimeUnit指定)
- 阻塞队列——>(不能为空)
- 创建线程的方式——>(不能为null)
- 要执行的任务过多时的解决方案——>(不能为null)
什么时候会创建临时线程:当核心线程没有空闲,阻塞队列满了的时候才会创建临时线程
先提交的任务不一定先执行
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3,//核心线程数量
6,//线程池中的最大容量
60,//空闲时间(值)
TimeUnit.SECONDS,//空闲时间(单位)
new ArrayBlockingQueue<>(3),//阻塞队列的实现类创建
Executors.defaultThreadFactory(),//创建线程工厂
new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略
);
任务拒绝策略
任务拒绝策略 | 说明 |
ThreadPoolExecutor.AbortPolicy | 默认策略:丢弃任务,并抛出RejectExecutionException异常 |
ThreadPoolExecutor.DiscardPolicy | 丢弃任务,但是不抛出异常,不推荐此做法 |
ThreadPoolExecutor.DiscardOldestPolicy | 抛弃队列中等待最久的任务,然后把当前任务加入队列当中 |
ThreadPoolExecutor.CallerRunsPolicy | 调用任务的run()方法绕过线程池直接执行 |