目录
八:多线程
8.1:基本概念
8.2:线程的创建与使用
8.2.1:Thread类的有关方法
8.2.2:线程的调度
8.2.3:两种创建线程方式的比较
8.3:线程的生命周期
8.4:线程的同步
8.4.1:同步代码块&&同步方法
8.4.2:单例模式的懒汉式修改为线程安全的
8.4.3:线程的死锁问题
8.4.4:锁(Lock)
8.5:线程的通信
8.6:JDK5.0新增线程创建方式
八:多线程
8.1:基本概念
①程序(Program):是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象
②进程(Process):是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在、消亡的过程 -- 生命周期
-程序是静态的,进程是动态的
-进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
③线程(Thread):进程可进一步细化为线程,是一个程序内部的一条执行路径。
-若一个进程同一时间并行执行多个线程,就是支持多线程的
-线程作为调度和执行的单位,每一个线程拥有独立的运行栈和程序计数器,线程切换的开销小
-一个进程中的多个线程共享相同的内存单元/内存地址空间 -> 它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患
8.2:线程的创建与使用
8.2.1:Thread类的有关方法
1.void start():启动线程,并执行对象的run()方法
2.run():线程在被调度时执行的操作
3.String getName():获取当前线程的名称
4.void setName(String name):设置该线程的名称
5.static Thread currentThread():返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类
6.static void yield():线程让步
>暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
>若队列中没有同优先级的线程,忽略此方法
7.join():当某个程序执行流中调用其它线程的join()方法时,当前线程将被阻塞,直到join()方法加入的其它线程执行完为止
>低优先级的线程也可以获得执行
8.static void sleep(long millis):(指定时间:毫秒)
>令当前活动线程在指定时间段内放弃对CPU控制,使其它线程有机会被执行,时间到后重新排队
>抛出InterruptedException异常
9.stop():强制线程生命周期结束,不推荐使用
10.boolean is Alive():返回boolean,判断线程是否存活
package com.jiayifeng.java;
/**
* author xiaojia
* create 2023-09-08 16:09
*
* 测试Thread中的常用方法
* 测试Thread中的优先级
*/
class HelloThread extends Thread{
@Override
public void run() {
for(int i = 0;i < 100;i++){
if(i % 2 == 0){
try {
sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + ":" +
Thread.currentThread().getPriority() + i);
}
if(i % 20 == 0){
this.yield();
}
}
}
public HelloThread(String name){
super(name);
}
}
public class ThreadMethodTest {
public static void main(String[] args) {
HelloThread t1 = new HelloThread("Thread1");
// t1.setName("线程一");
// 设置分线程的优先级
t1.setPriority(Thread.MAX_PRIORITY);
t1.start();
// 给主线程命名
Thread.currentThread().setName("主线程");
// 设置主线程的优先级
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
for(int i = 0;i < 100;i++){
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ":" +
Thread.currentThread().getPriority() + i);
}
if(i == 20){
try {
t1.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
System.out.println(t1.isAlive());
}
}
package com.jiayifeng.java;
/**
* author xiaojia
* create 2023-09-07 19:59
*
* translate:
* eclipse 中 workspace 相当于 IDEA 中的 project
* eclipse 中 project 相当于 IDEA 中的 module
*
* 一:多线程的创建,方式一:继承于Thread类
* 1.创建一个继承于Thread类的子类
* 2.重写Thread类的run() -> 将此线程执行的操作声明在run()中
* 3.创建Thread类的子类的对象
* 4.通过此对象调用start()
*
* 例子:遍历100以内的所有偶数
*/
public class ThreadTest {
public static void main(String[] args) {
// 3.创建Thread类的子类的对象
MyThread t1 = new MyThread();
// 4.通过此对象调用start()
t1.start(); //start()作用:①使当前的线程开始执行 ②Java虚拟机去调用当前的run()方法
System.out.println("hello");
// 问题一:我们不能通过直接调用run()的方式启动线程
// t1.run();
// 问题二:再启动一个线程,遍历100以内的偶数。不可以还让已经start()的线程
// 去执行,会报异常IllegalThreadStateException
// 我们需要重新创建一个线程的对象
// t1.start();
MyThread t2 = new MyThread();
t2.start();
}
}
//1.创建一个继承于Thread类的子类
class MyThread extends Thread{
// 2.重写Thread类的run()
@Override
public void run() {
for(int i = 0;i < 100;i++){
if(i % 2 == 0){
System.out.println(i);
}
}
}
}
package com.jiayifeng.prac;
/**
* @author xiaojia
* create 2023-09-07 21:20
* title:创建两个分线程,其中一个线程遍历100以内的偶数,另一个线程遍历100以内的奇数
*/
public class ThreadDemo {
public static void main(String[] args) {
// MyThread1 myThread1 = new MyThread1();
// myThread1.start();
// MyThread2 myThread2 = new MyThread2();
// myThread2.start();
// 创建Thread类的匿名子类的方式
// 100以内的偶数
new Thread(){
@Override
public void run() {
for(int i = 0;i < 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}.start();
// 100以内的奇数
new Thread(){
@Override
public void run() {
for(int i = 0;i < 100;i++){
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}.start();
}
}
class MyThread1 extends Thread{
@Override
public void run() {
for(int i = 0;i < 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
class MyThread2 extends Thread{
@Override
public void run() {
for(int i = 0;i < 100;i++){
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
8.2.2:线程的调度
调用策略:
①时间片
②抢占式:高优先级的线程抢占CPU
Java的调度方法:
①同优先级线程组成先进先出队列,使用时间片策略
②对高优先级,使用优先调度的抢占式策略
线程的优先级等级:
MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5
设计的方法:
getPriority():返回线程优先值
setPriority(int newPriority):改变线程的优先级
说明:
线程创建时继承父线程的优先级
低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用
package com.jiayifeng.java;
/**
* author xiaojia
* create 2023-09-09 14:53
*
* 创建多线程的方式二:实现Runnable接口
* 1.创建一个实现了Runnable接口的类
* 2.实现类去实现Runnable中的抽象方法:run()
* 3.创建实现类的对象
* 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
* 5.通过Thread类的对象调用start()
*/
//1.创建一个实现了Runnable接口的类
class MThread implements Runnable{
// 2.实现类去实现Runnable中的抽象方法:run()
@Override
public void run() {
for(int i = 0;i < 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadTest1 {
public static void main(String[] args) {
// 3.创建实现类的对象
MThread m1 = new MThread();
// 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread t1 = new Thread(m1);
t1.setName("线程一");
// 5.通过Thread类的对象调用start()
t1.start();
// 再启动一个线程,遍历100以内的偶数
Thread t2 = new Thread(m1);
t2.setName("线程二");
t2.start();
}
}
8.2.3:两种创建线程方式的比较
开发中,优先选择实现Runnable接口的方式
原因:
1.实现的方式没有类的单继承性的局限性
2.实现的方式更适合来处理多个线程有共享数据的情况
联系:public class Thread implements Runnable
相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中
8.3:线程的生命周期
JDK中用Thread.State类定义了线程的几种状态,在线程的一个完整周期中通常用经历如下的五个阶段:
①新建:当一个Thread类或其它子类的对象被声明并创建时,新生的线程对象处于新建状态
②就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没有分配到CPU资源
③运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
④阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时终止自己的执行,进入阻塞状态
⑤死亡:线程完成了它的全部工作或线程被提前强制性终止或出现异常导致结束
8.4:线程的同步
8.4.1:同步代码块&&同步方法
package com.jiayifeng.prac;
/**
* author xiaojia
* create 2023-09-09 18:15
*
* 例子:创建三个窗口卖票,总票数为100张,使用实现Runnable接口的方式
* 1.在Java中,我们通过同步机制来解决线程的安全问题
* 方式一:同步代码块
* synchronize(同步监视器){
* //需要被同步的代码
* }
* 说明:
* ①操作共享数据的代码,即为需要被同步的代码
* ②共享数据:多个线程共同操作的变量。比如:ticket就是共享数据
* ③同步监视器,俗称:锁。任何一个类的对象,都可以充当锁
* 要求:多个线程必须要共用一把锁
*
* 方式二:同步方法
* 如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明为同步的
*
*
* 2.同步的方式,解决了线程的安全问题 --好处
* 操作同步代码时,只能有一个线程参与,其它线程等待。相当于是一个单线程的过程,效率低
*
*/
class Window implements Runnable{
private int ticket = 100;
// Object obj = new Object();
@Override
public void run() {
while (true) {
// 方式一:
synchronized (this){
// 此时的this:唯一的Window的对象 方式二:synchronized(obj) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window w1 = new Window();
Thread t1 = new Thread(w1);
Thread t2 = new Thread(w1);
Thread t3 = new Thread(w1);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
package com.jiayifeng.prac;
import javax.jws.Oneway;
/**
* author xiaojia
* create 2023-09-09 18:49
*
* 使用同步代码块解决继承Thread类的方式的线程安全问题
*
* 说明:在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同
* 步监视器
*/
class Window1 extends Thread {
private static int ticket = 100;
private static Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
Window1 t1 = new Window1();
Window1 t2 = new Window1();
Window1 t3 = new Window1();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
package com.jiayifeng.prac;
/**
* author xiaojia
* create 2023-09-10 8:21
*
* 使用同步方法解决实现Runnable接口的线程安全问题
*
* 关于同步方法的总结:
* 1.同步方法仍然涉及到同步监视器,只是不需要我们显式的声明
* 2.非静态的同步方法,同步监视器是:this
* 3.静态的同步方法,同步监视器是:当前类本身
*/
class Window2 implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
private synchronized void show() { //同步监视器:this
// synchronized (this) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
}
// }
}
}
public class WindowTest2 {
public static void main(String[] args) {
Window2 w2 = new Window2();
Thread t1 = new Thread(w2);
Thread t2 = new Thread(w2);
Thread t3 = new Thread(w2);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
package com.jiayifeng.prac;
/**
* author xiaojia
* create 2023-09-10 8:37
*
* 使用同步方法处理继承Thread类的方式中的线程安全问题
*/
class Window3 extends Thread {
private static int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
private static synchronized void show(){ //同步监视器:Window3.class
// private synchronized void show() { //同步监视器:t1,t2,t3,此种解决方式是错误的
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
}
}
}
public class WindowTest3 {
public static void main(String[] args) {
Window3 t1 = new Window3();
Window3 t2 = new Window3();
Window3 t3 = new Window3();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
8.4.2:单例模式的懒汉式修改为线程安全的
package com.jiayifeng.java.java1;
/**
* author xiaojia
* create 2023-09-10 8:55
*
* 快捷键:
* 1.alt + shift + z:打开surround with
* 2.Ctrl + alt + enter:向上开辟一行
* 3.tab + s:构造器
* 一:使用同步机制将单例模式中的懒汉式改写为线程安全的
*/
public class BankTest {
}
class Bank{
private Bank(){}
private static Bank instance = null;
// 效率更高
public static Bank getInstance() {
if (instance == null) {
synchronized (Bank.class) {
if (instance == null) {
instance = new Bank();
}
}
}
return instance;
}
}
8.4.3:线程的死锁问题
死锁:
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
解决方法:
专门的算法、原则
尽量减少同步资源的定义
尽量避免嵌套同步
package com.jiayifeng.java1;
/**
* author xiaojia
* create 2023-09-10 9:22
*
* 线程的死锁问题
*/
public class ThreadTest {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
// 匿名创建
new Thread(){
@Override
public void run() {
synchronized (s1){
s1.append("a");
s2.append(1);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append("b");
s2.append(2);
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2){
s1.append("c");
s2.append(3);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append("d");
s2.append(4);
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
8.4.4:锁(Lock)
从JDK 5.0开始,Java提供了更强大的线程同步机制--通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当
package com.jiayifeng.java1;
import java.util.concurrent.locks.ReentrantLock;
/**
* author xiaojia
* create 2023-09-10 9:44
*
* 一:解决线程安全问题的方式三:Lock --- JDK 5.0新增
* 1.面试题:synchronized与lock的异同?
* 相同:两者都可以解决线程安全问题
* 不同:synchronized机制在执行完相应的同步代码后,自动的释放同步监视器
* lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())
*
* 2.优先使用顺序:
* lock -> 同步代码块(已经进入了方法体,分配了相应的资源) -> 同步方法(在方法体之
* 外)
*/
class Window implements Runnable{
private int ticket = 100;
// 1.实例化
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(true){
try{
// 2.调用锁定方法lock()
lock.lock();
if(ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":售票,票号为;" + ticket);
ticket--;
}else{
break;
}
}finally {
// 3.调用解锁的方法:unlock()
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
Window w = new Window();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
8.5:线程的通信
package com.jiayifeng.java.java2;
/**
* author xiaojia
* create 2023-09-10 10:25
*
* 1.线程通信的example:使用两个线程打印1-100.线程1,线程2交替打印
* 2.涉及到的三个方法:
* wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
* notify():一旦执行此方法,就会唤醒被wait()的一个线程。如果有多个线程,就唤醒
* 优先级高的
* notifyAll():一旦执行此方法,就会唤醒所有被wait的线程
* 3.说明:
* 这三个方法必须使用在同步代码块和同步方法中
* 这三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则会出现异常
* 这三个方法是定义在java.lang.Object类中的
* 4.面试题:sleep()和wait()的异同?
* ①相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态
* ②不同点:
* 1>两个方法声明的位置不同:Thread类中声明sleep(),Object()类中声明wait()
* 2>调用的要求不同:sleep()可以在任何需要的场景下使用,wait()必须使用在
* 同步代码块或同步方法中
* 3>是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()
* 不会释放锁,wait()会释放锁
*/
class Number implements Runnable{
private int number = 1;
@Override
public void run() {
while (true) {
synchronized (this) {
// 唤醒阻塞的线程
notify();
if (number < 100) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
try {
// 使得调用如下wait()方法的线程进入阻塞状态
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number n1 = new Number();
Thread t1 = new Thread(n1);
Thread t2 = new Thread(n1);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
8.6:JDK5.0新增线程创建方式
新增方式一:实现Callable接口
与使用Runnable相比,Callable功能更强大些
-相比run()方法,可以有返回值
-方法可以抛出异常
-支持泛型的返回值
-需要借助FutureTask类,比如获取返回结果
Future接口:
-可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等
-FutureTask是Future接口的唯一的实现类
-FutureTask同时实现了Runnable,Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
package com.jiayifeng.java.java2;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* author xiaojia
* create 2023-09-10 14:32
*
* 一:创建线程的方式三:实现Callable接口 --- JDK 5.0新增
*/
//1.创建一个实现Callable的实现类
class NumThread implements Callable{
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum = 0;
for(int i = 0;i <= 100;i++){
if (i % 2 == 0) {
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
// 3.创建Callable接口实现类的对象
NumThread n1 = new NumThread();
// 4.将此Callable接口实现类的对象作为传递到FutureTask的构造器中,创建
// FutureTask的对象
FutureTask f1 = new FutureTask(n1);
// 5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread
// 对象,并调用start()方法
new Thread(f1).start();
try {
// 6.获取Callable中call方法的返回值
// get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值
Object sum = f1.get();
System.out.println("总和为:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
新增方式二:使用线程池
思路:提前创好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用
好处:
->提高相应速度(减少了创建新线程的时间)
->降低资源消耗(重复利用线程池中线程,不需要每次都创建)
->便于线程管理
-corePoolSize:核心池的大小
-maximumPoolSize:最大线程数文章来源:https://www.toymoban.com/news/detail-700316.html
-keepAliveTime:线程没有任务时最多保持多长时间后会终止文章来源地址https://www.toymoban.com/news/detail-700316.html
package com.jiayifeng.java.java2;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
/**
* author xiaojia
* create 2023-09-10 15:54
*
* 一:创建线程的方式四:使用线程池
*/
class NumberThread implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
// 1.提供指定线程数量的线程池
ExecutorService serivce = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) serivce;
// 设置线程池的属性
System.out.println(serivce.getClass());
service1.getCorePoolSize();
// 2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
serivce.execute(new NumberThread()); //适用于Runnable
// serivce.submit(); //适用于Callable
// 3.关闭连接池
serivce.shutdown(); //关闭线程
}
}
到了这里,关于第八章:多线程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!