一. 从进程到线程
进程是指运行中的应用程序,每一个进程都有自己独立的内存空间。一个应用程序可以同时启动多个进程。
线程是指进程中的一个执行流程。一个进程可以由多个线程组成,在一个进程中可以同时运行多个不同的线程,它们分别执行不同的任务。
当进程内的多个线程同时运行时,这种运行方式称为并发运行。
线程与进程的区别:每个进程都需要操作系统为其分配独立的内存地址空间,而同一进程中的所有线程在同一块地址空间中工作,这些线程可以共享同一块内存和系统资源。
二. java中的线程
在java虚拟机进程中,执行程序代码的任务是由线程来完成的。每当用java命令启动一个Java虚拟机进程时,Java虚拟机都 会创建一个主线程。该线程从程序入口main()方法开始执行。
java中可以把线程分为前台线程(执行线程)、后台线程(守护线程)。
三. 线程的创建和启动
Java虚拟机的主线程从启动类的main()方法开始运行。此外,用户还可以创建自己的线程,它将和主线程并发运行。创建线程有两种方式:
. 继承java.lang.Thread类; //extends
. 实现Runnable接口;//implements
1. 继承java.lang.Thread类
1)重写Thread类的run()方法;//public void run(){}——包含线程运行时所执行的代码;
2)在main()方法中调用start()方法启动线程,一个线程只能被启动一次。
------------------------------------------------------------------------------------------
例:
public class ThreadTest extends Thread {
public void run(){
System.out.println("thread");
}
public static void main(String[] args) {
ThreadTest t=new ThreadTest();
t.start();
}
}
------------------------------------------------------------------------------------------
2. 实现Runnable接口
Java只能继承一个类,如果继承了Thread类,就不能继承其他类了。因此可以通过实现java.lang.Runnable接口避免此问题。
1)实现Runnable接口
2)创建Thread对象,传入要启动的线程类
3)调用Thread对象的start()方法 //接口中没有start()方法,不能直接启动
------------------------------------------------------------------------------------------
例:
public class ThreadTest implements Runnable {
public void run(){
System.out.println("thread");
}
public static void main(String[] args) {
ThreadTest tt=new ThreadTest();
Thread td=new Thread(tt);
td.start();
}
}
------------------------------------------------------------------------------------------
四. 线程状态
线程的五种状态;
1. 新建(New)——用new刚创建出的线程对象,只被分配了内存
2. 就绪(Runnable)——调用start()方法后,位于可运行池,等待CPU的使用权。
3. 运行(Running)——占用CPU,执行程序代码。
4. 阻塞(Blocked)——线程因为某些原因放弃CPU,暂时停止运行。
阻塞状态可分为三种:
1)位于对象等待池中的阻塞状态(Blocked in objects' wait pool): 运行状态时,执行某个对象的wait()方法;
2)位于对象锁池中的阻塞状态(Blocked in object's lock pool): 当线程处于运行状态,试图获得某个对象的同步锁时,如该对象的同步锁已经被其他线程占用,Java虚拟机就会把这个线程放到这个对象的锁池中;
3)其他阻塞状态(Otherwise Blocked): 当前线程执行了sleep()方法,或者调用了其他线程的join()方法,或者发出了I/O
请求时,就会进入这个状态。
线程从阻塞状态只能进入就绪状态,然后才有机会转到运行状态。
5. 死亡(Dead)——线程执行完run()方法或执行期间遇到异常退出run()方法。
五. 线程调度
按照特定的机制为多个线程分配CPU的使用权。
有两种调度模型:
. 分时调度模型:让所有线程轮流获得CPU的使用权,并且平均分配每个线程占用CPU的时间片。
. 抢占式调度模型:优先让可运行池中优先级高的线程较多可能占用CPU(概率高),如果可运行池中线程的优先级相同,那么就随机选择一个线程,使其占用CPU。处于可运行状态的线程会一直运行,直至它不得不放弃CPU。Java虚拟机采用这种。
一个线程会因为以下原因而放弃CPU:
. Java虚拟机让当前线程暂时放弃CPU,转到就绪状态;(interrupt)
. 当前线程因为某些原因而进入阻塞状态; (sleep join)
. 线程运行结束;
线程的调度还依赖于操作系统。在某些操作系统中,只要运行中的线程没有阻塞, 就不会放弃CPU;在某些操作系统中,即使运行中的线程没有遇到阻塞,也会在运行一段时间后放弃CPU,给其他线程运行机会。
常用方法:
1. stop();
可以强制终止一个线程,但从JDK1.2开始废弃了stop()方法。在实际编程中,一般是在受控制的线程中定义一个标志变量,其他线程通过改变标志变量的值,来控制线程的自然终止、暂停及恢复运行。
2. isAlive();
判定某个线程是否是活着的(该线程如果处于可运行状态、运行状态和阻塞状态、对象等待队列和对象的锁池中返回true)
3. Thread.sleep(5000);//参数单位为毫秒
放弃CPU, 转到阻塞状态。当结束睡眠后,首先转到就绪状态,如有其它线程在运行,不一定运行,而是在可运行池中等待获得CPU。
线程在睡眠时如果被中断,就会收到一个InterrupedException异常,线程跳到异常处理代码块。4. void sleepingThread.interrupt():
中断某个线程
5. boolean otherThread.isInterrupted():
测试某个线程是否被中断,与static boolean interrupted()不同,对它的调用不会改变该线程的“中断”状态。
6. public void join();
public void join(long timeout);
挂起当前线程,直至它所调用的线程终止才被运行。
线程A中调用线程B.join(),是使A线程阻塞,B线程开始执行。
谁调用谁阻塞。
六. 线程的同步
多个线程在操纵共享资源——实例变量时,有可能引起共享资源的况争。为了保证每个线程能正常执行操作,保证共享资源能正常访问和修改。Java引入了同步进制,具体做法是在有可能引起共享资源竞争的代码前加上synchronized标记。这样的代码被称为同步代码块。
每个Java对象都有且只有一个同步锁,在任何时刻,最多只允许一个线程拥有这把锁。当一个线程试图执行带有synchronized标记的代码块时,该线程必须首先获得this关键字引用的对象的锁。
如果这个锁已经被其他线程占用,Java虚拟机就会把这个线程放到this指定对象的锁池中,线程进入阻塞状态。在对象的锁池中可能会有许多等待锁的线程。等到其他线程释放了锁,Java虚拟机会从锁池中随机取出一个线程,使这个线程拥有锁,并且转到就绪状态。
假如这个锁没有被其他线程占用,线程就会获得这把锁,开始执行同步代码块。在一般情况下,线程只有执行完同步代码块,才会释放锁,使得其他线程能够获得锁。
如果一个方法中的所有代码都属于同步代码,则可以直接在方法前用synchronized修饰。
public synchronized String pop(){...}
等价于
public String pop(){
synchronized(this){...}
}
线程同步的特征:
1. 如果一个同步代码块和非同步代码块同时操纵共享资源,仍然会造成对共享资源的竞争。
因为当一个线程执行一个对象的同步代码块时,其他线程仍然可以执行对象的非同步代码块。
2. 每个对象都有唯一的同步锁。
3. 在静态方法前面也可以使用synchronized修饰符。此时该同步锁的对象为类对象(类的Class对象)。
4. 当一个线程开始执行同步代码块时,并不意味着必须以不中断的方式运行。进入同步代码块的线程也可以执行
Thread.sleep()或者执行Thread.yield()方法,此时它并没有释放锁,只是把运行机会(即CPU)让给了其他的线程。
5. synchnozied声明不会被继承。
同步是解决共享资源竞争的有效手段。当一个线程已经在操纵共享资源时,其他共享线程只能等待。为了提升并发性能,应该使同步代码块中包含尽可能少的操作,使得一个线程能尽快释放锁,减少其他线程等待锁的时间。
七. 线程的通信
锁对象.wait(): 执行该方法的线程释放对象的锁,Java虚拟机把该线程放到该对象的 等待池中。该线程等待其它线程将它唤醒;
锁对象.notify(): 执行该方法的线程唤醒在对象的等待池中等待的一个线程。Java虚拟机从对象的等待池中随机选择一个线程,把它转到对象的锁池中。如果对象的等待池中没有任何线程,那么notify()方法什么也不做。
锁对象.notifyAll():会把对象的等待池中的所有线程都转到对象的锁池中。
注意:notify notifyAll只会唤醒等待池中等待同一个锁对象的线程,因为同一个时刻在等到池中可能会有多个线程,而这多个线程可能是在等待不同的锁对象。
假如t1线程和t2线程共同操纵一个s对象,这两个线程可以通过s对象的wait()和notify()方法来进行通信。通信流程如下:
1. 当t1线程执行对象s的一个同步代码块时,t1线程持有对象s的锁,t2线程在对象s的锁池中等待;
2. t1线程在同步代码块中执行s.wait()方法, t1释放对象s的锁,进入对象s的等待池;
3. 在对象s的锁池中等待锁的t2线程获得了对象s的锁,执行对象s的另一个同步代码块;
4. t2线程在同步代码块中执行s.notify()方法,Java虚拟机把t1线程从对象s的等待池移到对象s的锁池中,在那里等待获得锁。
5. t2线程执行完同步代码块,释放锁。t1线程获得锁,继续执行同步代码块。
八. 线程的死锁
A线程等待B线程持有的锁,而B线程正在等待A持有的锁;
--------------------------------------------------------------------------------------------------
死锁的例子:
package t;
public class deadlock extends Thread {
public static String x="x";
public static String y="y";
int a;
public deadlock(int a){
this.a=a;
}
public void run(){
if(a==1){
synchronized(x){
System.out.println("1 in x");
//sleep(1000);
synchronized(y){
System.out.println("1 in y");
}
}
}
if(a==2){
synchronized(y){
System.out.println("2 in y");
//sleep(1000);
synchronized(x){
System.out.println("2 in x");
}
}
}
}
public static void main(String[] args) {
deadlock d1=new deadlock(1);
deadlock d2=new deadlock(2);
d1.start();
d2.start();
}
}
//大概率会死锁,也可能不会锁。加上sleep后则一定会死锁,但需要捕获异常InterruptedException e。
--------------------------------------------------------------------------------------------------
九. 线程让步
Thread.yield()静态方法,如果此时具有相同优先级的其他线程处于就绪状态,那么yield()方法将把当前运行的线程放到可运行池中并使另一个线程运行。如果没有相同优先级的可运行线程,则yield()方法什么也不做。
sleep()和yield()方法都是Thread类的静态方法,都会使当前处于运行状态的线程放弃CPU,把运行机会让给别的线程。区别:
. sleep()不考虑其他线程优先级;
yield()只会给相同优先级或者更高优先级的线程一个运行的机会。
. sleep()转到阻塞状态;
yield()转到就绪状态;
. sleep()会抛出InterruptedException异常,
yield()不抛任何异常
. sleep()比yield方法具有更好的可移植性,yield()只在测试时用。
十. 调整线程优先级
注意:优先级高的线程只能获得较多运行的概率,但是实际中不一定真的有效果
Thread类的setPriority(int)和getPriority()方法分别用来设置优先级和读取优先级。优先级用整数来表示,取值范围是1-10,Thread类有以下3个静态常量。
. MAX_PRIORITY: 10, 最高;
. MIN_PRIORITY: 1, 最低;
. NORM_PRIORITY: 5, 默认优先级;
其它:stop(): 中止线程运行; 已过时
resume(): 使暂停线程恢复运行; 已过时
suspend(): 暂停线程,不释放锁; 已过时
释放对象的锁:
. 执行完同步代码块;
. 执行同步代码块过程中,遇到异常而导致线程终止,释放锁;
. 执行同步代码块过程中,执行了锁所属对象的wait()方法,释放锁进入对象的等待池;
线程不释放锁:
. Thread.sleep()方法,放弃CPU,进入阻塞状态;
. Thread.yield()方法,放弃CPU,进入就绪状态;
. suspend()方法,暂停当前线程,已过时;
-------------------------------------------------------------------------------------------------
总结:
*线程Thread调用的常用方法:isAlive,sleep,interrupt,isInterrupted,join,yield。(stop,suspend,resume)
*锁对象synchronized调用的方法:wait,notify,notifyAll。-------------------------------------------------------------------------------------------------