原文地址:http://chenxiaoqiong.com/articles/thread1/
为什么要用多线程
使用多线程只有一个目的,那就是更好的利用cpu的资源。通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源。
在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位。由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度。
线程的实现
Java 提供了三种创建线程的方法
- 通过继承 Thread 类本身;
- 通过实现 Runnable 接口;
- 通过 Callable 和 Future 创建线程。
继承 Thread 类
将一个类声明为Thread的子类。 这个子类应该重写Thread类的run方法 。 例如:
public class MyThread extends Thread {
@Override
public void run() {
// 这里写线程需要完成的任务
System.out.println(Thread.currentThread().getId());
}
}
其中run()方法的方法体代表了线程需要完成的任务,称之为线程执行体。当创建此线程类对象时一个新的线程得以创建,并进入到线程新建状态。以下代码将创建一个线程并启动它运行:
Thread t1 = new MyThread();
t1.start();
通过调用线程对象引用的start()方法启动线程。start()方法使得该线程进入到就绪状态,此时此线程并不一定会马上得以执行,这取决于CPU调度时机。
注意: 不是调用run()方法启动线程,run方法中只是定义需要执行的任务,如果调用run方法,即相当于在主线程中执行run方法,跟普通的方法调用没有任何区别,此时并不会创建一个新的线程来执行定义的任务。
实现 Runnable 接口
创建一个类声明实现Runnable接口,然后这个类必须实现run方法。 在创建Thread时作为参数传递,并启动。例如:
public class MyRunnable implements Runnable {
@Override
public void run() {
// 这里写线程需要完成的任务
System.out.println(Thread.currentThread().getId());
}
}
以下代码将创建一个线程并启动它运行:
MyRunnable myRunnable =new MyRunnable();
Thread t2 = new Thread(myRunnable);
t2.start();
注意: 事实上,查看Thread类的实现源代码会发现Thread类是实现了Runnable接口的。在Java中,这2种方式都可以用来创建线程去执行子任务,具体选择哪一种方式要看自己的需求。直接继承Thread类的话,可能比实现Runnable接口看起来更加简洁,但是由于Java只允许单继承,所以如果自定义类需要继承其他类,则只能选择实现Runnable接口。
通过 Callable 和 Future 创建线程(实现有返回结果的多线程)
创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
class MyCallable implements Callable<T> {
...
public T call() throws Exception {
// 这里写线程需要完成的任务
}
}
创建 Callable 实现类的实例,两种方式启动线程并获得返回值:Java多线程 -(Callable 和 Future实现有返回值的线程)
线程状态(生命周期)
线程是一个动态执行的过程,它也有一个从产生到死亡的过程。下图显示了一个线程完整的生命周期:
新建状态(New): 当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
就绪状态(Runnable): 当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,等待JVM里线程调度器的调度。
运行状态(Running): 就绪状态的线程获取了CPU使用权,执行程序代码。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
阻塞状态(Blocked): 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
- 调用join()和sleep()方法。sleep()时间结束或被打断,join()中断,IO完成都会回到Runnable状态,等待JVM的调度。
- 调用wait(),使该线程处于等待池(wait blocked pool),直到notify()/notifyAll();线程被唤醒被放到锁定池(lock blocked pool)。释放同步锁使线程回到可运行状态。
- 对Running状态的线程加同步锁(Synchronized)使其进入(lock blocked pool )。同步锁被释放进入可运行状态(Runnable)。
死亡(Dead): 一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
概念
进程:进程是一个执行中的程序,一个进程包括由操作系统分配的内存空间,包含一个或多个线程。进程间的切换会有较大的开销。(进程是资源分配的最小单位)
线程:线程是进程的一部分,它不能独立的存在。同一类线程共享进程所拥有的资源,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位)
多线程:指的是这个程序(一个进程)运行时产生了不止一个线程。(用多线程只有一个目的,那就是更好的利用cpu的资源)
并行与并发:
并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源。
线程安全:线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
*经常用来描绘一段代码。指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果。这个时候使用多线程,我们只需要关注系统的内存,cpu是不是够用即可。反过来,线程不安全就意味着线程的调度顺序会影响最终结果,如不加事务的转账代码:
void transferMoney(User from, User to, float amount){
to.setMoney(to.getBalance() + amount);
from.setMoney(from.getBalance() - amount);
}*
同步:Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。如上面的代码简单加入@synchronized关键字。在保证结果准确的同时,提高性能,才是优秀的程序。线程安全的优先级高于性能。