线程定义:线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中实际运作单位。简单来说,应用软件中相互独立,可以同时运作的功能。
多线程作用:有了多线程,我们就可以让程序同时做多件事情。
应用场景:只要你想让多个事件同时运行就需要用到多线程。
并发:在同一时刻,有多个指令在单个CPU上交替执行。
并行:在同一时刻,有多个指令在多个CPU上同时执行。
多线程的实现方式
1.继承Thread类的方式进行执行
步骤:
自己定义一个类继承Thread
重写run方法
创建子类的对象,并启动线程。
public class MyThread extends Thread{@Overridepublic 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{@Overridepublic 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{//在此获取线程的名字@Overridepublic 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的和@Overridepublic 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{@Overridepublic 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);}@Overridepublic 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);}@Overridepublic void run() {for(int i=0;i<10;i++) {try {Thread.sleep(1000);//每个一秒打印一个数据} catch (InterruptedException e) {// TODO Auto-generated catch blocke.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{@Overridepublic 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{@Overridepublic void run() {for(int i=0;i<8;i++) {System.out.println(getName()+":"+i);}}
}
public class MyThread2 extends Thread{@Overridepublic 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{@Overridepublic 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{@Overridepublic 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();//锁对象一定要是唯一的@Overridepublic void run() {while(true) {synchronized(obj) {if(ticket<100) {try {Thread.sleep(100);//数据慢慢的输出} catch (InterruptedException e) {// TODO Auto-generated catch blocke.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;//使用第二种方法创建对象,不用设置这个为静态的@Overridepublic 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 blocke.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();//创建一个锁对象@Overridepublic 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{//消费者//循环//同步代码块(同步方法,锁方法均可)//判断共享数据是否到末尾(先写到了末尾,再写未到末尾)@Overridepublic 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{//生产者@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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;//抢到的金额的最小值@Overridepublic 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;}@Overridepublic 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<>();@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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{@Overridepublic 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()//任务拒绝策略);}}
线程池多大合适