首先了解一下概念:
进程是系统进行资源分配和调度的一个独立单位,进程包含以下3个特征:
- 独立性:拥有自己的资源与空间
- 动态性:动态执行的过程
- 并发性:可以运行多个进程在一个处理器上,多个进程之间相互不影响
多线程扩展了多进程的概念,使得同一个进程可以同时并发处理多个任务。线程也被称为轻量级的进程。每个进程启动都会启动一个主线程,也可以创建多个辅助的线程。线程是进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程。线程可以拥有自己的堆栈,自己的程序计数器和自己的局部变量,但不拥有系统资源,它与父进程的其他线程共享该进程的所有资源。线程是独立运行的,也是抢占式,一个线程可以创建和撤销另一个线程。
线程的创建和启动
Thread类代表线程,所有的线程都是Thread类的子类或者实例。
- 继承Thread类创建线程类
- 定义Thread类的子类,并重写该类的run方法,该run方法就代表了线程需要完成的任务。
- 创建Thread子类的实例。
- 调用对象的start方法来启动线程。
public class Thread1 extends Thread{
public void run() {
System.out.println(getName() + " hello");
}
public static void main(String[] args) {
new Thread1().start();
}
}
- Thread.currentThread() currentThread()是Thread类的静态方法,该方法总是返回当前正在执行的线程对象。
- getName() 该方法是Thread类的实例方法,该方法返回调用该方法的线程名字。
- 程序也可以通过使用setName(String name)方法为线程设置名字。
- 实现Runnable接口创建线程类
- 定义Runnable接口的实现类,并重写run方法
- 创建Runnable实现类的实例,并把该对象作为参数传给Thread创建Thread对象(如果启动多个线程,可以同时使用同一个Runnable实例)
- 调用Thread对象的start()方法来启动线程。
public class Thread2 implements Runnable{
public void run() {
System.out.println(Thread.currentThread().getName() + " hello");
}
public static void main(String[] args) {
Thread2 thread2 = new Thread2();
new Thread(thread2).start();
new Thread(thread2).start();
}
}
* 使用Callable 和 Future 创建线程
* Java 5开始,Java提供了Callable接口,该接口提供了一个call方法可以作为线程执行体。但是call方法比run方法功能更加强大。 * call方法可以有返回值 * call方法可以声明抛出异常
* 创建并启动有返回值的线程步骤如下:
* 创建Callable接口的实现类,并实现call方法,该call方法将作为线程执行体,且该call方法有返回值
* 创建callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call方法的返回值。
* 使用FutureTask对象作为Thread对象的target创建并茄冬新线程
* 调用FutureTask对象的get方法来获得子线程执行结束后的返回值
```
public class Thread3 implements Callable{
public static void main(String[] args) {
Thread3 thread3 = new Thread3();
FutureTask task = new FutureTask(thread3);
new Thread(task).start();
}
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName());
return 10;
}
}
```
# 线程的生命周期
线程的生命周期中包括: 新建、就绪、运行、阻塞和死亡。 new Thread/start/…/…/…
线程死亡有以下三种状态:
* run()或call()方法执行完成,线程正常结束
* 线程抛出一个未捕获的Exception 或 Error
* 直接调用线程的stop方法来结束该线程,该方法容易导致死锁,通常不推荐
不要对处于死亡状态的线程调用start方法,程序只能对新建状态的线程调用start方法,对新建的线程两次调用start方法也是错误的,这都会引发IllegalThreadStateExcpetion异常。
# 控制线程
join线程,Thread提供了让一个线程等待另一个线程完成的方法–join方法。当在某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞。知道被join方法加入的join线程执行完为止。
public class Thread4 extends Thread {
public Thread4(String name) {
super(name);
}
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + " " + i);
}
}
public static void main(String[] args) throws InterruptedException {
// 启动子进程
new Thread4("new thread").start();
for (int i = 0; i < 100; i++) {
if (i == 20) {
Thread4 th = new Thread4("joined thread");
th.start();
th.join();
}
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
* 上面程序一共有三个线程,主线程调用了th.join(),则表示主线程要等待th线程执行完之后才开始执行main线程。
* join方法还存在另一种为: join(long millis) 等待join的线程的时间最长为millis毫秒。如果在millis毫秒内被join的线程还没有执行结束,则不再等待。
# 线程同步
为了解决多个线程针对同一代码的调用而引起的操作异常,可以使用同步监视器来解决这个问题,使用同步监视器的通用方法就是同步代码块,同步代码块的语法格式如下:
sychronized(obj) {
}
任何时刻只有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对该同步监视器的锁定。
虽然可以是使用任何对象作为同步监视器,但是通常推荐使用可能被并发访问的共享资源充当同步监视器。
与同步代码块对应的是,Java多线程安全还支持提供同步方法,同步方法就是使用sychronized关键字来修饰某个方法,则该方法称为同步方法。对于同步方法而言无需显示指定同步监视器,同步方法的同步监视器就是this,也就是该对象本身。使用方法如下:
public sychronized void draw() {
}
sychronized 关键字可以修饰方法,可以修饰代码块,但不能修饰构造器和属性。
JDK提供的StringBuilder 和 StringBuffer两个类,前者就是线程不安全的,后者是线程安全类。前者的性能和效率高些,后者性能和效率低些。
* 在当前线程调用wait方法后,该线程会释放同步监视器
* 在当前线程调用Thread.sleep() 和 Thread.yield()方法来暂停当前线程的执行是不会释放同步监视器。
* 线程执行同步代码块时,其他线程调用了该线程的suspend方法将该线程挂起,该线程不会释放同步监视器。
**使用同步锁(Lock)来同步线程:**
Lock 提供了比sychronized方法和synchronized代码块更为广泛的锁定操作,Lock实现允许更加灵活的结构。它提供了ReadWriteLock(读写锁)和Lock两个根接口,并为Lock提供了ReentrantLock(可重入锁 )实现类。为ReadWriteLock提供了ReentrantReadWriteLock实现类。实现代码如下:
class X {
private final ReentrantLock lock = new ReentrantLock();
public void m() {
// 加锁
lock.lock();
try {
....
}
// 使用finally 块来保证释放锁
finally {
lock.unlock();
}
}
}
当两个线程相互等待对方释放同步监视器就会发生死锁,Java虚拟机没有监测,也没有采取措施处理死锁情况。所以多线程编程时应该采取措施避免死锁的出现。
# 线程通信
程序是无法控制线程的调度的,如果要实现两个线程同时交替执行,不能一个线程连续执行两次,这种线程同步方式,可以借助于Object类提供的wait() notify() 和 notifyAll() 3个方法,这三个方法是属于Object类自带方法,但是这三个方法必须由同步监视器对象来调用。
* 对于使用sychronized修饰的同步方法,this就是同步监视器,所以可以在同步方法中直接调用这3个方法
* 对于使用sychronized修饰的同步代码块,同步监视器是synchronized后括号中的对象,由这个对象来调用这三个同步方法。
关于这三个方法的解释如下:
* wait(): 导致线程等待,有三种形式:一种是无时间参数的wait,一种是带毫秒的,另一种是带微妙的。
* notify(): 唤醒在此同步监视器上等待的单个线程。如果所有线程都在此同步监视器上等待,则会选择其中的一个,这个是随机选择的。
* nofifyAll():唤醒。。。。。。。。。。所有线程。
如果使用Lock来同步代码,这样的话没有同步监视器,这样java提供了一个Condition,Lock替代了同步方法或同步代码块,Condition替代了同步监视器的功能。
Condition实例被绑定在一个Lock对象上,要获得特定Lock实例的Condition实例,调用Lock对象的newCondition()方法即可。提供了如下三个方法:
* await() – > wait
* signal() – > notify()
* signalAll() – > notifyAll()
# 线程池
系统启动一个新线程的成本很高,在这种情形下,需要使用线程池来提高性能。
Java 5新增了一个Executors工厂类来产生线程池,该工厂类包含如下的几个方法:
* newCachedThreadPool(),创建一个具有缓存功能的线程池,系统根据需要创建线程的个数,这些线程将会被缓存在线程池中
* newFixedThreadPool(int nThread), 创建一个可重用的,具有固定线程数的线程池。
* newSingleThreadPool()创建一个只具有单线程的线程池,它相当于调用newFixedThreadPool(1),传入的参数为1
* newScheduledThreadPool(int corePoolSize)创建具有指定线程数的线程池,它可以在指定延迟后执行线程任务。
前面三个方法是返回一个ExecutorService的对象,该对象提供了submit的方法来提交一个线程。
class MyThread implements Runnable {
public void run() {
....
}
}
public class ThreadPoolTest {
public stati void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(6);
pool.submit(new MyThread());
pool.submit(new MyThread());
pool.shutdown();
}
}
---