线程定义:线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中实际运作单位。简单来说,应用软件中相互独立,可以同时运作的功能。
多线程作用:有了多线程,我们就可以让程序同时做多件事情。
应用场景:只要你想让多个事件同时运行就需要用到多线程。
并发:在同一时刻,有多个指令在单个CPU上交替执行。
并行:在同一时刻,有多个指令在多个CPU上同时执行。
多线程的实现方式
1.继承Thread类的方式进行执行
步骤:
自己定义一个类继承Thread
重写run方法
创建子类的对象,并启动线程。
public class MyThread extends Thread{
@Override
public void run() {
for(int i=0;i<10;i++) {
System.out.println("Hello");
}
}
}
public class test {
public static void main(String [] args) {
//创建子类对象,开启线程
MyThread mt=new MyThread();
mt.start();
}
}
2.实现Runnable接口的方式进行实现
步骤:
自己定义一个类实现Runnable接口
重写里面的run方法
创建自己类的对象
创建一个Thread类的对象,并开启线程。
public class MyThread implements Runnable{
@Override
public void run() {
for(int i=0;i<10;i++) {
System.out.println("world");
}
}
}
public class test {
public static void main(String [] args) {
//创建自己类的对象
MyThread mt=new MyThread();
//创建Thread对象,并开启线程
Thread t=new Thread(mt);
t.start();
}
}
若想看看两个线程的相互执行,获取线程的名字:
public class MyThread implements Runnable{
//在此获取线程的名字
@Override
public void run() {
for(int i=0;i<10;i++) {
Thread t=Thread.currentThread();//获取当前线程对象
System.out.println(t.getName()+":world");
}
}
}
public class test {
public static void main(String [] args) {
//创建自己类的对象
MyThread mt=new MyThread();
//创建Thread对象,并开启线程
Thread t1=new Thread(mt);
Thread t2=new Thread(mt);
//给线程设置名字
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
3.利用Callable接口和Future接口方式实现
特点:可以获取多线程运行的结果。
1)创建一个MyCallable类实现Callable接口;
2)重写call方法(是有返回值的,表示多线程运行的结果);
3)创建MyCallable的对象(表示多线程要执行的任务);
4)创建FutureTask的对象(作用管理多线程的运行结果);
5)创建Thread类的对象,并启动(表示线程)。
public class MyCallable implements Callable<Integer>{
//计算1~10的和
@Override
public Integer call() throws Exception {
int sum=0;
for(int i=0;i<=10;i++) {
sum+=i;
}
return sum;
}
}
public class test {
public static void main(String [] args) throws InterruptedException, ExecutionException {
MyCallable mc=new MyCallable();
FutureTask<Integer> ft=new FutureTask<>(mc);//管理多线程运行结果
Thread t=new Thread(ft);
t.start();//开启线程
//获取返回值
Integer sum=ft.get();
System.out.println(sum);
}
}
多线程的成员方法
获取与设置线程的名字
注:若我们未给线程设置名字,线程也有默认的名字,格式:Thread-X(X为序号,从0开始)。
若我们要给线程设置名字,可以用set方法,也可以用构造方法(调用父类的构造方法)设置。
1)用set进行设置。
public class MyThread extends Thread{
@Override
public void run() {
for(int i=0;i<10;i++) {
//获取线程名字
System.out.println(getName()+":"+i);
}
}
}
public class test {
public static void main(String [] args) {
MyThread t1=new MyThread();
MyThread t2=new MyThread();
//设置线程名字,用set方法设置
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
线程之间交替运行。
2)用构造方法进行设置
public class MyThread extends Thread{
public MyThread() {//调用父类的构造方法
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for(int i=0;i<10;i++) {
System.out.println(getName()+":"+i);
}
}
}
public class test {
public static void main(String [] args) {
MyThread t1=new MyThread("飞机");
MyThread t2=new MyThread("鼠标");
t1.start();
t2.start();
}
}
线程休眠
static void sleep(long time)——让线程休眠指定时间,单位毫秒。
注:哪条线程执行到这个方法,那么哪条线程就会在这停留对应的时间。当时间到了之后,线程会自动醒来,执行下面的代码。
public class MyThread extends Thread{
public MyThread() {//调用父类的构造方法
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for(int i=0;i<10;i++) {
try {
Thread.sleep(1000);//每个一秒打印一个数据
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(getName()+":"+i);
}
}
}
public class test {
public static void main(String [] args) {
MyThread t1=new MyThread("飞机");
MyThread t2=new MyThread("鼠标");
t1.start();
t2.start();
}
}
线程的优先级
在java中使用抢占式调度,其特点为随机性。优先级为1~10,默认优先级为5,优先级越高,抢到CPU的概率越大,但并不是百分之一百抢到CPU。
public class MyThread extends Thread{
@Override
public void run() {
for(int i=0;i<7;i++) {
System.out.println(getName()+":"+i);
}
}
}
public class test {
public static void main(String [] args) {
MyThread t1=new MyThread();
MyThread t2=new MyThread();
//设置线程名字
t1.setName("键盘");
t2.setName("鼠标");
//设置线程的优先级
t1.setPriority(1);
t2.setPriority(10);
t1.start();
t2.start();
}
}
鼠标优先级大,鼠标先执行完毕。
守护线程
当其他非守护线程结束后,守护线程也会陆续结束(一般不会执行完毕)。
public class MyThread1 extends Thread{
@Override
public void run() {
for(int i=0;i<8;i++) {
System.out.println(getName()+":"+i);
}
}
}
public class MyThread2 extends Thread{
@Override
public void run() {
for(int i=0;i<100;i++) {
System.out.println(getName()+":"+i);
}
}
}
public class test {
public static void main(String [] args) {
MyThread1 t1=new MyThread1();
MyThread2 t2=new MyThread2();
//设置线程名字
t1.setName("线程");
t2.setName("备份");
//将t2设置为守护线程
t2.setDaemon(true);
t1.start();
t2.start();
}
}
礼让线程
礼让线程也叫出让线程,作用:尽可能使结果均匀一点。
public class MyThread extends Thread{
@Override
public void run() {
for(int i=0;i<8;i++) {
System.out.println(getName()+":"+i);
Thread.yield();//出让线程
}
}
}
public class test {
public static void main(String [] args) {
MyThread t1=new MyThread();
MyThread t2=new MyThread();
//设置线程名字
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
插入线程
public class MyThread extends Thread{
@Override
public void run() {
for(int i=0;i<100;i++) {
System.out.println(getName()+":"+i);
}
}
}
public class test {
public static void main(String [] args) throws InterruptedException {
MyThread t1=new MyThread();
t1.setName("土豆");
t1.start();
t1.join();//插入线程,将土豆线程插入到main线程之前
for(int i=0;i<10;i++) {
System.out.println("main线程"+i);
}
}
}
线程的生命周期
同步代码块
把操作共享的代码锁起来。
锁对象一定要求是唯一的。
利用同步代码块可解决线程安全问题。若没有锁,可能会出现数据重复,数据超出要求的范围等等问题。
练习
某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票。
public class MyThread extends Thread{
//设置一个静态数据,表示这个类所有对象可以共享
static int ticket=0;//票数
static Object obj=new Object();//锁对象一定要是唯一的
@Override
public void run() {
while(true) {
synchronized(obj) {
if(ticket<100) {
try {
Thread.sleep(100);//数据慢慢的输出
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
ticket++;
System.out.println(getName()+"今天卖出第"+ticket+"票");
}else {
break;
}
}
}
}
}
public class test {
public static void main(String [] args) {
MyThread t1=new MyThread();
MyThread t2=new MyThread();
MyThread t3=new MyThread();
//设置名字
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
同步方法
就是把synchronized关键字加到方法上。
同步方法是用同步代码块提取方法,改编而来的。
练习
某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票。(使用同步方法完成,技巧:先构建好同步代码块,在进行变换)
public class MyThread implements Runnable{//使用第二种方法
int ticket=0;//使用第二种方法创建对象,不用设置这个为静态的
@Override
public void run() {
while(true) {
if (mehod()) break;
}
}
private synchronized boolean mehod() {//同步方法
if(ticket==100) {
return true;
}else {
try {
Thread.sleep(100);//若想要看到多个线程交织进行,可采用sleep让线程睡一会
} catch (final InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
ticket++;
System.out.println(Thread.currentThread().getName()+"今天卖了第"+ticket+"张票");
}
return false;
}
}
public class test1 {
public static void main(String[] args) {
MyThread mt=new MyThread();
Thread t1=new Thread(mt);
Thread t2=new Thread(mt);
Thread t3=new Thread(mt);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
Lock锁
练习
某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票。(利用Lock锁进行)
在Lock锁当中利用try-catch-finall来进行开锁关锁。,将while循环中的主体程序写完之后,从获得锁开始后利用快捷方法生成try-catch-finall,finally中写释放锁。
public class MyThread extends Thread{
//设置一个静态数据,表示这个类所有对象可以共享
static int ticket=0;
Lock lock=new ReentrantLock();//创建一个锁对象
@Override
public void run() {
while(true) {
// synchronized (MyThread.class) {
lock.lock();//获得锁
try {
if (ticket < 100) {
Thread.sleep(100);//数据慢慢的输出
ticket++;
System.out.println(getName() + "今天卖出第" + ticket + "票");
} else {
break;
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();//释放锁
}
// }
}
}
}
public class test1 {
public static void main(String[] args) {
MyThread t1=new MyThread();
MyThread t2=new MyThread();
MyThread t3=new MyThread();
//设置名字
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
书写进程步骤:
生产者和消费者(等待唤醒机制)
生产者消费者模式是一个十分经典的多线程协作的模式。
例如:吃货和厨师(消费者和生产者)
常见方法:
实现方法1:基本方法
有:生产者、消费者、控制生产者和消费者的代码
先写中间:
public class Desk {//中间变量
//作用:控制消费者和生产者的代码
//定义桌子状态,0无面条,1有面条
public static int state=0;
//定义吃货最大能吃多少,总个数
public static int count=10;
//定义锁对象
public static Object lock=new Object();
}
消费者:
public class Eat extends Thread{//消费者
//循环
//同步代码块(同步方法,锁方法均可)
//判断共享数据是否到末尾(先写到了末尾,再写未到末尾)
@Override
public void run(){
while(true){
synchronized (Desk.lock){//锁对象
//判断共享数据是否到末尾
if(Desk.count==0){
break;
}else{
//先判断桌子上物品状态
if(Desk.state==0){
//如果没有消费者等待
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else{
//如果有
Desk.count--;
System.out.println("这个人还能再吃"+Desk.count+"碗饭");
//吃完唤醒厨师继续做
Desk.lock.notifyAll();
//修改桌子状态
Desk.state=0;
}
}
}
}
}
}
生产者:
public class Cook extends Thread{//生产者
@Override
public void run(){
while(true){
synchronized (Desk.lock){
if(Desk.count==0){
break;
}else{
//判断桌子状态
if(Desk.state==1){
//如果有,就等待
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else{
//如果没有
System.out.println("厨师做了一碗面");
//修改桌子状态
Desk.state=1;
//唤醒消费者
Desk.lock.notifyAll();
}
}
}
}
}
}
测试类:
public class test1 {
public static void main(String[] args) {
Cook c=new Cook();
Eat e=new Eat();
//设置名字
c.setName("厨师:");
e.setName("吃货");
//开启线程
c.start();
e.start();
}
}
实现方法2:阻塞队列方法实现
阻塞队列的接口和实现类:
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) {
throw new RuntimeException(e);
}
}
}
}
public class Eat extends Thread{//消费者
ArrayBlockingQueue<String> queue;
public Eat(ArrayBlockingQueue<String> queue) {
this.queue=queue;
}
@Override
public void run(){
while(true){
try {
String food=queue.take();
System.out.println(food);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class test1 {
public static void main(String[] args) {
//创建阻塞队列
ArrayBlockingQueue<String> queue=new ArrayBlockingQueue<>(10);//定义阻塞队列中的队列长度
Cook c=new Cook(queue);
Eat e=new Eat(queue);
c.setName("厨师");
e.setName("吃货");
c.start();
e.start();
}
}
线程的六种状态
综合练习
练习1:抢红包
(因为获取Double类型的随机数只能在JDK17中,抢红包实际抢到的是小数,在此题下理想化,认为是整数)
抢红包也用到了多线程。假设: 100块,分成了3个包,现在有5个人去抢。其中,红包是共享数据。5个人是5条线程。
打印结果如下:
XXX抢到了XXX元
XXX抢到了XXX元
XXX抢到了XXX元
XXX没抢到
XXX没抢到
package test1;
import java.util.Random;
public class MyThread extends Thread{
//定义静态变量
static int money=100;//总金额
static int count=3;//红包个数
static final int MIN=1;//抢到的金额的最小值
@Override
public void run() {
synchronized (MyThread.class) {
//判断金额
if (money == 0) {
System.out.println(getName() + "没有抢到红包");
} else {
//判断红包个数
int prize = 0;//中奖金额
if (count == 1) {
//只剩一个红包,中奖金额就是余额
prize = money;
} else {
//进行随机数,获取红包金额
Random r = new Random();
int bound = money - (count - 1) * MIN;
prize = r.nextInt(bound);
if (prize < MIN) {
prize = MIN;
}
}
money = money - prize;
count--;
System.out.println(getName() + "抢到了" + prize + "元");
}
}
}
}
public class test1 {
public static void main(String[] args) {
MyThread t1=new MyThread();
MyThread t2=new MyThread();
MyThread t3=new MyThread();
MyThread t4=new MyThread();
MyThread t5=new MyThread();
t1.setName("张三");
t2.setName("李四");
t3.setName("王五");
t4.setName("赵六");
t5.setName("导航");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
练习2:抽奖池抽奖
有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为{10,5,20,50,100,200,500,800,2,80,300,700};
创建两个抽奖箱(线程)设置线程名称分别为“ 抽奖箱1”,‘ '抽奖箱2”随机从抽奖池中获取奖项元素并打印在控制台上格式如下:
每次抽出一个奖项就打印一个(随机)
抽奖箱1又产生了一个10元大奖
抽奖箱1又产生了一个100元大奖
抽奖箱2又产生了一个700元大奖
分析:可用集合和数组进行,但数组去重比较复杂,利用集合完成。
public class MyThread extends Thread {
ArrayList<Integer> list;//静态变量
//可以利用构造方法
public MyThread(ArrayList<Integer> list) {
this.list = list;
}
@Override
public void run() {
while (true) {
synchronized (MyThread.class) {
if (list.size() == 0) {
break;
} else {
Collections.shuffle(list);//打乱list数据
int prize = list.remove(0);
System.out.println(getName() + "又产生了一个" + prize + "大奖");
}
}
try {
Thread.sleep(100);//目的是可以让两个线程交替进行显示
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class test1 {
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);
MyThread t1=new MyThread(list);
MyThread t2=new MyThread(list);
t1.setName("抽奖箱1");
t2.setName("抽奖箱2");
t1.start();
t2.start();
}
}
练习3:多线程统计并求最大值
在上一题基础上继续完成如下需求:
每次抽的过程中,不打印,抽完时一次性打印(随机),在此次抽奖过程中,抽奖箱1总共产生了6个奖项。
分别为: 10,20,100,500,2,300最高奖项为300元,总计额为932元在此次抽奖过程中,抽奖箱2总共产生了6个奖项。
分别为: 5, 50,200,800,80,700最高奖项为800元,总计额为1835元
解法一:对第一个程序进行改编
public class MyThread extends Thread{
ArrayList<Integer> list;//静态变量
//可以利用构造方法
public MyThread(ArrayList<Integer> list){
this.list=list;
}
//创建两个集合来存放数据
static ArrayList<Integer> list1=new ArrayList<>();
static ArrayList<Integer> list2=new ArrayList<>();
@Override
public void run(){
while(true){
synchronized (MyThread.class) {
if(list.size()==0){
if("抽奖箱1".equals(getName())){
System.out.println("抽奖箱1"+list1);
int max=0;
int count=0;
for(int i=0;i<list1.size();i++){
if(max<= list1.get(i)){
max=list1.get(i);
}
count+=list1.get(i);
}
System.out.println("抽奖箱1最大奖"+max);
System.out.println("抽奖箱1总计"+count);
}else if("抽奖箱2".equals(getName())) {
System.out.println("抽奖箱2"+list2);
int max=0;
int count=0;
for(int i=0;i<list2.size();i++){
if(max<= list2.get(i)){
max=list2.get(i);
}
count+=list2.get(i);
}
System.out.println("抽奖箱2最大奖"+max);
System.out.println("抽奖箱2总计"+count);
}
break;
}else{
Collections.shuffle(list);//打乱list数据
int prize=list.remove(0);
//进行判断
if("抽奖箱1".equals(getName())){
list1.add(prize);
}else if("抽奖箱2".equals(getName())){
list2.add(prize);
}
}
}
}
try {
Thread.sleep(10);//目的是可以让两个线程交替进行显示
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
解法二:线程栈
public class MyThread extends Thread {
ArrayList<Integer> list;//静态变量
//可以利用构造方法
public MyThread(ArrayList<Integer> list) {
this.list = list;
}
@Override
public void run() {
ArrayList<Integer> List=new ArrayList<>();
while (true) {
synchronized (MyThread.class) {
if (list.size() == 0) {
System.out.println(getName()+List);
break;
} else {
Collections.shuffle(list);//打乱list数据
int prize = list.remove(0);
List.add(prize);
}
}
try {
Thread.sleep(100);//目的是可以让两个线程交替进行显示
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
练习4:多线程之间的比较
在上一题基础上继续完成如下需求:在此次抽奖过程中,抽奖箱1总共产生了6个奖项,分别为: 10,20,100,500,2,300,最高奖项为300元,总计额为932元
在此次抽奖过程中,抽奖箱2总共产生了6个奖项,分别为: 5,50,200,800,80,700
最高奖项为800元,总计额为1835元
在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为800元
分析:最大奖项应该这两个线程结束后再比较,采用有返回值的线程创建方法,将最大值返回后打印。
public class MyCallable implements Callable<Integer> {
ArrayList<Integer> list;//静态变量
//可以利用构造方法
public MyCallable(ArrayList<Integer> list) {
this.list = list;
}
@Override
public Integer call() throws Exception {
ArrayList<Integer> List=new ArrayList<>();
while (true) {
synchronized (MyCallable.class) {
if (list.size() == 0) {
System.out.println(Thread.currentThread().getName()+List);
break;
} else {
Collections.shuffle(list);//打乱list数据
int prize = list.remove(0);
List.add(prize);
}
}
Thread.sleep(100);//目的是可以让两个线程交替进行显示
}
//把集合中的最大值返回
if(List.size()==0){
return null;
}else{
return Collections.max(List);
}
}
}
public class test1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ArrayList<Integer> list=new ArrayList<>();
Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);
MyCallable mc=new MyCallable(list);
FutureTask<Integer> ft1=new FutureTask<>(mc);
FutureTask<Integer> ft2=new FutureTask<>(mc);
Thread t1=new Thread(ft1);
Thread t2=new Thread(ft2);
t1.setName("抽奖箱1");
t2.setName("抽奖箱2");
t1.start();
t2.start();
int max1=ft1.get();
int max2=ft2.get();
System.out.println(max1);
System.out.println(max2);
}
}
线程池
核心原理:
创建线程池
创建一个没有上限的线程池:
public class MyRunnable implements Runnable{
@Override
public void run() {
for(int i=1;i<=100;i++){
System.out.println(Thread.currentThread().getName()+"--"+i);
}
}
}
public class test1 {
public static void main(String[] args) {
//创建线程池对象
ExecutorService poll= Executors.newCachedThreadPool();
//提交任务
poll.submit(new MyRunnable());
poll.submit(new MyRunnable());
poll.submit(new MyRunnable());
//线程池一般不会关闭
}
}
创建一个有上限的线程池:
public class test1 {
public static void main(String[] args) {
//创建线程池对象
ExecutorService poll= Executors.newFixedThreadPool(2);
//提交任务
poll.submit(new MyRunnable());
poll.submit(new MyRunnable());
poll.submit(new MyRunnable());
//线程池一般不会关闭
}
}
自定义线程池
系统先会创建服务于任务1、2、3的线程1、2、3;再将任务4、5、6拿去排队; 再调用临时线程服务于任务7、8、9;任务10会触发任务拒绝策略。
任务拒绝策略
创建自定义线程池:
public class test1 {
public static void main(String[] args) {
//自定义线程池
ThreadPoolExecutor pool=new ThreadPoolExecutor(
3,//核心线程的数量
6,//最大线程数
60,//空闲线程最大存活时间
TimeUnit.SECONDS,//时间单位
new ArrayBlockingQueue<>(3),//任务队列,相当于阻塞队列
Executors.defaultThreadFactory(),//创建线程工厂:线程池如何获取到一个线程
new ThreadPoolExecutor.AbortPolicy()//任务拒绝策略
);
}
}
线程池多大合适