多线程的创建、运行

1.0 有哪些方法创建线程

new Thread(); 的方式

// 继承 Thread 、实现 Runable接口、实现 Callable<V> 接口 都不是创建线程方式 而是 运行 线程的方式

// 都是 重写 run() 方法

1.1 Java 提供了三种运行线程的方式:

  • 通过实现 Runnable 接口;
  • 通过继承 Thread 类本身;
  • 通过 Callable 和 Future 创建线程。

1.1.2 继承Thread类:

  步骤:①、定义类继承Thread;

     ②、复写Thread类中的run方法;

   目的:将自定义代码存储在run方法,让线程运行

     ③、调用线程的start方法:

     该方法有两步:启动线程,调用run方法。

 

1.1.3 实现Runnable接口:

 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为run 的无参方法。

    步骤:  ①、定义类实现Runnable接口

       ②、覆盖Runnable接口中的run方法

         将线程要运行的代码放在该run方法中。

       ③、通过Thread类建立线程对象。

       ④、将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。

         自定义的run方法所属的对象是Runnable接口的子类对象。所以要让线程执行指定对象的run方法就要先明确run方法所属对象

       ⑤、调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。

 

实现Runnable 接口 Demo

1. 创建线程

package com.thread;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RunnableThread implements Runnable {
    private static final Logger logger = LoggerFactory.getLogger(RunnableThread.class);

    @Override
    public void run() {
        logger.info("执行线程!");
    }
}

2. 

package com.thread;

import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

public class ThreadTest02 {
    public static void main(String[] args){
        ThreadPoolTaskExecutor poolTaskExecutor = new  ThreadPoolTaskExecutor();
        //线程池维护线程的最少数量
        poolTaskExecutor.setCorePoolSize(5);
        //线程池维护线程的最大数量
        poolTaskExecutor.setMaxPoolSize(15);
        //线程池所使用的缓冲队列
        poolTaskExecutor.setQueueCapacity(200);
        //线程池维护线程所允许的空闲时间
        poolTaskExecutor.setKeepAliveSeconds(30000);
        poolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        poolTaskExecutor.initialize();

        RunnableThread testThread = new RunnableThread();
        poolTaskExecutor.execute(testThread);

    }
}

运行如下:

 

1.1.4 通过Callable和Future运行线程:

各种线程池其实都是实现了 ExecutorService 的,Callable 任务需要提交到线程池中才能运行:

    步骤:1、创建Callable接口的实现类(自定义一个类实现java.util.concurrent包下的Callable接口),

2、重写call()方法,将要在线程中执行的代码编写在call方法中,且具有返回值。

       3、创建Callable实现类的实例(创建ExecutorService线程池),使用FutrueTask类进行包装Callable对象,FutureTask对象封装了Callable对象的call()方法的返回值

       4、使用FutureTask对象作为Thread对象启动新线程。

       5、调用FutureTask对象的get()方法获取子线程执行结束后的返回值。

6、关闭线程池,不再接收新的线程,未执行完的线程不会被关闭

 

Callable<V> 接口 Demo

1. 实现 Callable<V> 接口,本例实现 Callable<Student> 接口。

package hy_demo.thread;
import java.util.concurrent.Callable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CallableStudent implements Callable<Student> {

	private Logger logger = LoggerFactory.getLogger(CallableStudent.class);
	
	private Student student;
		
	public CallableStudent(Student student) {
		this.student = student;
	}

	@Override
	public Student call() throws Exception {
		logger.info(">>>>>>>>进入call() 方法-------------------------");
		if(Integer.parseInt(student.getAge()) > 10){
			return student;
		}else{
			return null;
		}
	}
}

 

package com.thread;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.FutureTask;

public class ThreadTest {
    private static Logger logger = LoggerFactory.getLogger(ThreadTest.class);

    public static void main(String[] args) {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        //如果池中的实际线程数小于corePoolSize,无论是否其中有空闲的线程,都会给新的任务产生新的线程
        taskExecutor.setCorePoolSize(5);
        //连接池中保留的最大连接数。
        taskExecutor.setMaxPoolSize(15);
        //queueCapacity 线程池所使用的缓冲队列
        taskExecutor.setQueueCapacity(6000);
        //强烈建议一定要给线程起一个有意义的名称前缀,便于分析日志
        taskExecutor.setThreadNamePrefix("Student-Thread-");
        taskExecutor.initialize();

        Random randmo = new Random();
        List<Student> list = new ArrayList<Student>();
        for(int i = 0;i < 100000;i++){
            Student stu = new Student();
            stu.setName("stu" + i );
            stu.setAge(String.valueOf(randmo.nextInt(20)));
            list.add(stu);
        }

        long time1 = System.currentTimeMillis();
        for (Student student : list) {
            CallableStudent  callableStudent =new CallableStudent(student);
            FutureTask<Student> future = (FutureTask<Student>) taskExecutor.submit(callableStudent);
            try {
                if(null != future.get()){
                    //输出 CallableStudent ( Callable<Student> ) 线程实现类里,call() 方法返回的数据
                    //本例中返回 Student 对象,若是 Callable<Boolean>,则返回 boolean☞
                    System.out.println("future.get():" + future.get().getName() + " | " + future.get().getAge());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        long time2 = System.currentTimeMillis();
        long time = time2 - time1;
        logger.info("时间:" + time);

    }

}

 

实体类

package hy_demo.thread;

public class Student {

	private String name;
	
	private String age;

	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getAge() {
		return age;
	}
	public void setAge(String age) {
		this.age = age;
	}
}

 

运行结果如下:符合年龄大于10的打印出来

 

1.5 CompletableFuture

CompletableFuture是Java 8中对Future的增强版。

CompletableFuture跟性能上关系不大,更多的是为了支持函数式编程,在功能上的增强。当然开放了完成时间的设置是一大亮点。

 

Future接口的局限性:

1.将多个异步计算的结果合并成一个

2.等待Future集合中的所有任务都完成

3.通过编程方式完成一个Future任务的执行(即以手工设定异步操作结果的方式)。

4.Future完成事件(即,任务完成以后触发执行动作)

 

不管是Runnable任务还是Callable任务,线程池执行的任务可以划分为4个生命周期阶段:

  • 创建:创建任务对象的时期。
  • 提交:调用线程池的excute或者submit方法后,将任务塞到任务队列的时期。
  • 执行中:某个线程从任务队列中将任务取出开始执行的时期。
  • 完成:任务执行结束。

 

四、继承Thread类和实现Runnable接口、实现Callable接口的区别。

    继承Thread:线程代码存放在Thread子类run方法中。

        优势:编写简单,可直接用this.getname()获取当前线程,不必使用Thread.currentThread()方法。

        劣势:已经继承了Thread类,无法再继承其他类。

    实现Runnable:线程代码存放在接口的子类的run方法中。

        优势:避免了单继承的局限性、多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。

        劣势:比较复杂、访问线程必须使用Thread.currentThread()方法、无返回值。

    实现Callable:

        优势:有返回值、避免了单继承的局限性、多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。

        劣势:比较复杂、访问线程必须使用Thread.currentThread()方法

  建议使用实现接口的方式创建多线程。

 

 

总结:

同步的前提:

  1、必须要有两个或者两个以上的线程。

  2、必须是多个线程使用同一个锁。

  3、必须保证同步中只能有一个线程在运行。

  4、只能同步方法,不能同步变量和类。

  5、不必同步类中所有方法,类可以拥有同步和非同步的方法。

  6、如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。

  7、线程睡眠时,它所持的任何锁都不会释放。

 

  好处:解决了多线程的安全问题。

  弊端:多个线程需要判断,消耗资源,降低效率。

 

  如何找问题?

  1、明确哪些代码是多线程运行代码。

  2、明确共享数据。

  3、明确多线程运行代码中哪些语句是操作共享数据的。

 

死锁形成的必要条件总结(都满足之后就会产生):

    ①、互斥条件:资源不能被共享,只能被同一个进程使用;

    ②、请求与保持条件:已经得到资源的进程可以申请新的资源;

    ③、非剥夺条件:已经分配的资源不能从相应的进程中强制剥夺;

    ④、循环等待条件:系统中若干进程形成环路,该环路中每个进程都在等待相邻进程占用的资源。

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值