• Python——BeautifulSoup库



    前言

    线程(thread)是一个程序内部的一条执行路径。
    我们之前启动程序执行后,main方法的执行其实就是一条单独的执行路径。

    程序中如果只有一条执行路径,那么这个程序就是单线程的程序。

    多线程是指从软硬件上实现多条执行流程的技术
    或者说多线程是指多个可以同时并发运行的程序

    消息通信、淘宝、京东系统等都离不开多线程技术。


    在这里插入图片描述

    多线程

    并发:在一个时间段出现的,可能不是同时发生
    并行:同时发生
    进程:一个任务创建、运行、消亡
    线程:进程的一个单元,一个进程可以有多个线程。

    多线程的实现

    多线程实现方式有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()返回线程名称
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    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
    • 2
    • 3
    • 4
    • 5
    • 6

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

    Thread的构造器

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

    优缺点

    优点:编码简单
    缺点:存在单继承的局限性,线程类继承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()返回线程名称
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    优缺点

    优点:线程任务类只是实现了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 "二哈喇子";
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    测试类:

    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执行结束");
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    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();
    			}
    			 
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    线程安全问题是由全局变量及静态变量引起的,若多线程同时执行写操作(单纯读操作一般是线程安全的),线程会去抢夺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();    
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    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();
    					}
    				}
    			}				 
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    同步方法

    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();
    			}
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    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
        Thread t2 = new Thread(myThread);//线程2
        t1.start();
        t2.start();
       }  
    }
    class MyThread implements Runnable{
      private int num =100;//定义一个共享票源
      Lock lock = new ReentrantLock();  //声明一个lock对象
      @Override
      public void run() {
        while(true) {//还有票
        	lock.lock();   //上锁
        	if(num > 0) {
        		try {
        	        Thread.sleep(50);//为了模拟买票操作耗时
        	        System.out.println(Thread.currentThread().getName()+"正在卖"+num+"票");//获取当前线程名
        	         num--;
        	      } catch (InterruptedException e) {
        	        e.printStackTrace();
        	      }
        	}
        	lock.unlock(); //释放锁
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    线程池

    问题:如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。

    线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

    好处

    1、降低资源消耗,如果线程池无空闲,则需等待其他任务执行完毕后归还线程。

    2、提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。

    3、提高线程的可管理性,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    /*1.创建线程池对象
    2.实现Runnable接口子类
    3.使用线程池
    4.关闭线程池*/
    public class Test {
      public static void main(String[] args)  {
        //1.创建线程池对象
    	  ExecutorService service = Executors.newFixedThreadPool(2);  //包含2个线程对象
    	  MyThread m = new MyThread();  //2.实现Runnable接口子类
    //	  Thread t = new Thread(m);
    //	  t.start();
    	  service.submit(m);//3.使用线程池
    	  service.submit(m);//submit执行之后程序并没有关闭,是因为线程池控制了线程的关闭
    	  service.submit(m);
    	//  service.shutdown();//4.关闭线程池
    	 // service.submit(m); //关闭之后再执行则报异常
       }  
    }
    class MyThread implements Runnable{
    	@Override
    	public void run() {
    		 System.out.println("售票员开始卖票");
    		 try {
    			Thread.sleep(50);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}//为了模拟买票操作耗时
    		 System.out.println(Thread.currentThread().getName()+"正在卖票");//获取当前线程名
    		 System.out.println("线程结束,回到线程池");
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
  • 相关阅读:
    C++基础——输入输出和缺省参数讲解
    SQL窗口分析函数使用详解系列三之偏移量类窗口函数
    2023-06-13:统计高并发网站每个网页每天的 UV 数据,结合Redis你会如何实现?
    golang 通过案列感受下内存分析
    Golang GMP解读
    个人小程序申请制作流程
    JAVA和JVM和JDK和JRE和JAVA SE 是什么? 他们有什么区别? 怎么区分 编程下哪个?
    深入浅出MySQL-04-【常用函数】
    stm32 操作W25Q256 W25Q16 spi flash
    Kurator v0.4.0版本更新4大内容,满足多云环境的复杂需求
  • 原文地址:https://blog.csdn.net/rej177/article/details/127850887