什么是线程?
线程是一个程序内部的一条执行流程
程序中如果只有一条执行流程,那这个程序是单线程的程序
什么是多线程?
多线程是从软硬件上实现的多条执行流程的技术(多条线程由cpu负责调度执行)
定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法
创建MyThread类的对象
调用线程对象的start()方法启动线程(启动后还是执行run方法的)
优缺点:
优点:编码简单
缺点:线程类已经继承Thread,无法继承其他类,不利于功能的扩展。
多线程的注意事项
1、启动线程必须是调用start方法,不是调用run方法
直接调用run方法会当成普通方法执行,此时相当于还是单线程执行
只有调用start方法才是启动一个新的线程执行
2、不要把主线程任务放在启动子线程之前
这样主线程一直是先跑完的,相当于是一个单线程的效果了
package com.cqh.demo01;
/**
* @author cqh
* @date 2024/1/16
* @Description
*/
// 1、子类继承Thread线程类
public class MyThread extends Thread{
// 重写Thread类的 run方法
@Override
public void run() {
// 描述线程执行任务
for (int i = 0; i <=5 ; i++) {
System.out.println("子线程= " + i);
}
}
}
package com.cqh.demo01;
/**
* @author cqh
* @date 2024/1/16
* @Description
*/
public class ThreadTest01 {
// main方法是由一条默认的主线程执行
public static void main(String[] args) {
// 3、创建一个MyThread线程类的对象代表一个线程
Thread myThread = new MyThread();
// 启动线程
myThread.start();
for (int i = 0; i <=5 ; i++) {
System.out.println("主线程 = " + i);
}
}
}
定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法
创建MyRunnable任务对象
把MyRunnable任务对象交给Thread处理?
调用线程对象的start()方法启动线程
优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强。
缺点:需要多一个Runnable对象。
package com.cqh.demo02;
/**
* @author cqh
* @date 2024/1/16
* @Description
*/
// 1、定义一个类实现runnable接口
public class MyRunnable implements Runnable{
// 2、重写run方法
@Override
public void run() {
// 线程执行的任务
for (int i = 0; i <=5 ; i++) {
System.out.println("子线程= " + i);
}
}
}
package com.cqh.demo02;
/**
* @author cqh
* @date 2024/1/16
* @Description
*/
public class ThreadTest01 {
// main方法是由一条默认的主线程执行
public static void main(String[] args) {
// 3、创建一个MyRunnable线程类的对象代表一个线程
Runnable myRunnable = new MyRunnable();
// 4、把任务对象交给线程对象处理
// 启动线程
new Thread(myRunnable).start();
for (int i = 0; i <=5 ; i++) {
System.out.println("主线程 = " + i);
}
}
}
?
可以创建Runnable的匿名内部类对象。
再交给Thread线程对象。
再调用线程对象的start()启动线程。
package com.cqh.demo03;
import com.cqh.demo01.MyThread;
/**
* @author cqh
* @date 2024/1/16
* @Description
*/
public class ThreadTest01 {
// main方法是由一条默认的主线程执行
public static void main(String[] args) {
// 1、直接创建Runnable接口的匿名内部类形式
Runnable runnable = new Runnable(){
@Override
public void run() {
for (int i = 0; i <=5 ; i++) {
System.out.println("子线程 = " + i);
}
}
};
// 2、把任务对象交给线程对象处理
// 启动线程
new Thread(runnable).start();
for (int i = 0; i <=5 ; i++) {
System.out.println("主线程 = " + i);
}
}
}
?
package com.cqh.demo03;
import com.cqh.demo01.MyThread;
/**
* @author cqh
* @date 2024/1/16
* @Description
*/
public class ThreadTest01 {
// main方法是由一条默认的主线程执行
public static void main(String[] args) {
// 1、直接创建Runnable接口的匿名内部类形式
Runnable runnable = new Runnable(){
@Override
public void run() {
for (int i = 0; i <=5 ; i++) {
System.out.println("子线程1 = " + i);
}
}
};
// 2、把任务对象交给线程对象处理
// 启动线程
new Thread(runnable).start();
// 简化1
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <=5 ; i++) {
System.out.println("子线程2 = " + i);
}
}
}).start();
// 简化2
new Thread(()-> {
for (int i = 0; i <=5 ; i++) {
System.out.println("子线程3 = " + i);
}
}).start();
for (int i = 0; i <=5 ; i++) {
System.out.println("主线程 = " + i);
}
}
}
?前两种线程创建方式都存在的一个问题
假如线程执行完毕后有一些数据需要返回,他们重写的run方法均不能直接返回结果。
解决
JDK50提供了Callable接口和FutureTask类来实现(多线程的第三种创建方式)
1、创建任务对象
定义一个类实现Callable接口,重写call方法,封装要做的事情,和要返回的数据
把Callable类型的对象封装成FutureTask (线程任务对象)
2、把线程任务对象交给Thread对象。
3、调用Thread对象的start方法启动线程
4、线程执行完毕后、通过FutureTask对象的的get方法去获取线程任务执行的结果。
package com.cqh.demo04;
import java.util.concurrent.Callable;
/**
* @author cqh
* @date 2024/1/16
* @Description
*/
// 创建一个类实现Callable接口
public class MyCallable implements Callable<String> {
private int n;
public MyCallable(int n){
this.n=n;
}
// 2、重写call方法
@Override
public String call() throws Exception {
int sum=0;
for (int i = 0; i <=n ; i++) {
sum+=i;
}
return "线程求出了1-"+n+"的和为"+sum;
}
}
package com.cqh.demo04;
import com.cqh.demo02.MyRunnable;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @author cqh
* @date 2024/1/16
* @Description
*/
public class ThreadTest01 {
// main方法是由一条默认的主线程执行
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 3、创建一个Callable线程类的对象代表一个线程
Callable<String> myCallable = new MyCallable(100);
// 4、把callable对象封装成一个futureTask对象
FutureTask<String> stringFutureTask = new FutureTask<>(myCallable);
// 5、 把任务对象交给线程对象
// 启动线程
new Thread(stringFutureTask).start();
// 6、获取线程执行完成后的结果
String s = stringFutureTask.get();
System.out.println("s = " + s);
}
}
优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强,可以在线程执行完毕后去获取线程执行的结果,
缺点:编码复杂一点。?
package com.cqh.demo05;
/**
* @author cqh
* @date 2024/1/16
* @Description
*/
// 1、子类继承Thread线程类
public class MyThread extends Thread{
public MyThread(){
}
// 构造器方法为参数设置名字
public MyThread(String name){
super(name);
}
// 重写Thread类的 run方法
@Override
public void run() {
// 描述线程执行任务
Thread thread = Thread.currentThread();
for (int i = 0; i <=5 ; i++) {
if (i==5){
try {
// 线程暂停5秒 再执行
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(thread.getName()+"子线程= " + i);
}
}
}
package com.cqh.demo05;
/**
* @author cqh
* @date 2024/1/16
* @Description
*/
public class ThreadTest01 {
// main方法是由一条默认的主线程执行
public static void main(String[] args) throws InterruptedException {
// 3、创建一个MyThread线程类的对象代表一个线程
Thread myThread = new MyThread();
//设置线程名字
myThread.setName("子1");
// 启动线程
myThread.start();
// 获取线程名字
System.out.println( myThread.getName());
MyThread thread2 = new MyThread("子2");
thread2.start();
// 该线程执行完后继续执行其他线程
thread2.join();
// 那个线程执行它它就会得到哪个线程对象
// 可以用这个方法获取主线程名称
Thread thread = Thread.currentThread();
thread.setName("不错的线程 ");
for (int i = 0; i <=5 ; i++) {
System.out.println( thread.getName()+"主线程 = " + i);
}
}
}
多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题。
取钱案例
具体实现代码
用的是普通java项目
package com.cqh.demo06;
/**
* @author cqh
* @date 2024/1/16
* @Description
*/
public class Account {
private String carId;// 银行卡号
private double money;// 余额
public Account() {
}
public Account(String carId, double money) {
this.carId = carId;
this.money = money;
}
public String getCarId() {
return carId;
}
public void setCarId(String carId) {
this.carId = carId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"carId='" + carId + '\'' +
", money=" + money +
'}';
}
public void drawMoney(double money) {
Thread thread = Thread.currentThread();
String name = thread.getName();
// 1、判断余额是否足够
if(this.money>=money){
System.out.println(name+"来取钱"+money+"成功");
this.money-=money;
System.out.println(name+"取钱,余额剩余"+this.money);
}else {
System.out.println(name+"来取钱,余额不足");
}
}
}
package com.cqh.demo06;
/**
* @author cqh
* @date 2024/1/16
* @Description
*/
public class DrawThread extends Thread{
private Account account;
public DrawThread(Account account,String name) {
super(name);
this.account=account;
}
@Override
public void run() {
// 取钱
account.drawMoney(10000);
}
}
package com.cqh.demo06;
/**
* @author cqh
* @date 2024/1/16
* @Description
*/
public class ThreadTest {
public static void main(String[] args) {
// 1、创建账户对象,代表两个人的共享账户
Account account = new Account("CardId-110",10000);
// 2、创建两个线程分别代表小明、小红分别在同一个账户去钱
new DrawThread(account,"小明").start();
new DrawThread(account,"小红").start();
}
}
解决线程安全问题
让多个线程实现先后依次访问共享资源,这样就解决了安全问题
加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来.
作用:把共享资源的核心代码给上锁,以保证线程安全
原理: 每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行。
注意:?
修改?
效果?
锁对象的使用规范
建议使用共享资源作为锁对象,对于实例方法建议使用this作为锁对象。
对于静态方法建议使用字节码 (类名.class) 对象作为锁对象
作用:把访问共享资源的核心方法给上锁,以此保证线程安全
原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行.
修改
效果
同步方法底层原理
同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码
如果方法是实例方法:同步方法默认用this作为的锁对象。
如果方法是静态方法:同步方法默认用类名.class作为的锁对象。?
同步代码块与同步方法比较
范围上:
同步代码块锁的范围更小,同步方法锁的范围更大
Lock锁是]DK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大
Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象。
什么是线程通信?
当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,以相互协调,并避免无效的资源争夺
线程通信的常见模型(生产者与消费者模型)
生产者线程负责生产数据
消费者线程负责消费生产者生产的数据
注意:生产者生产完数据应该等待自己,通知消费者消费;消费者消费完数据也应该等待自己,再通知生产者生产
需求:
3个生产者线程,负责生产包子,每个线程每次只脑生产1个包子放在来子上,2个消费者线程负责吃包子,每人每次只能从集予上产1个包子吃。
注意
上述方法应该使用当前同步锁对象进行调用
具体代码
package com.cqh.demo07;
import java.util.ArrayList;
import java.util.List;
/**
* @author cqh
* @date 2024/1/16
* @Description
*/
public class Desk {
private List<String> list=new ArrayList<>();
// 生产
public synchronized void put() {
try {
String name = Thread.currentThread().getName();
// 判断有没有包子
if (list.size()==0){
list.add(name+"做的肉包子");
System.out.println(name+"做了一个肉包子");
Thread.sleep(2000);
// 唤醒别人 等待自己
this.notifyAll();
this.wait();
}else {
// 唤醒别人 等待自己
this.notifyAll();
this.wait();
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 消费
public synchronized void get() {
try {
String name = Thread.currentThread().getName();
if (list.size()==1){
System.out.println(name+"吃了" + list.get(0));
list.clear();
Thread.sleep(1000);
this.notifyAll();
this.wait();
}else {
this.notifyAll();
this.wait();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.cqh.demo07;
/**
* @author cqh
* @date 2024/1/16
* @Description
*/
public class ThreadTest {
public static void main(String[] args) {
Desk desk = new Desk();
// 创建3个生产者
new Thread(()-> {
while (true){
desk.put();
}
},"厨师1").start();
new Thread(()-> {
while (true){
desk.put();
}
},"厨师2").start();
new Thread(()-> {
while (true){
desk.put();
}
},"厨师3").start();
//创建2个消费者
new Thread(()-> {
while (true){
desk.get();
}
},"消费者1").start();
new Thread(()-> {
while (true){
desk.get();
}
},"消费者2").start();
}
}
什么是线程池?
线程池就是一个可以复用线程的技术。
不使用线程池的问题
?用户每发起一个请求,后台就需要创建一个新线程来处理,下次新任务来了肯定又要创建新线程处理的,而创建新线程的开销是很大的,并且请求过多时,肯定会产生大量的线程出来,这样会严重影响系统的性能。
JDK 5.0起提供了代表线程池的接口: ExecutorService。?
如何得到线程池对象??
方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象?
方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象
参数一: corePoolSize:指定线程池的核心线程的数量
参数二:maximumPoolSize: 指定线程池的最大线程数量。
参数三: keepAliveTime: 指定临时线程的存活时间
参数四: unit: 指定临时线程存活的时间单位(秒、分、时、天)
参数五:workQueue: 指定线程池的任务队列。
参数六: threadFactory: 指定线程池的线程工厂
参数七:handler;指定线程池的任务拒绝策略(线程都在忙,任务队列也满了的时候,新任务来了该怎么处理)?
线程池的注意事项
1、临时线程什么时候创建?
新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。
2、什么时候会开始拒绝新任务?
核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务。
?
?
package com.cqh.demo08;
/**
* @author cqh
* @date 2024/1/16
* @Description
*/
public class MyRunnable implements Runnable{
@Override
public void run() {
// 描述任务是做什么的
System.out.println(Thread.currentThread().getName()+"-----" );
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package com.cqh.demo08;
import java.util.concurrent.*;
/**
* @author cqh
* @date 2024/1/16
* @Description
*/
public class ThreadPoolTest {
public static void main(String[] args) {
// 1、通过ThreadPoolExecutor创建一个线程池对象
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
3, 5, 8,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(4),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
MyRunnable myRunnable = new MyRunnable();
poolExecutor.execute(myRunnable);// 核心线程使用
poolExecutor.execute(myRunnable);// 核心线程使用
poolExecutor.execute(myRunnable);// 核心线程使用
poolExecutor.execute(myRunnable);
poolExecutor.execute(myRunnable);
poolExecutor.execute(myRunnable);
poolExecutor.execute(myRunnable);
// 到了临时线程的创建时机了
poolExecutor.execute(myRunnable);
poolExecutor.execute(myRunnable);
// 到了临时线程的创建时机
poolExecutor.execute(myRunnable);
// poolExecutor.shutdown();// 等着线程池的任务全部执行完毕后,再关闭线程池
// poolExecutor.shutdownNow();// 立即关闭线程池
}
}
package com.cqh.demo08;
import java.util.concurrent.Callable;
/**
* @author cqh
* @date 2024/1/16
* @Description
*/
public class MyCallable implements Callable<String> {
private int n;
public MyCallable(int n){
}
@Override
public String call() throws Exception {
int sum=0;
for (int i = 0; i <=5 ; i++) {
sum+=1;
}
return Thread.currentThread().getName()+"求出了1-"+n+"的和是"+sum;
}
}
package com.cqh.demo08;
import java.util.concurrent.*;
/**
* @author cqh
* @date 2024/1/16
* @Description
*/
public class ThreadPoolTest1 {
public static void main(String[] args) throws Exception {
// 1、通过ThreadPoolExecutor创建一个线程池对象
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
3, 5, 8,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(4),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
// 使用线程处理callable任务
Future<String> submit1 = poolExecutor.submit(new MyCallable(100));
Future<String> submit2 = poolExecutor.submit(new MyCallable(200));
Future<String> submit3 = poolExecutor.submit(new MyCallable(300));
Future<String> submit4 = poolExecutor.submit(new MyCallable(400));
System.out.println(submit1.get());
System.out.println(submit2.get());
System.out.println(submit3.get());
System.out.println(submit4.get());
}
}
概念:
它是一个线程池的工具类,提供了很多静态方法用于返回不同特点的线程池对象。
?
注意 : 这些方法的底层,都是通过线程池的实现类ThreadPoolExecutor创建的线程池对象?
package com.cqh.demo08;
import java.util.concurrent.*;
/**
* @author cqh
* @date 2024/1/16
* @Description
*/
public class ThreadPoolTest2 {
public static void main(String[] args) throws Exception {
// 1、通过Executors创建一个线程池对象
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 使用线程处理callable任务
Future<String> submit1 = executorService.submit(new MyCallable(100));
Future<String> submit2 = executorService.submit(new MyCallable(200));
Future<String> submit3 = executorService.submit(new MyCallable(300));
Future<String> submit4 = executorService.submit(new MyCallable(400));
System.out.println(submit1.get());
System.out.println(submit2.get());
System.out.println(submit3.get());
System.out.println(submit4.get());
}
}
计算密集型的任务:核心线程数=CPU的核数 + 1
I0集型的任务:核心程数量 = CPU核数 * 2
大型并发系统环境中使用Executors如果不注意可能会出现系统风险。
什么是进程?
正在运行的程序(软件)就是一个独立的进程
线程是属于进程的,一个进程中可以同时运行很多个线程
进程中的多个线程其实是并发和并行执行的
什么是并发?
进程中的线程是由CPU负责调度执行的,但CPU能同时处理线程的数量有限,为了保证全部线程都能往前执行,CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发
简单来说就是:
在同一个时刻上,同时有多个线程在被CPU调度执行
也就是线程从生到死的过程中,经历的各种状态及状态转换。
理解线程这些状态有利于提升并发编程的理解能力。
Java线程的状态
Java总共定义了6种状态
6种状态都定义在Thread类的内部枚举类中
?线程的6种状态互相转换
?
?悲观锁:一上来就加锁,没有安全感。每次只能一个线程进入访问完毕后,再解锁。 线程安全,性能较差!
乐观锁:一开始不上锁,认为是没有问题的,大家一起跑,等要出现线程安全问题的时候才开始控制。线程安全,性能较好。
悲观锁案例
乐观锁
package com.cqh.demo09;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author cqh
* @date 2024/1/16
* @Description
*/
public class MyRunnable2 implements Runnable {
// 整数修改的乐观锁:原子类
private AtomicInteger count=new AtomicInteger();
@Override
public void run() {
for (int i = 0; i <=100 ; i++) {
synchronized (this){
System.out.println(Thread.currentThread().getName()+"count = " + (count.incrementAndGet()));
}
}
}
}
?练习题
目标: .有100份礼品,小红,小明两人同时发送,当剩下的礼品小于10份的时候则不再送出, 利用多线程模拟该过程并将线程的名称打印出来。并最后在控制台分别打印小红,小明各自送出多少分礼物。
package com.cqh.demo10;
import java.util.List;
import java.util.Random;
/**
* @author cqh
* @date 2024/1/17
* @Description
*/
public class SendThread extends Thread {
private List<String> gift;
private String name;
private int count=0;
public SendThread(List<String> gift, String name) {
super(name);
this.gift = gift;
this.name = name;
}
@Override
public void run() {
Random random = new Random();
while (true) {
if (gift.size() < 10) {
return;
}
synchronized (gift) {
String remove = gift.remove(random.nextInt(gift.size()));
System.out.println(name+"发出了礼物" + remove);
count++;
}
}
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
package com.cqh.demo10;
import jdk.nashorn.internal.ir.CallNode;
import java.util.ArrayList;
import java.util.Random;
/**
* @author cqh
* @date 2024/1/16
* @Description
*/
public class MyTest {
public static void main(String[] args) throws InterruptedException {
// 目标: .有100份礼品,小红,小明两人同时发送,当剩下的礼品小于10份的时候则不再送出,
// 利用多线程模拟该过程并将线程的名称打印出来。并最后在控制台分别打印小红,小明各自送出多少分礼物。
ArrayList<String> gift = new ArrayList<>();
String[] names={"苹果手机","笔记本电脑","ipad","耳机","音响"};
Random random = new Random();
for (int i = 0; i <100 ; i++) {
gift.add(names[random.nextInt(names.length)]+(i+1));
}
SendThread sendThread1 = new SendThread(gift,"小明");
SendThread sendThread2 = new SendThread(gift, "小红");
sendThread1.start();
sendThread1.join();
sendThread2.start();
sendThread2.join();
System.out.println(sendThread1.getName() +"发送了"+ sendThread1.getCount()+"个礼物");
System.out.println(sendThread2.getName() +"发送了"+ sendThread2.getCount()+"个礼物");
}
}