1.1 线程概述
进程:每个进程都有独立的代码和数据空间,进程是资源分配的最小单位,每个独立的程序占有一个进程
线程:表示程序的执行流程,是CPU调度执行的基本单位,同一类线程共享代码和数据空间,进程是线程的容器,即一个进程包含1+n个线程
多线程:每个线程完成一个功能,并与其他线程在同一个进程中并发执行,这种机制被称为多线程
多线程的优点:让程序更好的利用系统资源,不但能更好的利用系统的空闲时间,还能快速对用户的请求作出响应,使程序的运行效率大大提高,也增加了程序的灵活程度;最重要的是,可通过多线程技术解决不同任务之间的协调操作与运行、数据交互、资源分配等难题;在某些情况下使程序设计更简单。
1.2 线程的创建
实现多线程编程的方式主要有两种:
一种是继承Thread类
另一种是实现Runnable接口
继承Thread类:
package com.se.Thread; // 子线程,继承Thread类,重写run()方法
//创建线程方式一:继承Thread类,重写run方法,调用start开启线程
//总结:注意,线程开启不一定立即执行,有CPU调用
public class MyThread extends Thread{ @Override public void run() { // 这个线程要干的事! for (int i = 0; i < 100; i++) { System.out.println(this.getName()+"-->MyThread run:"+i); } } } // 测试类 class Test02{ public static void main(String[] args) { // 创建Thread子类对象 MyThread myThread = new MyThread(); // 给线程设置名字 myThread.setName("我们的第一个子线程"); // 开启线程(子线程) myThread.start(); // 开启主线程 for (int i = 0; i < 100; i++) { System.out.println("mian run:"+i); } } }
实现Runnable接口:
package com.se.Thread; // 子线程,继承Runnable接口,重写run()方法 public class MyRunnable implements Runnable { @Override public void run() { // 子线程要执行的任务 for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + "-->MyRunnable run:" + i); } } } // 测试类 class Test03 { public static void main(String[] args) { // 创建Runnable实现类的对象 MyRunnable runnable = new MyRunnable(); // 以此对象作为参数构造Thread类 Thread thread = new Thread(runnable); // 给线程设置名字 thread.setName("我们的第二个子线程"); // 开启线程(子线程) thread.start(); // 开启主线程 for (int i = 0; i < 100; i++) { System.out.println("mian run:" + i); } } }
1.3 两种创建线程方式的对比
使用继承自Thread类的方式创建多线程:
优点:编写简单,如果需要访问当前线程,直接使用 this 即可
缺点:继承自Thread类了,不能再继承其他类
使用实现Runnable接口的方式创建多线程:
优点:还可以继承自其他类
缺点:编写稍微复杂,访问当前线程,需使用 Thread.currentThread() 方法
package com.kuangshen.demo02; import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.concurrent.*; //线程创建方式三:实现callable接口 public class TestCallable implements Callable<Boolean> { private String url; private String name; public TestCallable(String url, String name) { this.url = url; this.name = name; } @Override public Boolean call() throws Exception { WebDownloader webDownloader = new WebDownloader(); webDownloader.downloader(url,name); System.out.println("下载的文件名为:"+name); return true; } public static void main(String[] args) throws ExecutionException, InterruptedException { TestCallable testCallable1 = new TestCallable("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fup.enterdesk.com%2Fedpic_360_360%2Fbb%2F37%2Ff5%2Fbb37f583e8da88aed385306a07361c2a.jpg&refer=http%3A%2F%2Fup.enterdesk.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1629440173&t=03523962fee34ed4d7f9d7b0e1de3f2f", "1.jpg"); TestCallable testCallable2 = new TestCallable("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fup.enterdesk.com%2Fedpic_360_360%2Fbb%2F37%2Ff5%2Fbb37f583e8da88aed385306a07361c2a.jpg&refer=http%3A%2F%2Fup.enterdesk.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1629440173&t=03523962fee34ed4d7f9d7b0e1de3f2f", "2.jpg"); TestCallable testCallable3 = new TestCallable("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fup.enterdesk.com%2Fedpic_360_360%2Fbb%2F37%2Ff5%2Fbb37f583e8da88aed385306a07361c2a.jpg&refer=http%3A%2F%2Fup.enterdesk.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1629440173&t=03523962fee34ed4d7f9d7b0e1de3f2f", "3.jpg"); //创建执行服务: ExecutorService ser = Executors.newFixedThreadPool(3); //提交执行 Future<Boolean> r1 = ser.submit(testCallable1); Future<Boolean> r2 = ser.submit(testCallable2); Future<Boolean> r3 = ser.submit(testCallable3); //获取结果 Boolean rs1 = r1.get(); Boolean rs2 = r2.get(); Boolean rs3 = r3.get(); System.out.println(rs1); System.out.println(rs2); System.out.println(rs3); //关闭服务 ser.shutdownNow(); } } class WebDownloader{ //下载方法 public void downloader(String url,String name){ try { FileUtils.copyURLToFile(new URL(url),new File(name)); } catch (IOException e) { e.printStackTrace(); } } }
1.4 线程的静态代理模式
静态代理模式总结:
真实对象和代理对象都要实现同一个接口
代理对象要代理真实角色
好处:
代理对象可以做很多真实对象做不了的事情
真实对象专注做自己的事情
package com.kuangshen.demo02; //静态代理模式总结; //真实对象和代理对象都要实现同一个接口 //代理对象要代理真实角色 //好处: //代理对象可以做很多真实对象做不了的事情 //真实对象专注做自己的事情 public class StaticProxy { public static void main(String[] args) { You you = new You(); //你要结婚 new Thread(() -> System.out.println("我爱你")).start(); new WeddingCompany(new You()).happyMarry(); // WeddingCompany weddingCompany = new WeddingCompany(you); // weddingCompany.happyMarry(); } } interface Marry { //人间四大喜事 //久旱逢甘露 //他乡遇故知 //洞房花烛夜 //金榜提名时 void happyMarry(); } //真实角色,你去结婚 class You implements Marry { @Override public void happyMarry() { System.out.println("秦老师要结婚,超开心"); } } //代理角色,帮助你结婚 class WeddingCompany implements Marry { //代理谁 --》 真实目标角色 private Marry target; public WeddingCompany(Marry target) { this.target = target; } @Override public void happyMarry() { before(); this.target.happyMarry(); after(); } public void before() { System.out.println("结婚之前,布置现场"); } public void after() { System.out.println("结婚之后,收尾款"); } }
1.5 Lamda表达式
总结:
lambda表达式只能有一行代码的情况下才能简化成为一行,如果有多行,那么就用代码块包裹。
前提是接口为函数式接口
多个参数也可以去掉参数类型,要去掉都去掉,必须加括号
package com.kuangshen.demo02; /** * 推到lambda表达式 */ public class TestLambdal { //3.静态内部类 static class Like2 implements ILike { @Override public void lambda() { System.out.println("i like lambda2"); } } public static void main(String[] args) { ILike like = new Like(); like.lambda(); ILike like2 = new Like2(); like2.lambda(); //4.成员内部类 class Like3 implements ILike { @Override public void lambda() { System.out.println("i like lambda4"); } } ILike like3 = new Like3(); like3.lambda(); //5.匿名内部类,没有类的名称,必须借助接口或者父类 like = new ILike() { @Override public void lambda() { System.out.println("I like lambda5"); } }; like.lambda(); //6.用lambda简化 like = ()->{ System.out.println("I like lambda6"); }; like.lambda(); } } //1.定义一个函数式接口 interface ILike { void lambda(); } //2.实现类 class Like implements ILike { @Override public void lambda() { System.out.println("i like lambda"); } }
1.6 线程的生命周期
新建状态:当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时仅由JVM为其分配内存,并初始化其成员变量的值
就绪状态:当线程对象调用了start()方法之后,该线程处于就绪状态。Java虚拟机会为其创建方法调用栈和程序计数器,等待调度运行
运行状态:如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态
阻塞状态:当处于运行状态的线程失去所占用资源之后,便进入阻塞状态
死亡状态:线程在run()方法执行结束后进入死亡状态。此外,如果线程执行了interrupt()或stop()方法,那么它也会以异常退出的方式进入死亡状态。
1.4 线程的优先级
进程中至少有一个线程,在众多线程中,有时需要优先执行一个线程,这个线程的优先级越高,得到CPU执行的机会就越大,反之就越小
线程的优先级使用1-10之间的整数来表示,数字越大,优先级越高;除了通过整数表示优先级,还可以通过Thread类的3个静态常量表示:MAX_PRIORITY、NORM_PRIORITY、MIN_PRIORITY
package com.se.Thread; public class MyThread2 extends Thread{ public MyThread2(String name){ super(name); } @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println("当前线程名字:"+this.getName()+"/优先级:"+this.getPriority()+"/循环到第几次:"+i); } } }
package com.se.Thread; public class Test05 { public static void main(String[] args) { System.out.println("当前线程名字:"+Thread.currentThread().getName()+"/优先级:"+Thread.currentThread().getPriority()+"/主线程:"); //创建子线程 MyThread2 myThread1 = new MyThread2("第一个子线程"); MyThread2 myThread2 = new MyThread2("第二个子线程"); MyThread2 myThread3 = new MyThread2("第三个子线程"); MyThread2 myThread4 = new MyThread2("第四个子线程"); //设置优先级 myThread1.setPriority(Thread.MIN_PRIORITY); // 1 myThread2.setPriority(Thread.NORM_PRIORITY);// 5 myThread3.setPriority(6); myThread4.setPriority(Thread.MAX_PRIORITY); // 10 myThread1.start(); myThread2.start(); myThread3.start(); myThread4.start(); } }
1.5 线程的控制
sleep()方法,来使正在执行的线程以指定的毫秒数暂停,进入睡眠状态,在该线程休眠时间内,CPU会调度其他线程
package com.se.Thread; public class MySleepThread extends Thread { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(this.getName()+"输出数字:"+i); if (i % 2 == 0) { try { Thread.sleep(500); //当前线程休眠500毫秒 } catch (InterruptedException e) { e.printStackTrace(); } } } } }
package com.se.Thread; public class Test06 { public static void main(String[] args) { //创建一个子线程 MySleepThread mySleepThread = new MySleepThread(); //启动子线程 mySleepThread.start(); //主线程的代码 for (int i = 0; i < 50; i++) { System.out.println(Thread.currentThread().getName() + "输出数字:" + i); if (i % 3 == 0) { try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
yield()方法,来暂停当前正在执行的线程对象,把机会让给相同或优先级更高的线程,因此, yield()方法也称为线程让步
package com.se.Thread; public class MyYeildThread extends Thread { @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println(this.getName() + "循环次数:" + i); if (i == 10) { System.out.println("当前线程执行了10次," + this.getName() + "我先让步"); Thread.yield(); //让步 } } } }
package com.se.Thread; public class Test07 { public static void main(String[] args) { MyYeildThread thread1 = new MyYeildThread(); MyYeildThread thread2 = new MyYeildThread(); thread1.start(); thread2.start(); } }
join()方法,来实现让某个线程插队这一功能。当线程调用join()方法时,线程会进入阻塞状态,直到join方法加入的线程执行完毕,该线程才会继续执行
package com.se.Thread; public class MyJoinThread extends Thread { @Override public void run() { System.out.println(this.getName() + "线程开启..."); for (int i = 0; i < 20; i++) { System.out.println(this.getName() + "--->" + i); } System.out.println(this.getName() + "线程关闭..."); } }
package com.se.Thread; public class Test08 { public static void main(String[] args) { MyJoinThread thread1 = new MyJoinThread(); MyJoinThread thread2 = new MyJoinThread(); thread1.start(); thread2.start(); try { thread1.join(); } catch (InterruptedException e) { e.printStackTrace(); } try { thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("主线程执行完毕!"); } }
1.6 线程的同步
在多线程中,为了处理共享资源竞争,Java可以使用同步机制;
所谓同步机制,指的是两个线程同时作用在一个对象上,应该保持对象数据的统一性和整体性。
Java中提供了关键字synchronized,可以处理多线程同步竞争资源的问题
案例演示:
有1500张从北京西到郑州的z63车次的车票,售票1000张,退票500张,利用多线程中线程同步
package com.se.syncPackage; public class Tickets { public static final Object lock = new Object(); // 锁 public static int bjz2zz = 1500; // 有1500张从北京西到郑州的z63车次的车票 /** * 售票 */ public synchronized void sale(){ bjz2zz--; } /** * 退票 */ public synchronized void refound(){ bjz2zz++; } }
package com.se.syncPackage; public class SaleTickets extends Thread { private Tickets tickets; public SaleTickets(Tickets tickets) { this.tickets = tickets; } @Override public synchronized void run() { for (int i = 0; i < 1000; i++) { //售票 tickets.sale(); } } }
package com.se.syncPackage; public class RefoundTickets extends Thread { private Tickets tickets; public RefoundTickets(Tickets tickets) { this.tickets = tickets; } @Override public synchronized void run() { for (int i = 0; i < 500; i++) { // 退票 tickets.refound(); } } }
package com.se.syncPackage; public class Test01 { public static void main(String[] args) { Tickets tickets = new Tickets(); //售票的子线程 SaleTickets saleTickets = new SaleTickets(tickets); //退票的子线程 RefoundTickets refoundTickets = new RefoundTickets(tickets); saleTickets.start(); refoundTickets.start(); try { saleTickets.join(); } catch (InterruptedException e) { e.printStackTrace(); } try { refoundTickets.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Tickets.bjz2zz); } }
1.7 线程的停止
总结:
1.建议线程正常停止 --》 利用次数,不建议死循环
2.建议使用标志位 --》 设置一个标志位
3.不要使用stop或者destory等果实或者jdk不建议使用的方法
package com.kuangshen.state; //测试stop //1.建议线程正常停止 --》 利用次数,不建议死循环 //2.建议使用标志位 --》 设置一个标志位 //3.不要使用stop或者destory等果实或者jdk不建议使用的方法 public class TestStop implements Runnable{ //设置一个标志位 private boolean flag = true; @Override public void run() { int i = 0; while (flag){ System.out.println("run...Thread"+i++); } } //2.设置一个公开的方法停止线程 public void stop(){ this.flag = false; } public static void main(String[] args) { TestStop testStop = new TestStop(); new Thread(testStop).start(); for (int i = 0; i < 1000; i++) { System.out.println("main:"+i); if (i==900){ //调用stop方法切换标志位,让线程停止 testStop.stop(); System.out.println("线程停止"); } } } }
1.8 线程的状态
Thread thread = new Thread();
thread.getState();//获取线程当前的状态
Thread.State.NEW //新生状态
Thread.State.RUNNABLE // 运行状态
Thread.State.TERMINATED //终止状态
package com.kuangshen.state; //观察测试线程的状态 public class TestState { public static void main(String[] args) { Thread thread = new Thread(() -> { for (int i = 0; i < 5; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("==========="); }); //观察状态 Thread.State state = thread.getState(); System.out.println(state); //观察启动后 thread.start();//启动线程 state = thread.getState(); System.out.println(state); while (state != Thread.State.TERMINATED) { //只要线程不终止,就一直输出状态 try { Thread.sleep(100); state = thread.getState(); // 更新线程状态 System.out.println(state);// 输出状态 } catch (InterruptedException e) { e.printStackTrace(); } } } }
1.9 线程的死锁
所谓死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。
在多线程中,形成死锁的4个必要条件:
1.互斥使用
2.不可抢占
3.请求和等待
4.循环等待
Java中的死锁是应该尽量避免的,避免死锁,只需要避免4个条件中的任意一个即可;
常用的方法:
1.避免嵌套锁
2.设置锁定的顺序
3.设置锁定等待的时间
package com.se.deadLockPackage; public class Tickets { public static final Object lock = new Object(); // 锁 public static int bjz2zz = 1500; // 有1500张从北京西到郑州的z63车次的车票 public static final Object lock2 = new Object(); // 锁 public static int bjz2gz = 1500; // 有1500张从北京西到广州的z63车次的车票 /** * 售票 */ public void sale() { synchronized (lock) { bjz2zz--; // bjx2gz也减少了一张? synchronized (lock2) { bjz2gz--; } } } /** * 退票 */ public void refound() { // 锁的先后顺序要一致,否则会出现线程的死锁 synchronized (lock) { bjz2zz++; synchronized (lock2) { bjz2gz++; } } } }
package com.se.deadLockPackage; public class SaleTickets extends Thread { private Tickets tickets; public SaleTickets(Tickets tickets) { this.tickets = tickets; } @Override public synchronized void run() { for (int i = 0; i < 1000; i++) { // 售票 tickets.sale(); } } }
package com.se.deadLockPackage; public class RefoundTickets extends Thread { private Tickets tickets; public RefoundTickets(Tickets tickets) { this.tickets = tickets; } @Override public synchronized void run() { for (int i = 0; i < 500; i++) { tickets.refound(); } } }
package com.se.deadLockPackage; public class Test01 { public static void main(String[] args) { Tickets tickets = new Tickets(); SaleTickets saleTickets = new SaleTickets(tickets); RefoundTickets refoundTickets = new RefoundTickets(tickets); saleTickets.start(); refoundTickets.start(); try { saleTickets.join(); } catch (InterruptedException e) { e.printStackTrace(); } try { refoundTickets.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Tickets.bjz2zz); System.out.println(Tickets.bjz2gz); } }