【Thread类的run方法和start方法】
JAVA程序运行时,最开始运行的只能是主线程。所以必须在程序中启动新线程。
启动线程时,要使用如下类(一般称为Thread类)
public class MyThread extends Thread {
public void run(){
for(int i=0;i<100;i++){
System.out.println("Nice!");
}
}
}
新启动的线程的操作都编写在run方法中。新线程启动后会调用run方法。随后,当run方法执行结束时,线程也会跟着终止。
用户启动线程的代码如下
public static void main(String[] args){
MyThread t = new MyThread(); //主线程创建MyThread类的实例
t.start(); //由主线程启动新线程
for(int i=0;i<100;i++){
System.out.println("Good!");
}
}
start方法是Thread类中的方法,用于启动新的线程。
调用start方法后,程序会在后台启动新的线程。然后,由这个新线程调用run方法。
【顺序、并行与并发】
顺序用于表示多个操作 “依次处理”。比如把十个操作交给一个人处理时,这个人要一个一个地按顺序来处理。
并行用于表示多个操作“同时处理”。比如十个操作分给两个人处理时,这两个人就会并行来处理。
并发相对于顺序和并行来说比较抽象,用于表示“将一个操作分割成多个部分并且允许无序处理”。比如将十个操作分成相对独立的两类,这样便能够哦开始并发处理了。
【线程的启动】
启动线程的方法有如下两种:
1 利用Thread类的子类的实例启动线程
2 利用Runnable接口的实现类的实例启动线程
线程的启动一:利用Thread类的子类
new MyThread().start();
线程的启动二:利用Runnable接口
Runnable接口包含在java.lang包中,声明如下
public interface Runnable {
public abstract void run();
}
Runnable接口的实现类必须要实现run方法。
创建Runnable接口的实现类。将实现类的实例作为参数传给Thread的构造函数,调用start方法。
new Thread(new Printer("Good")).start();
(小知识java.util.concurrent.ThreadFactory中的线程创建)
ThreadFactory就是一个线程工厂也就是负责生产线程的。java.util.concurrent中包含一个将线程创建抽象化的ThreadFactory接口。利用该接口,我们可以将以Runnable作为传入参数并通过new创建Thread实例的处理隐藏在ThreadFactory内部。
默认的ThreadFactory对象是通过Executors.defaultThreadFactory方法获取的。
public static void main(String[] args){
ThreadFactory factory = Executors.defaultThreadFactory();
factory.newThread(new Printer()).start();
for(int i=0;i<100;i++){
System.out.println("hello!");
}
}
【线程的暂停】
线程Thread类中的sleep方法能够暂停线程运行,sleep是Thread类的静态方法。
public static void main(String[] args){
for(int i=0;i<100;i++){
System.out.println("hello!");
try{
Thread.sleep(1000);
}catch(InterruptedException e){
}
}
}
【线程的互斥处理】
多线程程序的各个线程都是自由运行的,所以他们有时就会同时操作同一个实例。这在某些情况下会引发问题。
java使用关键字synchronized来执行线程的互斥处理。
【synchronized方法】
如果声明一个方法是,在前面加上关键字synchronized,那么这个方法就 只能由一个线程运行。称为同步方法。
public class Bank {
private int money;
private String name;
public Bank(String name, int money){
this.name = name;
this.money = money;
}
//存款
public synchronized void deposit(int m){
money+=m;
}
//取款
public synchronized boolean withdraw(int m){
if(money>=m) {
money-=m;
return true;//取款成功
} else {
return false;//余额不足
}
}
public String getName(){
return name;
}
}
【线程的协作】
如果有一个线程正在运行synchronized方法,那么其他线程就无法再运行这个方法了。这是简单的互斥处理。
假如我们现在想执行更加精确的控制,而不是单纯地等待其他线程运行终止,例如下面这样的控制:
如果空间为空则写入数据;如果非空则一直等待到变空为止
空间已为空时,通知正在等待的线程。
Java提供了用于执行线程控制的wait方法、notify方法和notifyAll方法。wait是让线程等待的方法,而notify和notifyAll是唤醒等待中的线程的方法。
【等待队列——线程休息室】
所有实例都拥有一个等待队列,它是在实例的wait方法执行后停止操作的线程的队列。
在执行wait方法后,线程便会暂停操作,进入等待队列这个休息室。除非发生下列某一情况,否则线程会一直在等待队列中休眠:
1.有其他线程的notify方法唤醒线程
2.有其他线程的notifyAll方法唤醒线程
3.有其他线程的interrupt方法唤醒线程
4.wait方法超时
若要执行wait方法,线程必须持有锁。但如果线程进入等待队列,便会释放其实例的锁。
【习题1 阅读下面内容,叙述正确请打√,错误请打×】
1.在Java程序中,至少有一个线程在运行。(√)
2.调用Thread类的run方法后,新的线程就会启动。(×)
答:启动新线程的并不是run方法,而是start方法。
3.start方法和run方法声明在Runnable接口中。(×)
答:只有run方法声明在Runnable接口中。
4有时多个线程会调用同一个实例的方法。(√)
答:正因为如此,我们才需要执行线程的互斥处理。
5.有时多个线程会调用Thread类的一个实例的方法。(√)
答:虽说是Thread类的实例方法,但与其他类的实例方法并没有什么不同,所以也会被多个线程调用。
6.sleep方法执行后,在指定的时间所有的线程都会暂停。(×)
答:暂停的只是调用了sleep方法的线程。
7.某个线程在运行synchronized方法时,其他所有线程都会停止运行。(×)
答:停止运行的只是想要获取同一个实例的锁的线程。
8.执行sleep方法后的线程仅在指定时间内待在等待队列中。(×)
答:执行sleep方法后的线程并不会进入等待队列。只有在wait方法后,线程才会进入等待队列。
9.wait方法的调用语句必须写在synchronized方法中。(×)
答:synchronized方法或synchronized代码块。只要执行wait方法的线程在执行时获得了对象实例的锁即可。
10.notifyAll方法是java.lang.Object类的实例方法。(√)
【习题2】
当下面的程序运行时,程序会在输出1000个“*”后,再输出1000个“+”。请问,为什么输出结果并不是“*”和“+”交错混杂的呢?
public class main{
public static void main(String[] args){
new PrintThread("*").run();
new PrintThread("+").run();
}
}
public class printThread extends Thread{
private String message;
public printThread(String message){
this.message=message;
}
public void run(){
for(int i=0;i<1000;i++){
System.out.print(message);
}
}
}
答:因为主线程调用的不是start方法,而是run方法。new PrintThread("*").run();这条语句会创建PrintThread类的实例,并执行该实例的run方法。但执行run方法的并不是新线程,而是主线程。当输出100个“*”之后,下面的语句才会被执行。new PrintThread("+").run();
最终,所有的输出都是由主线程这一个线程来执行的,也就是说,这个程序其实是一个单线程程序。
//创建实例
MyThread t=new MyThread();
//启动线程
t.start();
//暂停已启动的线程
try{
t.sleep(1000);
}catch(InterruptedException e){
}
【习题3】
某人写了如下代码,想让启动的线程暂停约1秒。但这个代码是错误的,为什么呢?假设下面的MyThread就是代码清单I1-2中声明的那个类。
答:这是因为执行t.sleep(1000);时暂停的并不是与t相关的那个线程,而是执行这条语句的线程。
t.sleep(1000);
上面这条语句调用出来的并不是t的实例方法,而是Thread的静态方法。也就是说,这等同于执行下面这条语句。
Thread.sleep(1000);
当想要暂停新启动的线程时,我们可以在MyThread类的run方法中调用sleep方法。
【习题4】
假设存在一个如下声明的Something类,变量x,y表示Something类的两个不同实例。请判断下列组合是否允许多个线程同时运行,允许请画√,否则请画×。
public classSomething {
public void iA(){ }
public void iB(){ }
public synchronized void isSyncA(){ }
public synchronized void isSyncB(){ }
public static void cA(){ }
public static void cB(){ }
public static synchronized void cSyncA(){ }
public static synchronized void cSyncB(){ }
}
√(1)x.iA(); 与 x.iA();
答:非synchronized方法可在任意时间多个线程运行。
√(2)x.iA(); 与 x.iB();
答:非synchronized方法可在任意时间多个线程运行。
√(3)x.iA(); 与 x.iSyncA();
答:非synchronized方法可在任意时间多个线程运行,即使存在正在运行其他的synchronized方法的线程,非synchronized方法也任然可以由多个线程运行。
×(4)x.iSyncA(); 与 x.iSyncA();
答:同一个实例的synchronized实例方法同时只能由一个线程运行。
×(5)x.iSyncA(); 与 x.iSyncA();
答:同一个实例的synchronized实例方法同时只能由一个线程运行。
√(6)x.iSyncA(); 与 y.iSyncA();
答:实例不同,锁也就不同,所以就算是synchronized实例方法,也可以由多个线程同时运行。
√(7)x.iSyncA(); 与 x.iSyncB();
答:实例不同,锁也就不同,所以就算是synchronized实例方法,也可以由多个线程同时运行。
√(8)x.iSyncA(); 与 Something.cA();
答:静态方法本来就不是synchronized方法,因此可以同时运行。
√(9)x.iSyncA(); 与 Something.cSyncA();
答:synchronized实例方法和synchronized静态方法的锁不同,所以可以由多个线程同时运行。
×(10)Something.cSyncA(); 与 Something.cSyncA();
答:synchronized静态方法不可以有多个线程同时运行。
×(11)Something.cSyncA(); 与 Something.cSyncB();
答:synchronized静态方法不可以有多个线程同时运行。
×(12)x.cSyncA(); 与 y.cSyncB();
答:synchronized静态方法不可以有多个线程同时运行。