1.什么是多线程
多线程可以让程序同时做多件事情,可以提高软件的运行效率,当让多个事情同时运行时就需要用到多线程。例如软件中的耗时操作、所有聊天软件、所有的服务器。
2.并发和并行
并发:在同一时刻,有多个指令在单个CPU上交替进行
并行:在同一时刻,有多个指令在多个CPU上同时进行
3.多线程的实现方式
第一种实现方式:继承Thread类
(1)定义一个类继承Thread
(2)重写run方法
(3)创建子类的对象,并启动线程
//MyThread.java
public class MyThread extends Thread{
@Override
public void run() {
//书写线程要执行的代码
for (int i = 0; i < 100; i++) {
System.out.println(this.getName() + "HelloWorld");
}
}
}
//ThreadDemo1.java
public class ThreadDemo1 {
public static void main(String[] args) {
//创建多线程对象
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
//为线程设置名字
t1.setName("线程1");
t2.setName("线程2");
//开启线程
t1.start();
t2.start();
}
}
第二种实现方式:实现Runnable接口
(1)定义一个类实现Runnable接口
(2)重写里面的run方法
(3)创建自己的类的对象
(4)创建一个Thread类的对象,并开启线程
//MyRun.java
public class MyRun implements Runnable{
@Override
public void run() {
//书写线程要执行的代码
for (int i = 0; i < 100; i++) {
//获取到当前线程的对象
System.out.println(Thread.currentThread().getName() +
"HelloWorld");
}
}
}
//ThreadDemo2.java
public class ThreadDemo {
public static void main(String[] args) {
//创建MyRun对象表示多线程要执行的任务
MyRun mr = new MyRun();
//创建多线程对象
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
//为线程设置名字
t1.setName("线程1");
t2.setName("线程2");
//开启线程
t1.start();
t2.start();
}
}
第三种实现方式:实现Callable接口
特点:可以获取到多线程中运行的结果
(1)创建一个类MyCallable实现Callable接口
(2)重写call方法(有返回值,表示多线程运行的结果)
(3)创建MyCallable的对象(表示多线程要执行的任务)
(4)创建FutureTask的对象(表示管理多线程运行的结果)
(5)创建Thread类的对象,并启动
//MyCallable.java
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
//求1-100之间的整数和
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i;
}
return sum;
}
}
//ThreadDemo3.java
public class ThreadDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建MyCallable对象,表示多线程要执行的任务
MyCallable mc = new MyCallable();
//创建FutureTask对象,管理多线程任务运行的结果
FutureTask<Integer> ft = new FutureTask<>(mc);
//创建线程的对象
Thread t1 = new Thread(ft);
t1.start();
//获取多线程运行的结果
Integer result = ft.get();
System.out.println(result);
}
}
优点 | 缺点 | |
---|---|---|
继承Thread类 | 编程比较简单,可以直接使用Thread类中的方法 | 可扩展性差,不能再继承其他的类 |
实现Runnable接口, 实现Callable接口 | 扩展性强,实现该接口的同时还可以继承其他的类 | 编程相对复杂,不能直接使用Thread类中的方法 |
4.常见的成员方法
- String getName() 返回此线程的名称
- void setName() 设置线程的名字(构造方法也可以设置)
(1)如果没有给线程设置名字,线程会有默认的名字
(2)如果我们要给线程设置名字,可以用set方法设置,也可以用构造方法设置 - static Thread currentThread() 获取当前线程的对象
当JVM虚拟机启动之后,会自动启动多条线程,其中有一条线程叫做main线程,其作用是去调用main方法,并执行里面的代码。 - static void sleep(long time) 让线程休眠指定的时间,单位是毫秒
//MyThread.java
public class MyThread extends Thread{
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + i);
}
}
}
//ThreadDemo4.java
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start();
t2.start();
Thread t = Thread.currentThread();
String name = t.getName();
System.out.println(name);
//哪条线程执行到该方法,此时获取的就是哪条线程的对象
System.out.println(11111);
Thread.sleep(10000);
System.out.println(22222);
}
}
- setPriority(int new Priority) 设置线程的优先级
- final int getPriority() 获取线程的优先级
程优先级只是概率大小问题,优先级高的也不一定每次都先完成。 - final void setDaemon(boolean on) 设置为守护线程
当其他的非守护线程执行完毕之后,守护线程会陆续结束 - public static void yield() 出让线程/礼让线程
表示出让当前CPU的执行权 - public final void join() 插入线程/插队线程
表示将某个线程插入到当前线程之前
5.线程的生命周期
6.同步代码块
在使用多线程时,多个线程执行同一个任务时,它们之间没有限制,这个线程的任务没结束,另一个线程就开始执行任务。可能会发生数据异常问题。
此时需要一个同步代码块为线程任务加上一个锁,当一个线程执行任务时,其他线程不能进入该代码块执行任务。只有等这个线程执行完任务,才可以抢占CPU执行代码。
public class MyThread extends Thread{
static int ticket = 0;
@Override
public void run() {
while(true){
//同步代码块
synchronized (MyThread.class){
if(ticket < 100){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket ++;
System.out.println(getName() + "正在执行第" +ticket + "条任务");
}else{
break;
}
}
}
}
}
7.同步方法
将synchronized关键字加到方法上
特点1:同步方法是锁住方法里面所有的代码
特点2:锁对象不能自己指定。非静态方法:this;静态方法:当前类的字节码文件对象
public class MyRunnable implements Runnable {
int ticket = 0;
@Override
public void run() {
while(true){
if (method()) break;
}
}
private synchronized boolean method() {
if(ticket == 100){
return true;
}else{
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket ++;
System.out.println(Thread.currentThread().getName() + "---" + ticket);
}
return false;
}
}
8.lock锁
为了更清晰的表达如何加锁和如何释放锁
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作
Lock中提供了获得锁和释放锁的方法
void lock() 获得锁
void unlock() 释放锁
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
ReentrantLock构造方法:ReentrantLock() 创建一个ReentrantLock的实例
public class MyRunnable implements Runnable {
int ticket = 0;
static Lock lock = new ReentrantLock();
@Override
public void run() {
while(true){
// synchronized (Thread.class){
lock.lock();
try {
if(ticket == 100){
break;
}else{
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket ++;
System.out.println(Thread.currentThread().getName() + "---" + ticket);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
// }
}
}
}
9.死锁
是一种错误类型,在实现代码时需要避免,一般嵌套锁会产生死锁
10.等待唤醒机制
常见方法:
- void wait() 当前线程等待,被其他线程唤醒
- void notify() 随机唤醒单个线程
- void notifyAll() 唤醒单个线程
生产者和消费者的工作流程
消费者:
- 判断桌子上是否有食物
- 如果没有就等待
- 如果有就开吃
- 吃完之后唤醒厨师继续做
生产者:
- 判断桌上是否有食物
- 有:等待
- 没有:制作食物
- 把食物放在桌子上
- 叫醒等待的消费者开吃
public class ThreadDemo {
public static void main(String[] args) {
Cook c = new Cook();
Foodie f = new Foodie();
c.setName("厨师");
f.setName("吃货");
c.start();
f.start();
}
}
public class Desk {
// 作用:控制生产者和消费者的执行
//是否有面条 0:没有面条 1:有面条
public static int foodFlag = 0;
//总个数
public static int count = 10;
//锁对象
public static Object lock = new Object();
}
public class Cook extends Thread{
/*
* 1.循环
* 2.同步代码块
* 3.判断共享数据是否到了末尾(到了末尾)
* 4.判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
*/
@Override
public void run() {
while (true) {
synchronized (Desk.lock){
if(Desk.count == 0){
break;
}else{
//判断桌上是否有食物
if(Desk.foodFlag == 1){
//有:等待
try {
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
//没有:制作食物
System.out.println("厨师做了一碗面条");
//把食物放在桌子上
Desk.foodFlag = 1;
//叫醒等待的消费者开吃
Desk.lock.notifyAll();
}
}
}
}
}
}
public class Foodie extends Thread{
@Override
public void run() {
/*
* 1.循环
* 2.同步代码块
* 3.判断共享数据是否到了末尾(到了末尾)
* 4.判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
*/
while(true){
synchronized (Desk.lock){
if(Desk.count == 0){
break;
}else{
//先判断桌上有没有面条
if(Desk.foodFlag == 0){
//如果没有,就等待
try {
Desk.lock.wait();//让当前线程跟锁绑定
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
Desk.count --;
//如果有,就开吃
System.out.println("吃货在吃面条,还能再吃" + Desk.count + "碗");
//吃完后,唤醒厨师继续做
Desk.lock.notifyAll();
//修改桌子状态
Desk.foodFlag = 0;
}
}
}
}
}
}
阻塞队列
阻塞队列的实现代码中已经自动加锁了,所以用阻塞队列实现生产者消费者任务时,不用自己写锁。
public class ThreadDemo {
public static void main(String[] args) {
//1.创建阻塞队列的对象
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
//2.创建线程的对象,并把阻塞队列传递进去
Cook c = new Cook(queue);
Foodie f = new Foodie(queue);
c.start();
f.start();
}
}
public class Desk {
// 作用:控制生产者和消费者的执行
//是否有面条 0:没有面条 1:有面条
public static int foodFlag = 0;
//总个数
public static int count = 10;
//锁对象
public static Object lock = new Object();
}
public class Cook extends Thread{
ArrayBlockingQueue<String> queue;
public Cook(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
try{
queue.put("面条");
System.out.println("厨师放了一碗面条");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Foodie extends Thread{
ArrayBlockingQueue<String> queue;
public Foodie(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while(true){
try{
String food = queue.take();
System.out.println(food);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
11.线程的状态![请添加图片描述](https://i-blog.csdnimg.cn/blog_migrate/5e653cdb4752f5b50d242af959877407.png)
12.线程池
核心原理:
(1)创建一个池子,池子中是空的
(2)提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下次再提交任务时,不需要创建新的线程,直接复用已有的线程即可
(3)但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待
核心方法:
public static ExecutorService newCachedThreadPool() 创建一个没有上限的线程池
piblic static ExecutorService newFixedThreadPool() 创建一个有上限的线程池
public class MyThreadPoolDemo {
public static void main(String[] args) throws InterruptedException {
//1.获取线程池对象
ExecutorService pool1 = Executors.newCachedThreadPool();
//2.提交任务
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
Thread.sleep(1000);
//3.销毁线程池
// pool1.shutdown();
}
}
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
}
13.自定义线程池
public class MyThreadPoolDemo1 {
public static void main(String[] args) {
/*
* ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor
* (核心线程数量,最大线程数量,空闲线程最大存活时间,时间单位,任务队列,创建线程工厂,任务的拒绝策略)
*
* 参数一:核心线程数量 不能小于0
* 参数二:最大线程数量 不能小于0,最大数量>=核心线程数量
* 参数三:空闲线程最大存活时间 不能小于0
* 参数四:时间单位 用TimeUnit指定
* 参数五:任务队列 不能为null
* 参数六:创建线程工厂 不能为null
* 参数七:任务的拒绝策略 不能为null
*
* */
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3,
6,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
}
}
线程池大小问题:
获取java的最大并行数:
int count = Runtime.getRuntime().availableProcessors();
System.out.println(count);
CPU密集型运算:最大并行数+1
I/O密集型运算:最大并行数 * 期望CPU利用率 *(总时间(CPU计算时间+等待时间)/ CPU计算时间)