线程:是程序内部的顺序控制流。
Java线程:通过java.lang.Thread类来实现。
一、实现线程方法:
1、新建一个Thread对象
2、新建一个Thread类的子类对象
3、新建一个实现Runnable接口的对象
二、Java VM的主线程:VM启动一个主线程(由public static void main(String[] args){}定义)。
多线程:在操作系统中同时运行几个线程(顺序流)。即程序运行期间多个进程(主方法、其他进程实现方法的实例对象)之间共享CPU。
三、线程的状态:
1、创建:创建线程对象。
2、start:调用线程对象的该方法,则该线程进入就绪状态
3、就绪:就绪状态的线程,等待CPU的调度,调度后执行。
4、运行:就绪状态获取CPU资源,线程就可以执行
5、阻塞:阻塞事件发生触发线程从运行状态进入阻塞状态。当阻塞解除后线程进入就绪状态。阻塞事件例同步方法中调用this.wait()方法,阻塞当前锁定该对象的线程。阻塞解除事件如notify/notifyAll来唤醒wait进程。注wait方法在wait pool中不占用锁,唤醒后重新占用锁,而sleep在休眠时也占用锁。
6、终止:销毁线程。原来的JDK中使用stop方法来销毁线程。在JDK6中已经停用了改方法。现在一般处理方法是要终止线程,即线程的run方法运行完毕。可设置一个boolean flag属性来控制run方法的执行,当且仅当flag为true,run方法内部才执行。同时,可以定义一个shutDown方法来利用修改flag为false来终止线程的运行。
如:定义一个线程类
class TreadStop implements Runnable {
private boolean flag=true;
public void run() {
int i = 0;
while (flag==true) {
System.out.print("Thread of ThreadStop: " + i++);
}
}
public void shutDown() {
flag = false;
}
}
在调用该线程类的一个实例时:使用实例调用shutDown方法类终止线程
public class TestThreadStop{
public static void main(String args[]){
ThreadStop t = new ThreadStop();
t.start();
for(int i=0;i<100000;i++){
if(i%10000==0 & i>0)
System.out.println("in thread main i=" + i);
}
System.out.println("Thread main is over");
t.shutDown();
}
}
四、线程状态控制方法:
方法名 | 控制说明 |
isAlive | 线程是否启动并未终止 |
getPriority() | 获取线程的调度优先级数值。优先级高的占用CPU时间片多。 |
setPriority() | 设置线程的调度优先级数值。数值为1-10,默认为5。 |
Thread.sleep() | 当前线程睡眠的毫秒数 |
join() | 调用某个线程的该方法,将当前进程和该进程合并,直到该进程终止,再恢复当前进程的运行 相当于方法调用 |
yeild | 让出CPU,进入就绪状态等待调度 |
wait | 进入对象的wait pool |
notify/notifyAll | 唤醒对象的wait pool的一个或全部线程 |
注;wait和sleep的区别:
wait是别的线程可以访问锁定对象,且调用wait方法时必须锁定该对象。而sleep时别的线程也不可以访问锁定对象。
五、线程同步:
在Java中,用对象互斥锁来保证共享数据的完整性。每个对象都对应一个“互斥锁”标记。这个标记保证在任意时刻,只有一个线程在访问该对象。Java中,互斥锁标记用Synchronized关键字来说明。当某个对象用Synchronized修饰时表示任一时刻只有一个线程可以访问该对象,这样保证了数据的完整性。同样,Synchronized可以用于修饰方法,说明该方法是同步方法(方法执行时当前对象被锁定)。
synchronized (this){ //执行语句块时当前对象被锁定 }
同理同步方法为:
public synchronized void methodName(){ //执行该方法时当前对象被锁定 }
否则,在多个线程运行时不能保证原来一个语句块的原子性:
例:我们希望在add方法中输出该方法是被第几次访问。
public static int num=0;
public void add(){
num++;
System.out.println("这是第 "+num+"次调用该方法");
}
在进程1中:在num++;和System.out.println();之间若没有锁定当前对象的话,其他进程(进程2)打断当前进程(进程1)的执行,执行了add方法中的num++,此时num值为2。然后回到原来的进程1 执行,输出进程1是第2次调用该方法。然后回到进程2,输出这是第二次调用该方法。这就导致执行结果和我们预期的不一致。
这就是为什么要用synchronized关键字来锁定当前对象。若锁定在方法add中当前对象或使用synchronized(this){}将要原子执行的语句放在花括号中也可以。
之所以上面问题的解决方案可有:
1、用synchronized(this){}锁定当前对象
public void add(){
synchronized(this){
num++;
System.out.println("这是第 "+num+"次调用该方法");
}
}
2、用synchronized关键字修饰add方法使在add方法内锁定当前对象
public synchronized void add(){
num++;
System.out.println("这是第 "+num+"次调用该方法");
}
注:当一个线程类中有同步方法和非同步方法时,要仔细考虑可以访问关注对象的所有方法是否加锁。对于同步方法,只能有一个线程独占执行完成后释放。而非同步方法,可以由多个线程交叉访问。
对于一个对象,我们对对象"写"的方法需要设置为同步方法,这是因为多个同步方法都要锁定对象,所以,只有一个线程在一段时间内"写"该对象。"读"方法可以不用设置为同步(效率高)。