Java 多线程,
Java 多线程,
多线程
1. 线程与进程的关系
(1) 进程是运行中的程序,具有如下特点:
① 独立性:拥有自己的资源和独立的内存区,通常来说,一个程序的内存空间是不允许其它进程访问的;
② 动态性:程序是静止的,运行起来才称作进程;
③ 并发性:操作系统可以同时“并发(concurrent)”运行多个进程。
(2) 线程:进程中的“并发”执行流;
(3) 进程与线程的区别:进程(Process)是有独立的内存空间的,因而创建进程的代价比创建线程大;
(4) 多线程的优势:
① 线程功能上类似于进程,但创建代价比进程小,效率更高;
② 所有线程共享进程的内存,因此线程间的通信非常方便;
2. 线程的创建和启动
(1) Thread ——就是线程,Java 程序默认有主线程,主线程的线程执行体(线程将要做的事情)就是 main()方法;
(2) Java 创建线程的3种方法和启动方法:
① 继承 Thread,重写一个 run() 方法 ,这个run()方法就是线程执行体;
—— 该run()方法没有返回值,不能抛出异常
—— 创建Thread 的子类的实例就可以启动
示例程序:
public class FirstThread extends Thread
{
@Override
//该run()方法重写父类的run()方法,没有返回值也不能声明抛出异常
public void run()
{
for(int i=0;i<10;i++)
{
// currentThread()用于返回当前正在运行的线程
// getName()方法用于获得线程的名称
System.out.println(FirstThread.currentThread().getName()+"----"+i);
}
}
public static void main(String[] args)
{
//********************************
//FirstThread ft = new FirstThread();
//ft.start();
//等同下面的写法
//创建Thread 的子类的实例调用start()来启动线程
new FirstThread().start();
//以下作为主线程的线程执行体
for(int i=0;i<10;i++)
{
// currentThread()用于返回当前正在运行的线程
// getName()方法用于获得线程的名称
System.out.println(FirstThread.currentThread().getName()+"----"+i);
}
}
}
运行结果:(一般不同机器多次运行结果可能会不同)
② 实现Runnable接口,重写一个 run() 方法 ,这个run()方法就是线程执行体;
—— 该run()方法没有返回值,不能抛出异常
—— 需要将 Runnable对象包装成 Thread对象之后再启动
示例程序:
public class SecondThread implements Runnable
{
@Override
//该run()方法重写父类的run()方法,没有返回值也不能声明抛出异常
public void run()
{
for(int i=0;i<10;i++)
{
// currentThread()用于返回当前正在运行的线程
// getName()方法用于获得线程的名称
System.out.println(Thread.currentThread().getName()+"----"+i);
}
}
public static void main(String[] args)
{
//需要将Runnable包装成 Thread对象才能启动
SecondThread st = new SecondThread();
new Thread(st).start();
//以下作为主线程的线程执行体
for(int i=0;i<10;i++)
{
// currentThread()用于返回当前正在运行的线程
// getName()方法用于获得线程的名称
System.out.println(Thread.currentThread().getName()+"----"+i);
}
}
}
运行结果:(一般不同机器多次运行结果可能会不同)
③ 实现 Callable 接口(相当于是 Runnable的增强版)
—— 重写call()方法,该方法有返回值,可以声明抛出异常
—— 需要先将Callable包装成Runnable(FutureTask),再把Runnable包装成Thread对象后再启动;FutureTask 还可以用于获取线程的返回值 。
示例程序:
import java.util.concurrent.*;
public class ThirdThread implements Callable
{
@Override
//该 call()方法有发返回值、可以声明抛出异常,还可以使用泛型
public Integer call() throws InterruptedException
{
for(int i=0;i<10;i++)
{
// currentThread()用于返回当前正在运行的线程
// getName()方法用于获得线程的名称
System.out.println(Thread.currentThread().getName()+"----"+i);
}
return 0;
}
public static void main(String[] args)
{
ThirdThread tt = new ThirdThread();
//把Callable包装成 FutureTask(Runnable接口的实现类)
//可以使用泛型
FutureTask<Integer> ft = new FutureTask<>(tt);
new Thread(ft).start();
//以下作为主线程的线程体
for(int i=0;i<10;i++)
{
// currentThread()用于返回当前正在运行的线程
// getName()方法用于获得线程的名称
System.out.println(Thread.currentThread().getName()+"----"+i);
}
}
}
运行结果:(一般不同机器多次运行结果可能会不同)
(3) 线程的启动:调用 Thread对象的 start()方法,注意不是直接调用run()方法,直接调用run()方法只是普通的方法调用,不会启动多线程。
(4) 创建线程的方法比较
① 创建线程的方法可以分为2类:
— 继承 Thread类
— 实现 Runnable 和 Callable 接口
② 实际中,实现接口的方法比较好,原因是:实现接口之后依然可以继承其它类,但是继承了 Thread 类就无法再继承其它类;实现接口时,可以让多个线程共享同一个 Runnable 对象,可以更好地实现代码与数据的分离,形成更清晰的逻辑,唯一的不足时编程相对略微的复杂。
3. 线程的状态
—— 当调用 start()方法后,只是启动线程,线程并不会立即执行。线程的状态变迁图如下:
4. 控制线程的方法
(1) join 线程
—— 让加一个线程等待另一个线程的方法,当某个程序在执行流中调用其他线程的 join()方法时,调用线程将会被阻塞,直到被 join方法假如的join线程完成为止,简单的说就是调用join方法的线程必须等到被 joined 的线程结束才能再次执行。
(2) 后台线程
—— Daemon Thread ,即守护线程或者精灵线程,JVM的垃圾回收线程就是典型的后台线程;
—— 特点是所有的前台线程结束,后台进程会自动死亡(注意主线程结束,子线程并不一定结束);
—— 调用Thread对象的 setDaemon(true)可将指定线程是指成为后台线程。
(3) 线程等待(暂停)
—— Thread.sleep(time):让线程等待(暂停)time ms,并进入阻塞状态。
(4) 线程让步
—— Thread.yield() :让线程让出CPU,进入就绪状态,所以可能出现某进程让出CPU进入就绪状态后,线程调度器自此将其调度出来执行。
★ sleep 和 yield 的区别
① sleep方法暂停当前进程后,会给其他线程执行机会,不会理会其它进程的优先级,但是yield 方法只会给优先级相同或者更高的线程执行机会;
② sleep 方法会将线程转入阻塞状态,直到经过阻塞时间才会转入就绪状态,而yield方法是强制当前进程进入就绪状态;
③ sleep 方法声明抛出了InterruptedException 异常,所以调用sleep方法是要么捕捉该异常,要么显示声明抛出该异常,而yield方法没有声明抛出异常;
④ sleep方法比yield方法有更好的可移植性,通常不要依靠yield来控制并发线程的执行。
5.线程同步(线程安全)
(1) 竞争资源(共享资源)
—— 如果有多条线程需要并发访问,并修改某个对象,该对象就是“竞争资源”,为了避免多个线程“自由竞争”修改共享资源所导致的不安全问题,可以考虑加锁。
(2) 线程同步方法:
① 用同步代码块:需要显示指定同步监视器;
② 用同步方法:相当于使用方法调用者作为同步监视器,如果该方法是实例方法,相当于用 this作为同步监视器,如果该方法是类方法,相当于以类作为同步监视器;
★ 二者的实现机制是完全相同的,从语法角度来看,任意对象都可作为同步器,从程序逻辑看,选择“竞争资源”做同步监视器。
6. 线程通信
—— 当一个程序中有多条并发线程执行时,线程之间是互不干扰的,有些时候需要在线程之间进行通信,让多条线程按照某种交替进行。
Object 提供了如下方法:
— wait() :让当前正在执行的线程“等待”,进入阻塞状态,同时会释放对同步监视器的锁定;
— motify() :唤醒处于“等待”的线程,让其进入就绪状态;
— motifyAll() :唤醒处于“等待”的所有线程,让其进入就绪状态;
★ 以上3种方法,表面上属于Object类,任何方法都可以调用,但实际是上只有同步器监听器才能调用。
7. 线程组
—— Java使用ThreadGroup代表线程组,在创建一个Thread实例时,通过传入的ThreadGroup对象,即可将该线程放入指定的线程组。Java通过线程组来对一批线程进行整体的管理。
—— JDK 1.5 之前,如果 线程出现了异常,系统会自动回调其所在的线程组的uncaughtException()方法来修复异常;在JDK1.5之后,线程允许自行设置“异常处理器”,无需线程组:
Thread.setDefaultUncaughtExceptionHandler 为所有线程(包括主线程)设置默认的异常处理器
实例.setUncaughtExceptionHandler 为当前线程实例设置异常处理器
★ 实例设置的异常处理器会覆盖默认的异常处理器
8. 线程池(Pool)
—— 池的本质就是一种“缓存”技术,是否缓存一个对象,取决于该对象的创建成本,同时还要考虑到系统的内存大小;
缓存的本质是牺牲空间换取时间,线程对象的创建成本相对普通的Java对象依然较大,但是比创建进程的成本小。为了解决这个问题,就要用到线程池。
—— Java对线程池的良好支持:
Executors —创建线程池、线程工厂的工具类
ExecutorService — 代表线程池
ScheduledExecutorService — ExecutorService 的子接口,可以周期性的调度任务
—— 线程池的编程步骤:
① 通过 Executors 的静态工厂方法创ExecutorService 或 ScheduledExecutorService
② 调用 ExecutorService 的方法提交线程即可
③ 调用线程池的showdown() 关闭线程池
相关文章
- 暂无相关文章
用户点评