1. 创建线程有两种方式:
1)扩展java.lang.Thread类
public class ThreadTest extends Thread{ }
2)实现Runnable接口
public class ThreadTest implements Runnable{ }
2. Thread类代表线程类,有两个最主要的方法:
1)run() 包含线程运行时所执行的代码。
用户的线程类只需要继承Thread类,覆盖Thread类的run()方法即可。在Thread类中的run()方法没有招聘任何异常,所以Thread子类的run()方法也不能声明招聘任何异常。
2)start() 用于启动线程。
3.线程的运行过程:
1)主线程与用户自定义的线程并发运行
2)多个线程共享一个对象的实例变量
3)不要随便覆盖Thread类的start()方法,假如一定要覆盖,就在重新定义的start()方法首语句中调用super.start()方法。
4)一个线程只能被启动一次
4.线程的状态转换:
1)新建状态
用new语句创建的线程对象处于新建状态,此时它和其他Java对象一样,仅仅在堆区中被分配了内存。
2)就绪状态
当一个线程对象创建后,其他线程调用它的start()方法,该线程就进入就绪状态,JVM会为它创建方法调用栈和程序计数器。处于这个状态的线程位于可运行池中,等待获得CPU的使用权。
3)运行状态
处于这个状态的线程占用CPU,执行程序代码。在并发运行环境中,如果计算机只有一个CPU,那么任何时刻只会有一个线程处于这个状态。如果计算机有多个CPU,那么同一时刻可以让几个线程占用不同的CPU,使它们都处于运行状态。只有牌就绪状态的线程才有机会转到运行状态。
4)阻塞状态
阻塞状态是指线程因为某些原因放弃CPU,暂时停止运行。当线程处于阻塞状态时,JVM不会给线程分配CPU,直到线程重新进入就绪状态,它才有机会转到运行状态。
阻塞状态可分为以下3种
1))位于对象等等池中的阻塞状态:当线程牌运行状态时,如果执行了某个对象的wait()方法,JVM就会把线程放到这个对象的等待池中。
2))位于对象锁池中的阻塞状态:当线程牌运行状态,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他线程占用,JVM就会把这个线程放到这个对旬的锁池中。
3))其他阻塞状态:当前线程执行了sleep()方法,或者调用了其他线程的join()方法,或者发出了I/O请求时,就会进入这个状态。
当一个线程执行System.out.println()或者System.in.read()方法时,就会发出一个I/O请求,该线程放弃CPU,进入阻塞状态,直到I/O处理完毕,该线程才会恢复运行。
5)死亡状态
当线程退出run()方法时,就进入死亡状态,该线程结束生命周期。线程有可能是正常执行完run()方法而退出,也有可能是遇到异常而退出。不管线程正常结束还是异常结束,都不会对其他其他线程造成影响。
Thread类的isAlive()方法判断一个线程是否活着,当线程处于死亡状态或者新建状态时,该方法返回false,在其余状态下,该方法返回true.
5.线程的调度:
有两种调度方式:分时调度模型和抢占式调度模型。
分时调度模型是指让所有线程轮流获得CPU的使用权,并且平均分配每个线程占用CPU的时间片。
Java虚拟机采用抢占式高度模型,是指优先让可运行池中优先级高的线程占用CPU,如果可运行池中线程的优先级相同,那么就随机选择一个线程,使其占用CPU。处于运行状态的线程会一直运行,直至它不得不放弃CPU。
线程的调度不是跨平台的,它不仅取决于JVM,还依赖于操作系统。
6.Thread类的setPriority(int)和getPriority()方法分别用来设置优先级和读取优先级。
1)MAX_PRIORITY:取值为10,表示最高优先级
2)MIN_PRIORITY:取值为1,表示最低优先级
3)NORM_PRIORITY:取值为5,表示默认的优先级
如果希望程序能移植到各个操作系统中,应该确保在设置线程的优先级时,只使用MAX_PRIORITY、MIN_PRIORITY、NORM_PRIORITY这三个优先级。这样才能保证在不同的操作系统中,对同样优先级的线程采用同样的调度方式。
7.sleep()方法与yield()方法的区别:
1)sleep()方法会给其他线程运行的机会,而不考虑其他线程的优先级,因此会给较低优先级线程一个运行的机会;yield()方法只会给相同优先级或者更高优先级的线程一个运行的机会。
2)当线程执行了sleep()方法后,将转到阻塞状态;当线程执行了yield()方法后,将转到就绪状态。
3)sleep()方法声明抛出InterruptedException异常,而yield()方法没有声明招聘任何异常。
4)sleep()方法比yield()方法具有更好的可移植性。
8.等待其他线程结束:join()方法
当前运行的线程可以调用另一个线程的join()方法,当前运行的线程将转到阻塞状态,直至另一个线程运行结束,它才会恢复运行(确切的意思是指线程从阻塞状态转到就绪状态)。
9.定时器Timer
Timer timer=new Timer(true);
TimerTask task=new TimerTask(){ }
timer.schedule(task,10,500);//定时器将在10毫秒后开始执行task任务,以后每隔//500毫秒重复执行一次task任务。
timer.schedule(task,10);//只执行一次task任务。
10.同步代码块
为了保证每个线程能正常执行原子操作,Java引入了同步机制,具体做法是在代表原子操作的程序代码前加上synchronized标记,这样的代码被称为同步代码块。
有两种方式加代码锁:
1) 直接在方法前加synchronized标记,
public synchronized void add(){... }
2) 在方法内形成synchronized标记的代码块
public void add(){synchronized(this){... }}
取得对象锁:
1) 假如这个锁已经被其他线程占用,JVM就会把这个消费者线程放到Stack对象的锁池中,消费者线程进入阻塞状态。在Stack对象的锁池中可能会有许多等待锁的线程。等到其他线程释放了锁,JVM会从锁池中随机取出一个线程,使这个线程拥有锁,并且转到就绪状态。
2) 假如这个锁没有被其他线程占用,消费者线程就会获得这把锁,开始执行同步代码块。在一般情况下,消费者线程只有执行完同步代码块,才会释放锁,使得其他线程能够获得锁。
释放对象锁:
1) 执行完同步代码块,就会释放锁。
2) 在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放。
3) 在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放锁,进入对象的等待池。
4) 在执行同步代码块的过程中,执行了锁所属对象的notify()方法后,JVM会从对象的等待池中随机选择一个线程,把它转到对象的锁池中。
线程同步的特征:
1) 如果一个同步代码块和非同步代码块同时操纵共享资源,仍然会造成对共享资源的竞争。因为当一个线程执行一个对象的同步代码块时,其他线程仍然可以执行对象的非同步代码块。
2) 每一个对象都有惟一的同步锁。
3) 在静态方法前面也可以使用synchronized修饰符。
4) 当一个线程开始执行同步代码块时,并不意味着必须以不中断的方式运行。进入同步代码块中的线程也可以执行Thread.sleep()或者执行Thread.yield()方法,此时它并没有释放锁,只是把运行机会(即CPU)让给了其他的线程。
5) synchronized声明不会被继承.
11.死锁:
当一个线程等等由另一个线程持有的锁,而后者正在等待已被第一个线程持有的锁时,就会发生死锁。JVM不监测也不试图避免这种情况,因此保证不发生死锁就成了程序员的责任。