Java面向对象程序开发——多线程开发,2024年最新大数据开发开发究竟该如何学习

47 阅读5分钟

多线程实现方式有3种:
继承Thread类
实现Runnable接口重写run方法(方法无返回值 )
实现Callable接口,重写call方法(方法可以有返回值 )

①、继承Thread类

① 定义一个子类,重写run方法
② 实例化子类
③ 对子类对象执行start方法来启动线程
(直接调用run方法会当成普通方法执行,此时相当于还是单线程执行。只有调用start方法才是启动一个新的线程执行。)

public class Test {
	public static void main(String[] args)  {
		MyThread myThread = new MyThread();//2.实例化子类,创建Thread的子类对象
		myThread.start();  //3.对子类对象执行start方法来启动线程
		for(int i =0; i<10; i++) {
			System.out.println("主线程"+i);
		}
	 }	
}

class MyThread extends Thread{ //1.自定义一个子类,重写run方法
	@Override
	public void run(){
		for(int i =0; i<10; i++) {
			System.out.println("子线程"+getName()+i); //getName()返回线程名称
		}
	}
}

Java是通过java.lang.Thread 类来代表线程的。
按照面向对象的思想,Thread类应该提供了实现多线程的方式。

Thread常用方法

String getName​()  //获取当前线程的名称,默认线程名称是Thread-索引
void setName​(String name)  //将此线程的名称更改为指定的名称,通过构造器也可以设置线程名称
public static Thread currentThread():  //返回对当前正在执行的线程对象的引用
public static void sleep(long time)  //让当前线程休眠指定的时间后再继续执行,单位为毫秒(Thread类的线程休眠方法)
public void run()  //线程任务方法
public void start()  //线程启动方法

注意:
1、此方法是Thread类的静态方法,可以直接使用Thread类调用。
2、这个方法是在哪个线程执行中调用的,就会得到哪个线程对象。

Thread的构造器

public Thread(String name)  //可以为当前线程指定名称
public Thread(Runnable target)  //封装Runnable对象交给线程对象
public Thread(Runnable target,String name)  //封装Runnable对象成为线程对象,并指定线程名称

优缺点

优点:编码简单
缺点:存在单继承的局限性,线程类继承Thread后,不能继承其他类,不便于扩展。不能返回线程执行结果

②、实现Runnable接口

① 声明一个Runnable接口实现类,重写run方法
② 实例化,把实例化对象作为线程的目标进行创建
③ 执行start方法来启动线程

public class Test {
	public static void main(String[] args)  {
		MyThread myThread = new MyThread();//2.实例化
		Thread t = new Thread(myThread);//2.把实例化对象myThread作为Thread的target进行创建
		t.start();//启动线程
		for(int i =0; i<10; i++) {
			System.out.println("主线程"+i);
		}//主线程和子线程在抢夺资源,所以每次运行结果都不一样
	 }	
}

//1.声明一个Runnable接口实现类,重写run方法
class MyThread implements Runnable{
	@Override
	public void run() {
		for(int i =0; i<10; i++) {
			System.out.println("子线程"+Thread.currentThread().getName()+i); //getName()返回线程名称
		}
	}
}

优缺点

优点:线程任务类只是实现了Runnale接口,可以继续继承类和实现接口,扩展性强
缺点:编程多一层对象包装,如果线程有执行结果是不可以直接返回的

③、实现Callable接口

与Thread类、Runnable接口对比它是可以有返回值

public class Demo3 implements Callable<String>{

	//实现call方法
	@Override
	public String call() throws Exception {
		System.out.println("Demo3开始运行");
		Thread.sleep(3000);  //暂停3000毫秒
		System.out.println("Demo3运行结束");
		return "二哈喇子";
	}
}

测试类:

public class 实现Callable接口线程测试 {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		Demo3 d3 = new Demo3();
		//创建FutureTask类对象 获得线程执行后返回值
		FutureTask<String> ft = new FutureTask<>(d3);
		//创建Thread类对象
		Thread thread = new Thread(ft);
		
		thread.start();
		String result = ft.get();
		
		System.out.println("Demo3执行完的结果是"+result);
		System.out.println("main执行结束");
	}
}

Runnable接口和继承Thread类的区别

这个问题面试的时候经常问

① 、创建线程的方式不同

② 、设置线程名方式不同

③、获取线程名方式不同

④ 、由于Java是单继承,一个类继承Thread类以后不能继承其他类,扩展性不好。而实现Runnable接口则可以侧面实现了多继承

⑤、继承Thread类不能实现线程变量资源共享,注意,是线程里的变量实现Runnable 接口线程变量是可以共享的,也可以不共享,看创建线程的方式

多线程安全怎么处理

互斥访问: 多个线程同时访问同一个共享资源时,需要保证同一时刻只有一个线程能够访问该资源,以避免数据竞争的发生。可以使用synchronized关键字、Lock接口等机制来实现互斥访问。

原子操作: 原子操作是指一系列不可分割的操作,不会被其他线程中断。在多线程环境下,需要保证原子操作的执行,以避免数据的不一致性。可以使用AtomicInteger、AtomicLong等原子类来实现原子操作。

可见性: 多个线程同时访问同一个变量时,需要保证对该变量的读写操作对其他线程是可见的,以避免出现数据不一致的情况。可以使用volatile关键字来保证变量的可见性。

线程同步机制

模拟电影院卖100张票

public class Test {
	public static void main(String[] args)  {
		MyThread myThread = new MyThread();
		Thread t1 = new Thread(myThread);//线程1
		Thread t2 = new Thread(myThread);//线程2
		t1.start();
		t2.start();
	 }	
}
class MyThread implements Runnable{
	private int num =100;//定义一个共享票源
	@Override
	public void run() {
		while(num > 0) {//还有票
			try {
				Thread.sleep(50);//为了模拟买票操作耗时
				System.out.println(Thread.currentThread().getName()+"正在卖"+num+"票");//获取当前线程名
				 num--;
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			 
		}
	}
}

线程安全问题是由全局变量及静态变量引起的,若多线程同时执行写操作(单纯读操作一般是线程安全的),线程会去抢夺cpu资源完成操作,造成线程不安全。

同步机制:
1、volatile
2、同步锁
3、同步方法
4、CAS
5、Lock锁

synchronized(同步锁){
	需要同步操作的代码:1锁对象可以是任意类型。2多个线程对象要使用同一把锁。
}

public synchronized void method(){
	可能会产生线程安全问题的代码
}

class Ticket implements  Runnable{
    Lock lock = new ReentrantLock();
    private int num = 100;//定一个多线程共享的票源
    @Override
    public void run() {
        while (true){
         //上锁
            lock.lock();
            if (num>0){
              ...//代码省略
            }
            //释放锁
            lock.unlock();    
        }
    }
}

volatile

假如说线程1修改了data变量的值为1,然后将这个修改写入自己的本地工作内存。
那么此时,线程1的工作内存里的data值为1。
然而,主内存里的data值还是为0!线程2的工作内存里的data值还是0啊?!

作用:

1、一旦data变量定义的时候前面加了volatile来修饰的话,那么线程1只要修改data变量的值,就会在修改完自己本地工作内存的data变量值之后,强制将这个data变量最新的值刷回主内存,必须让主内存里的data变量值立马变成最新的值!
2、如果此时别的线程的工作内存中有这个data变量的本地缓存,也就是一个变量副本的话,那么会强制让其他线程的工作内存中的data变量缓存直接失效过期,不允许再次读取和使用了!
3、如果线程2在代码运行过程中再次需要读取data变量的值,他就必须重新从主内存中加载data变量最新的值!那么不就可以读取到data = 1这个最新的值了!

同步锁

在这里插入图片描述

public class Test {
	public static void main(String[] args)  {
		MyThread myThread = new MyThread();
		Thread t1 = new Thread(myThread);//线程1
		Thread t2 = new Thread(myThread);//线程2
		t1.start();
		t2.start();
	 }	
}
class MyThread implements Runnable{
	private int num =100;//定义一个共享票源
	Object obj = new Object();
	@Override
	public void run() {
		while(true) {//窗口永远开启
			synchronized(obj) {//同步锁
				if(num > 0) {
					try {
						Thread.sleep(50);//为了模拟买票操作耗时
						System.out.println(Thread.currentThread().getName()+"正在卖"+num+"票");//获取当前线程名
						 num--;
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}				 
		}
	}
}

同步方法

public class Test {
	public static void main(String[] args)  {
		MyThread myThread = new MyThread();
		Thread t1 = new Thread(myThread);//线程1
		Thread t2 = new Thread(myThread);//线程2
		t1.start();
		t2.start();
	 }	
}
class MyThread implements Runnable{
	private int num =100;//定义一个共享票源
	Object obj = new Object();
	@Override
	public void run() {
		while(true) {//窗口永远开启
			method();	 
		}
	}
	public synchronized void method(){ //同步方法
		if(num > 0) {
			try {
				Thread.sleep(50);//为了模拟买票操作耗时
				System.out.println(Thread.currentThread().getName()+"正在卖"+num+"票");//获取当前线程名
				 num--;
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

CAS(Compare and Set)

同步和异步有什么区别

同步(Synchronous):在同步处理中,一个任务的完成需要等待另一个任务完成后才能进行。适用于需要按照一定顺序执行任务的情况,例如多个线程共享数据、调用需要花费长时间才能完成的方法等

异步(Asynchronous):在异步处理中,多个任务可以同时进行,不必等待前一个任务完成。适用于不需要等待前一个任务完成就可以执行下一个任务的情况,例如发送一个请求并希望在得到响应后继续执行其他任务。

lock锁

使用ReentrantLock实现同步, lock()方法上锁,unlock()方法释放锁

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Test {
  public static void main(String[] args)  {
    MyThread myThread = new MyThread();
    Thread t1 = new Thread(myThread);//线程1


![img](https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/8f5c1a42f5a54c4fab25c11a01fa4be6~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5py65Zmo5a2m5Lmg5LmL5b-DQUk=:q75.awebp?rk3s=f64ab15b&x-expires=1772471853&x-signature=bYkuaC1DWCqI6q86qcX9OL5d3Ks%3D)
![img](https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/2b9ea789ae3f4f2e94f8e5b7967ffeb9~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5py65Zmo5a2m5Lmg5LmL5b-DQUk=:q75.awebp?rk3s=f64ab15b&x-expires=1772471853&x-signature=k3LAu4pogy%2FzKnL2b5oKi3CGFoY%3D)
![img](https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/3dddb306b3c14d2bbd5ed402dc36d24d~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5py65Zmo5a2m5Lmg5LmL5b-DQUk=:q75.awebp?rk3s=f64ab15b&x-expires=1772471853&x-signature=KBmS3zciPjoJFIbWzLa%2Bpmv9KM8%3D)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!**


**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[需要这份系统化资料的朋友,可以戳这里获取](https://gitee.com/vip204888)**