Java中线程共有5中状态:
- 新建:当创建一个线程对象时,新线程对象就处于新建状态,并获得除CPU外所需的资源。
- 就绪:当处于新建状态的线程被启动后,将进入线程队列等待CPU资源。这时它就具备了运行条件,一旦获得CPU资源,就可以脱离创建它的主线程独立运行。另外原来处于阻塞状态的线程结束阻塞状态后,也进入就绪状态。
- 运行:当一个就绪状态的线程获得CPU资源时,就进入了运行状态。每个线程对象都有一个run方法,一旦线程开始运行,就会自动运行该方法。run()中定义了具体的线程操作。
- 阻塞:一个正在运行的线程因为某种特殊的原因,比如遇到优先级高的线程、某种资源无法满足时就会让出CPU并停止自身的运行,进入阻塞状态。只有当引起阻塞的原因消除时,它才能重新进入就绪状态。
- 死亡:不具备继续运行能力的线程处于死亡状态。一般由两种情况引起:run()方法运行完毕;其他线程强制终止它。
就绪队列:处于就绪状态的线程都在就绪队列中等待CPU资源,而一般就绪队列中会有多个线程。为此,系统会给每一个线程分配一个优先级,优先级高的可以排在较前面的位置,能优先得到CPU资源。对于优先级相同的线程,一般按照先来先服务的原则调度。
进程和线程最大的区别在于进程是由操作系统来控制的,而线程是由进程来控制的,所以是由程序员来控制的。进程都是相互独立的,各自享有各自的内存空间。而一个进程中的多个线程是共享内存空间的,他们可以访问相同的变量和对象。本来这样的设计方便了线程间的通讯,但却带来了新的问题:多个线程同时访问一个变量可能出现意想不到的错误,如死锁。所以多线程操作要注意协调与配合,进行互斥和同步处理。
- 互斥:当多个线程需要访问同一资源时,而这一资源在某一时刻只允许一个线程访问,那么这些线程就是互斥。如线程A需要读取变量count,而线程B会给count赋值,那边A和B是互斥的。
- 同步:多个线程需要访问同一资源,而且需要相互配合才能正常工作,这些线程运行时就是一种同步关系。例如,A线程需要从缓冲区中读取数据,而B线程需要向缓冲区中写数据,但缓冲区的大小是固定的。
- 临界区:为了实现线程间的互斥和同步,需要将共享资源放入一个区域,该区域一次只允许一个线程进入,该区域就被称为临界区域。线程在访问共享资源前需要进行检查,看自己是否有权访问资源,如果有权访问还要阻止其他线程进入该区域。该代码段就是临界区。
- 死锁:若多个线程相互等待其他线程释放资源,而所有线程又都不释放自己所占的资源,从而导致相关线程处于永远等待的状态,这种现象称为线程死锁。
线程通讯
线程间互斥和同步必须使用信号量,Java中信号量需要用户自己管理,系统只提供了起到PV原语作用的方法:
- void notify():唤醒在此对象监视器上等待的某一个线程.具体是哪一个可以认为是不确定的.
- void notifyAll():唤醒在此对象监视器上等待的所有线程。
- void wait():导致当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll()方法.
wait会释放占有的资源(锁),notify和notifyAll不会释放占用的资源(锁).而线程睡眠(sleep)时,也都不会释放资源(锁)。
除了三个方法,还有一个常用的关键字:synchronized.
凡是被synchronized标志的资源,都是临界区,在同一时刻只能有一个线程能够进入该临界区。synchronized可以用于数据、方法,甚至是一段代码。synchronized也被称为“对象锁”,而且上面的三个方法都只能使用在由synchronized标志的代码块中,否则会发生如下异常:
java.lang.IllegalMonitorStateException: current thread not owner
synchronized是和一个锁对象关联的,一个对象只有一个锁。当程序运行到synchronized同步方法或代码块时就会检查锁,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。
如myThread.java文件代码:
public class myThread extends Thread { private static int count=0; private static Object o=new Object();//定义一个Object对象,用于锁 @Override public void run() { synchronized(o){ for(int i=0;i<20;i++){ count+=1; System.out.println("My name is "+this.getName()+" count= "+count); try { sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } } public myThread(String arg0) { super(arg0); } }
启动线程代码如下:
myThread tf=new myThread("first"); myThread ts=new myThread("secod"); tf.start(); ts.start();
执行的结果:
My name is secod count= 1 My name is secod count= 2 ..... ..... My name is secod count= 20 My name is first count= 21 My name is first count= 22 My name is first count= 23 ..... ..... My name is first count= 40
myThread.java文件中的for循环使用synchronized标志,是临界区,第一次进入的是secod线程,知道执行完毕释放锁,first线程才进入临界区。
synchronized可以用于方法、数据,甚至是代码块:
- synchronized限制static方法的时候,锁对象为这个类的class变量,相当于XXXClass.class. 例如:
public static synchronized int setName(String name){ Xxx.name = name; }
public static int setName(String name){ synchronized(Xxx.class){ Xxx.name = name; } }
- synchronized限制非static方法的时候,锁对象为这个类的实例(相当于this).例如:
public synchronized int getX() { return x++; }
public int getX() { synchronized (this) { return x; } }
- synchronized限制一个对象实例的时候,如(synchronized (xlock)),锁对象为指定的这个对象实例,如xlock.
synchronized限制static方法的时候,在某一时刻,同一个虚拟机只能有一个线程正在执行这个static方法,因为锁对象是这个class的XXXClass.class实例,一个虚拟机只有一个XXXClass.class实例
如果把myThread.java文件代码改成如下:
public class myThread extends Thread { private static int count=0; private static Object o=new Object();//定义一个Object对象,用于锁 @Override public synchronized void run() { for(int i=0;i<20;i++){ count+=1; System.out.println("My name is "+this.getName()+" count= "+count); try { sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } public myThread(String arg0) { super(arg0); } }
执行的结果:
My name is first count= 1 My name is secod count= 2 My name is secod count= 3 My name is first count= 4 My name is first count= 5 My name is secod count= 6 My name is secod count= 7 ..... ..... My name is secod count= 34 My name is first count= 35 My name is secod count= 36 My name is secod count= 37 My name is first count= 38 My name is first count= 39 My name is secod count= 40
可见是交叉执行的,那是因为synchronized限制某一个类的非static方法的时候,对这个类的某一特定实例,在某一时刻,同一个虚拟机只能有一个线程正在执行这个方法,但是可以同时执行多个实例的这个特定方法,因为锁对象不同.