齐诺网站建设东莞网站建设做网站河南最新消息
第八章:多线程
8-1 基本概念:程序、进程、线程 (了解即可)
程序(program):一段静态代码,静态对象。为了完成特定任务、用某种语言编写的一组指令的集合
进程(process):是程序的一次执行过程,或是在运行的一个程序
线程(thread):进程可以进一步细化为线程,是一个程序内部的一条执行路径。
线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc)
并行与并发:
并行:多个 CPU 同时执行多个任务
并发:一个 CPU 同时执行多个任务
并发:一个 CPU 同时执行多个任务 比如:秒杀、多个人做同一件事
8-2 线程的创建和使用
方式一、继承于 Thread 类
1、创建一个继承于 Thread 类的子类
2、重写 Thread 类的run()
3、创建 Thread 类的子类对象
4、通过此对象调用 start()
我们不能直接调用对象.run()来启动线程
不可以让已经 start 的线程再去执行一个新的线程;我们需要重新创建一个线程的对象
用代码来演示一下过程
/*** 多线程的创建,方式一:继承于 Thread 类* @author Jackson_kcw* @create 2025-02-22*/
public class ThreadTest {public static void main(String[] args) {//3、创建 Thread类的子类的对象MyThread myThread = new MyThread();//4、通过次对象调用 start() :启动当前线程、调用当前线程的 run()myThread.start();}}
//1、创建一个继承于 Thread 类的子类
class MyThread extends Thread{//2、重写 Thread 类的 run()@Overridepublic void run() {for (int i = 0; i <100 ; i++) {//输出 100以内的偶数if(i%2==0){System.out.println(i);}}}
}
Thread 其中的方法
yield():释放当前 CPU 的执行权
join():在线程 a 中调用线程 b 的 join 方法,此时线程 a 进入阻塞状态,直到线程 b 完全执行完之后,线程 a 才会结束阻塞状态
stop(): 强制结束当前线程
sleep(long millitime):让当前线程“睡眠”指定的 millitime 毫秒,在指定的时间内,当前线程为阻塞状态
线程的优先级:
1、优先级分类
MAX_PRIORITY: 10
MIN_PRIORITY:1
NORM_PRIORITY:5
2、
getPriority():获取线程的优先级
setPriority(int p):设置线程优先级
注:高优先级只是从概率上来说更可能会先执行,但不意味着绝对
方式二、创建多线程的方式二:实现 Runnable 接口
1、创建一个实现了 Runnable 接口的类
2、实现类去实现 Runnable 的抽象方法:run()
3、创建实现类的对象
4、将此对象作为参数传递到 Thread 类的构造器中,创建 Thread 类的对象
5、通过 Thread 类的对象调用 start()
代码例子:
package com.kcw.java1;/**创建多线程的方式二:实现 Runnable 接口* @author Jackson_kcw* @create 2025-02-23*/
public class ThreadTest1 {public static void main(String[] args) {//3、创建实现类的对象MThread thread = new MThread();//4、将此对象作为参数传递到 Thread 类的构造器中,创建 Thread 类的对象Thread t1=new Thread(thread);//5、通过 Thread 类的对象调用 start():启动线程;调用当前线程的 run()t1.start();}
}
//1、创建一个类实现了 Runnable 接口的方法
class MThread implements Runnable{//2、实现类去实现 Runnable 中的抽象方法:run()@Overridepublic void run() {for (int i = 0; i <100 ; i++) {if(i%2==0){System.out.println(i);}}}
}
创建线程的两种方式的比较
开发中:优先选择:实现 Runnable 接口的方式
原因:1、实现的方式没有类的单继承性的局限性
2、实现的方式更适合来处理多个线程共有数据的情况
联系:Thread 也是实现 Runnable
相同点:两者都需要重写 run 方法,将线程要执行的逻辑声明在 run()中
8-3 线程的生命周期
完整生命周期的几种状态
线程生命周期
8-4 线程的同步
线程安全问题:当某个线程在运行过程中,另一个线程进入运行状态,会影响到共享数据;如卖票 会出现负数情况
如何解决线程安全问题:当一个线程 a操作共享数据数据的时候,其他的线程不能参与进来,直到线程 a操作完共享数据,其他线程才可以开始操作共享数据,即使线程 a 出现阻塞,也不能改变
在 Java 中,我们通过同步机制,解决线程的安全问题
方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:1、操作共享数据的代码即为 需要被同步的代码 ---->不能包含代码多了,也不能包少了
2、共享数据:多个线程共同操作的变量。比如卖票问题里面的 ticket
3、同步监视器:俗称锁。任何一个类的对象都可以充当锁
要求:多个线程必须要共用一把锁
补充:在实现 Runnable接口创建多线程的方式中,我们可以考虑使用 this 充当同步监视器
在继承 Thread 类创建多线程的方式中,慎用使用 this 充当同步监视器,可以考虑使用当前类充当同步监视器
方法二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步
1、同步方法仍然涉及到同步监视器,只是不需要我们显式声明
2、非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类本身
同步方法的格式如下代码:show()方法的格式
package java1;/*** @author Jackson_kcw* @Time 2025-02-23*/
public class WindowTest4 {public static void main(String[] args) {Window4 w = new Window4();Thread t1 = new Thread(w);Thread t2 = new Thread(w);Thread t3 = new Thread(w);t1.start();t2.start();t3.start();}
}
class Window4 implements Runnable{private static int ticket=100;@Overridepublic void run() {while(true){show();}}public synchronized void show(){//同步方法 如果这个是继承 Thread 会创建几个对象,需要加 static 将此方法转为静态if(ticket>0){System.out.println(Thread.currentThread().getName()+"卖票:"+ticket);ticket--;}}
}
同步的方式,解决了线程的安全;但是在操作同步代码时,只能一个线程参与,其他线程等待,效率会低一点
补充:之前的单例模式中的懒汉解决线程安全问题
mac 电脑中 ide 包裹代码块的快捷键为:option+command+T
/**使用同步机制将单例模式里面的懒汉式改写成线程安全的* @author Jackson_kcw* @Time 2025-02-23*/
public class BankTest {}
class Bank{private Bank(){}private static Bank instance=null;public synchronized Bank getInstance(){//方式一:效率不高
// synchronized (Bank.class) {
// if(instance==null){
// instance=new Bank();
// return instance;
// }
// else return instance;
// }//方式二:效率更高if(instance==null){synchronized (Bank.class) {if(instance==null){instance=new Bank();}}}return instance;}}
死锁问题
我们使用同步时,要避免死锁
解决线程安全问题的方式三:Lock 锁-------JDK5 新增的
例如:
package Lock;import java.util.concurrent.locks.ReentrantLock;/*** @author Jackson_kcw* @Time 2025-02-23 PM4:52*/
public class LockTest {public static void main(String[] args) {Window window=new Window();Thread thread1=new Thread(window);Thread thread2=new Thread(window);Thread thread3=new Thread(window);thread1.start();thread2.start();thread3.start();}
}
class Window implements Runnable{private int ticket=100;//1、实例化 ReentrantLockprivate ReentrantLock lock=new ReentrantLock();@Overridepublic void run() {while(true){//2、调用锁定方法 locklock.lock();try {if(ticket>0){System.out.println(Thread.currentThread().getName()+"卖票:"+ticket);ticket--;}else break;} finally {//3、调用解锁方法 unlock()lock.unlock();}}}
}
面试题:synchronized 于 Lock 的异同
相同:两者都是解决线程安全的方法
不同:synchronized 机制在执行完相应的同步代码以后,自动的释放同步监察器
Lock 需要手动的启动同步(lock()),同时结束同步也需要手动的实现解锁( unlock() )
优先使用的顺序
Lock–>同步代码块(已经进入了方法体,分配了相应资源)–>同步方法(在方法体之外)
如何解决线程安全问题?有哪几种方式
答:1、使用 synchronized 关键字,用于代码块或方法(即同步代码块、同步方法)
2、使用 ReentrantLock(可重入锁),lock()与 unlock()手动设置锁和解除锁
8-5 线程的通信
线程通信涉及到三个方法:
wait():使线程进入阻塞状态,并释放同步监视器
notify():唤醒被 wait 的一个线程,如果有多个线程被 wait,就唤醒优先级高的
notifyAll():唤醒所有 wait 的线程
以下见代码例子:
package Java2Communication;/**线程通信的例子:使用两个线程打印 1-100.线程一、线程二交替打印* @author Jackson_kcw* @Time 2025-02-24 PM2:04*/
public class Communication {public static void main(String[] args) {Number n1=new Number();Thread t1=new Thread(n1);Thread t2=new Thread(n1);t1.setName("t1");t2.setName("t2");t1.start();t2.start();}
}
class Number implements Runnable{private int number=1;@Overridepublic void run() {while (true) {synchronized (this) {//唤醒 阻塞的线程notify();if(number<=100){System.out.println(Thread.currentThread().getName()+":"+number);number++;try {//使得调用如下wait()方法的线程进入阻塞状态wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}else break;}}}
}
注意点:
1、wait()、notify()、notifyAll()三个方法必须使用在同步代码块或同步方法中
2、这三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则会出现 IllegalMonitorStateException异常
3、这三个方法都定义在 Java.lang.Object 类中
面试题
sleep()和 wait()的异同:
同:一旦执行方法,都可以使当前的线程进入阻塞状态
异:1、两个方法声明的位置不同:Thread 类中声明 sleep(),Object 类中声明 wait()
2、调用的范围要求不同:sleep()可以在任何需要的场景下调用,wait()必须使用在同步代码块
3、关于是否释放同步监视器的问题:如果两个方法都用在同步代码块或同步方法中,sleep 不会释放 锁(同步监视器),但是 wait()会释放锁
8-6 JDK5.0 新增线程创建方式
新增方式一:实现 Callable 接口
创建步骤:
1、创建一个实现 Callable 的实现类
2、实现 call 方法,将此线程需要执行的操作声明在 call()中
3、创建 Callable 接口实现类的对象
4、将此 Callable 接口实现类的对象作为参数传递到 FutureTask 构造器中,创建 FutureTask 的对象
5、将 FutureTask 的对象作为参数传递到 Thread类的构造器中,创建 Thread 对象,并调用 start();
6、获取 Callable 中的 call 方法的返回值
在代码中如下:
package Java3;import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;/**创建线程的实现方式三:实现 Callable接口* @author Jackson_kcw* @Time 2025-02-24 PM2:40*/
public class ThreadNew {public static void main(String[] args) {//3、创建 Callable 接口实现类的对象NumberThread numberThread = new NumberThread();//4、将此 Callable 接口实现类的对象作为参数传递到 FutureTask 构造器中,创建 FutureTask 的对象FutureTask futureTask= new FutureTask(numberThread);//5、将 FutureTask 的对象作为参数传递到 Thread类的构造器中,创建 Thread 对象,并调用 start();new Thread(futureTask).start();try {//6、获取 Callable 中的 call 方法的返回值//get()返回值即为 FutureTask 构造器参数 Callable 实现类重写的 call()的返回值Object sum=futureTask.get();System.out.println("总和为:"+sum);} catch (InterruptedException e) {throw new RuntimeException(e);} catch (ExecutionException e) {throw new RuntimeException(e);}}
}
//1、创建一个实现 Callable 的实现类
class NumberThread implements Callable {//2、实现 call 方法,将此线程需要执行的操作声明在 call()中@Overridepublic Object call() throws Exception {int sum=0;for (int i = 1; i <=100 ; i++) {if(i%2==0){System.out.println(i);sum+=i;}}return sum;}
}
二、如何理解实现 Callable 接口的方式创建多线程比实现Runnable接口要强大
1、call()方法可以有返回值
2、call()方法可以抛出异常,被外面的操作捕获,获取异常信息
3、Callable 是支持泛型的
新增方式二:使用线程池
利用线程池方式创建线程的步骤:
1、提供指定线程数量的线程池
2、执行指定的线程的操作。需要提供实现 Runnable 或 Callable 接口的实现类
3、关闭连接池子
具体的代码例子
package Java3;import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;/**创建线程的方式四:使用线程池的方式* @author Jackson_kcw* @Time 2025-02-24 PM3:31*/
public class ThreadPool {public static void main(String[] args) {//1、提供指定线程数量的线程池ExecutorService executorService = Executors.newFixedThreadPool(10);//2、执行指定的线程的操作。需要提供实现 Runnable 或 Callable 接口的实现类executorService.execute(new NumberThread2());//适合使用于 RunnableexecutorService.submit(new FutureTask(new NumberThread1())); //适合适用于 CallableexecutorService.shutdown();//3、关闭连接池子}}
class NumberThread1 implements Callable {@Overridepublic Object call() throws Exception {for (int i = 0; i < 100; i++) {if (i % 2 != 0) {System.out.println(Thread.currentThread().getName()+" "+i);}}return null;}}
class NumberThread2 implements Runnable {@Overridepublic void run() {for (int i = 0; i <= 100; i++) {if (i % 2 == 0) {System.out.println(Thread.currentThread().getName() + " " + i);}}}
}
面试题:创建多线程的方式?四种
方式一、继承于 Thread 类
方式二、实现 Runnable 接口
方式三:实现 Callable 接口
方式四:使用线程池(实际开发中常用)