前言
优点
- 提高应用程序的响应,对图形化界面更有意义,可增强用户体验
- 提高计算机系统CPU的利用率
- 改善程序结构,将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
何时需要多线程
- 程序同时执行两个或多个任务
- 需要实现一些等待任务时,如用户输入,文件读写操作,网络操作,搜索
- 需要一些后台运行的程序
创建方式(4种)
1. 继承Thread,重写run方法
- 注意,一定要【对象名】.start()调用,不能直接调用run方法
2. 实现Runnable接口
- 创建一个实现了Runnable接口的类
- 在run()中编写功能代码
- 创建实现类的对象
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
- 调用start()方法,启动线程
public class HeapDemo implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
HeapDemo heapDemo = new HeapDemo();
Thread thread = new Thread(heapDemo);
thread.start();
Thread thread1= new Thread(heapDemo);
thread1.start();
}
}
上述两种创建方式的区别:优先选择Runnable,因为此方法 (1)没有类的单继承的局限性(2)实现方式更加适合处理多线程有共享数据的情况
3. Thread也实现了Runnable
-
实现Callable接口(jdk5.0新增)
相比Runnable,Callable功能更强大些
- 相比run()方法,可以有返回值
- 方法可以抛异常
- 支持泛型返回值
- 需要借助FutureTask类,比如获取返回值
eg:
public class HeapDemo implements Callable { @Override public Object call() throws Exception { return 222; } } class tews{ public static void main(String[] args) { // 创建Callable接口的实现类 HeapDemo heapDemo = new HeapDemo(); // 将此接口的对象传递到FutureTask中,创建FutureTask对象 FutureTask futureTask = new FutureTask(heapDemo); // 将futureTask作为参数传递到Thread的构造器中,并启动线程 new Thread(futureTask).start(); try { // 获取call返回值 Object o = futureTask.get(); System.out.println(o);// 222 } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
4. 使用线程池
背景:
经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路: 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
好处:- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)便于线程管理
- corePoolSize:核心池的大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
public class HeapDemo { public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(20); executorService.execute(new te());// 适用于Runnable executorService.submit(new te());// 适用于Callable executorService.shutdown(); } } class te implements Runnable{ @Override public void run() { int i = 0; while (true) { System.out.println(i); if (i > 100) { break; } i++; } } }
线程的生命周期
新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时他已经具备运行条件,只是没有分配到CPU资源
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
阻塞: 在某种情况下,被人为挂起或执行输入输出时,让出CPU并临时中止自己的执行,进入阻塞状态
死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
常用方法
- start():启动当前线程,调用run()方法
- run():重写Thread类中的此方法,将线程要进行的操作写在其中
- currentThread():静态方法,返回当前代码的线程
- getName():获取当前线程的名字
- setName():设置当前线程的名字
- yield():线程让步,释放当前cpu的执行权。让给同优先级或优先级更高的线程。若队列中没有同优先级的线程,则忽略此方法
- join():在线程A中调用线程B的join(),此时A进入阻塞状态,直到B执行完后线程A在结束阻塞,开始执行。
- sleep():指定时间为毫秒,令当前线程在指定时间内放弃对cpu的控制,使其他线程有机会被执行,时间到后重新排队
- stop():强制结束线程。不推荐使用
- isAlive():返回boolean,判断线程是否还活着
- setPriority():设置线程优先级
- getPriority():设置线程优先级
我们调用 start() 方法时执行 run() 方法和直接调用 run() 方法的区别?
答:调用 start()
方法,会启动一个线程并使线程进入了就绪状态,当分配到CPU时间片后就可以开始运行了。此时运行的是真正的线程,会自动执行run()
方法中的内容,而我们直接调用run()
方法的话,就相当于调用了一个普通的方法,并不会创建一个线程去执行它。