说明:这是2007年复习SCJP期间的学习笔记(JavaSE 5.0),有部分遗失。现在整理一下发到Blog上,一方面做个备份,另一方面分享出来,希望对需要的人有用。
------------------------
第九章 线程
1、线程的执行是一个单独的进程,它有自己的调用栈。在Java中,每个调用栈有一个线程。启动整个程序的main()方法运行在一个线程内,它被称作主线程。
2、一旦创建新线程就产生一个新栈,从那个线程调用的方法运行在一个与main()调用栈分开的调用栈内。
3、不同的JVM能以不同的方式运行线程。
4、所有操作都是从执行run()方法开始,run()方法内需要编写需要在独立线程内执行的代码。
5、实例化线程:扩展java.lang.Thread,重写run方法。
实现Runnalbe接口,重写run方法。
可以在Thread子类中自由重载run方法:
public void run(){//
}
public void run(String s){ //
}
执行将不会用不同的调用堆栈在一个新的执行线程中发生,它只发生在于你发出调用代码相同的调用堆栈中。
6、如果实现Runnalbe,为了使代码被独立的线程运行,还需要一个Thread实例:
MyRunnable r=new MyRunnable(); //传递给Thread构造函数的Runnable称作目标Runnable。
Thread t1=new Thread(r);
Thread t2=new Thread(r);
Thread t3=new Thread(r);
Thread t4=new Thread(r); //把一个目标赋给多个线程意味着几个执行线程将运行完全相同的作业。
7、可以传递一个Thread给另一个Thread构造函数:
Thread t=new Thread(new MyThread());
8、run()方法结束时,线程被认为是死的。isAlive方法用于确定线程是否已经启动但还没有完成它的run方法的方式。
9、使用Thread的getName()方法,让每个Runnable打印出执行Runnable对象的run()方法的线程名称,首先调用静态Thread.currentThread()方法,它返回对当前执行线程的引用:
class NamedThread implements Runnable{
public void run(){
String s = Thread.getcurrentThread().getName();
System.out.println(s);
}
}
10、Java规范中根本没有提到线程将按照它们启动的顺序,也就是每个线程上调用start()的顺序开始运行,不保证一旦某个线程开始执行它将连续执行到它完成为止。
11、启动一个线程,告诉它不要运行直到某个其它线程已经结束为止,使用join()方法实现该操作。
12、如果具有对Thread实例的引用,则即使当该Thread实例不再是一个执行线程时,仍能够调用这个Thread实例上的方法,但是不能做的操作是再次调用start(),否则抛出一个IllegalThreadStateException,该异常是RuntimeException的一种。一旦线程启动,它就不能再次启动。
13、当start()方法调用时,线程首先进入可运行状态。
只有一种方法进入运行状态:调度程序从可运行池选择一个线程。
一个线程不告诉另一个线程阻塞。
suspend()和stop()不应该使用,很危险。
14、sleep()方法是Thread类的一个静态方法,一个线程不能使另一个线程睡眠:
try{
Thread.sleep(5*60*100);
}catch(InterruptedException e){}
15、大多数调度程序使用基于优先级的抢先调度机制,运行线程的优先级将大于或等于池中线程的最高优先级。
设置优先级:
MyRunnable r=new MyRunnable();
Thread t=new Thread(r);
t.setPriority(8); //1-10, 默认是5。
t.start();
16、yield()使当前运行的线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。
不能保证yield()达到让步目的,也无法保证让步线程不会从所有其它线程中被再次选中。
大多数情况下,yield()将导致线程从运行态到可运行态,但也可能没有效果。
17、非静态join()方法让一个线程加入到另一个线程的尾部:
Thread t=new Thread();
t.start(); //另一个线程开始运行。
t.join(); //当前线程等待另一线程完成。
System.out.println(“test”);
18、不能保证单个线程连续完成整个原子操作,但可以保证即使执行原子操作的线程进入和退出运行状态,也没有其他运行线程能够对相同的数据进行操作。
保护数据操作:变量修饰符定义为private,同步那些修改变量的代码,使用Synchronized实现。
19、同步和锁定:Java中每个对象都有一个内置锁,当对象已经同步方法代码时内置锁才起作用。当我们进入同步的非静态方法时,我们自动获得与我们正在执行的代码的类的当前实例有关的锁。
20、只能同步方法,不能同步变量或类。
每个对象只有一个锁。
不必同步类中的所有方法,类可以有同步和非同步方法。
一旦一个线程上获得一个锁,就没有任何其他线程可以进入类中的任何同步方法。
如果类具有同步和非同步方法,则多个线程仍然能够访问该类的非同步方法!
类具有同步和非同步方法,则多个线程仍然能够访问该类的非同步方法。
线程进入睡眠,保持已有的任何锁。
线程可以获得多个锁。
可以同步代码块,而不是方法,如果方法作用域超出所需范围,则可以把同步部分的作用域减小到小于整个方法。
21、当前实例上同步可以使用this:
public void doStuff(){
synchronized(this){
System.out.println(“this”);
}
}
22、可以同步静态方法,每类只需一个锁。Java中装载的每个类都有一个对应代表该类的java.lang.Class实例。就是使用这个实例的锁来保护类的静态方法:
public static synchronized int getCount(int count){
return count;
}
或使用同步块:
public static int getCount(int count){
synchronized(MyClass.class){
return count;
}
}
23、如果使用相同的实例进行调用的话,同一个类中非静态同步方法的线程将彼此阻塞,它们每个都在这个实例上加锁。
如果它们使用两个不同的实例进行调用,将调用两个锁,这两个锁互不干扰。
调用同一个类中的静态方法的线程将始终彼此阻塞(即使是不同对象)—它们都锁定在相同的Class实例上。
静态同步方法和非静态同步方法将永远不会彼此阻塞。
24、每个线程都会获取局部变量的自身副本。
非静态字段中的可更改数据,使用相同实例运行该方法的线程不会被同步访问,使用不
同数据的线程却可以彼此忽略。
静态数据中的可交换数据,确保尝试访问数据的任何两个线程将不会同时访问。
静态字段的访问应该通过静态同步方法完成,访问非静态字段应该通过非静态同步方法完成。
25、StringBuffer中的所有方法在需要时可以被同步,而StringBuilder中的那些方法没有。
26、Object类的方法:wait() \ notify() \ notifyAll()。
27、必须从同步环境中调用wait() \ notify() \ notifyAll(),线程不能调用对象上的等待或通知方法,除非它拥有那个对象锁。
28、每个对象可以有一个线程列表,它们等待来自该对象的通知。线程通过执行对象的wait方法获得这个等待列表。它将不再执行任何其他指令,直到调用目标对象的notify()方法为止:
class ThreadA {
public static void main(String[] s){
ThreadB b=new ThreadB();
b.start();
synchronized(b){ //拥有b对象的锁,以调用wait()方法。
try{
b.wait(); //等待,释放该对象的锁。
}catch(InterruptedException e){
System.out.println(“exc”);
}
}
}
}
class ThreadB {
int total;
public void run(){
synchronized(this){ //获得对象锁,以调用notify()。
for(int i=0;i<100;i++){
total += i;
}
notify(); //通知ThreadA进入可运行态。
}
}
}
29、在对象上调用wait()方法时,执行该代码的线程立即放弃它在对象上的锁。当调用notify()时,并不意味着这时线程会放弃其锁,如果线程仍旧在完成同步代码,则线程在移出同步代码之前不会放弃锁,所以只调用notify()不意味着这时该锁将变得可用。
30、可以使用notifyAll()让所有线程冲出等待区,返回可运行状态。多个线程等待一个对象,使用notify()将只影响其中一个,具体哪个将由JVM决定。
31、放入某种循环中,该循环检查某种条件表达式,并且只有在你正在等待的事情还没有发生的情况下,才继续等待。
32、还有一种称为自发醒来的情形,存在于下列场合:线程可能醒来,即使代码没有调用notify()或notifyAll()。将wait()放入while循环,并且重新检查表示我们正在等待的事情的条件。我们确保不管因为什么原因醒来,当且仅当我们正在等待的事情还没有发生,将重新进入wait()。
33、要点:当使用wait() \ notify() \ notifyAll()时,几乎总有一个while循环包围着wait(),以便检查条件,并强制连续等待,直到条件满足为止。
34、Object类----wait() \ notify() \ notifyAll().
Thread类----start() \ join() \ yield():static \ sleep():static
Runnable类----run().