您的位置:首页 > 编程语言 > Java开发

学习笔记之JavaSE(25)--多线程5

2016-11-17 14:54 387 查看
今天学习的内容是操作线程的方法

一、wait()方法和notify()/notifyAll()方法

之前我们举过一个程序示例:多个线程都执行操作共享数据的代码时,即使它们的任务不同,也可能会出现线程安全问题,解决方法就是把每个线程操作共享数据的代码都同步化。可是这个例子的输出结果只能是一串“k:男”和一串“q:女”交替出现,如果希望交替打印这两个字符串,也就是希望两个线程交替执行,就可以使用等待唤醒机制。等待唤醒机制实际上是通过wait()方法和notify()方法完成的,之前说到线程状态的时候讲过,调用wait()方法可以使线程进入等待状态,必须用notify()或notifyAll()方法唤醒该线程。详细点说,wait()方法会让线程处于等待状态,并被存储到线程池中;notify()方法会唤醒线程池中任意一个线程;notifyAll()方法会唤醒线程池中所有线程

那么问题来了

线程池是什么?答案:线程池是由对象的锁管理的!实际上,这三个方法是Object类的方法,它们必须使用在同步代码之中,并由同步代码的锁所属的对象调用。这么说有点绕,举个例子:假设有一个含有wait()方法的同步代码块,它使用a对象的锁,那么这个wait()方法必须由a对象调用,且任何进入这个同步代码块的线程都会进入等待状态,被放置于a对象的锁管理的线程池中。那么问题又来了。。如果线程t1进入了这个同步代码块,那么a对象的所有同步代码都会被锁住,如何执行notify()方法唤醒线程t1呢?答案:处于等待状态的线程会将钥匙归还并打开对象的锁!也就是说这时线程t2,t3也可以进入该同步代码块并进入等待状态,t4则可以进入a对象锁住的其他同步代码块使用notifyAll()方法唤醒它们。注意:唤醒t1,t2和t3之后,只有持有钥匙的线程才能继续执行同步代码块中的代码,这也就避免了线程安全问题

顺便说说wait()方法与sleep()方法的区别(sleep()方法比较简单,不细说了):

wait()方法由任意对象调用;sleep()方法由线程调用
wait()方法 可以指定时间也可以不指定;sleep()方法必须指定时间
在同步代码中,执行wait()方法而进入等待状态的线程会打开对象的锁并归还钥匙;执行sleep()方法而进入休眠状态的线程依旧持有钥匙,不会打开对象的锁
下面是使用等待唤醒机制交替执行线程的程序示例:
public class ThreadCommunicationTest{
public static void main(String[] args){
Resource r = new Resource();//资源
Input in = new Input(r);//任务1
Output out = new Output(r);//任务2
Thread t1 = new Thread(in,"线程1");//线程1
Thread t2 = new Thread(out,"线程2");//线程2
t1.start();
t2.start();
}
}

//资源
class Resource{
private String name;
private String sex;
private boolean flag = false;//标志着现在可否打印name:sex

public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}

public synchronized void set(String name,String sex){
if(flag){
try{
wait();
}catch(InterruptedException e){
e.printStackTrace();
}
}
this.name = name;
this.sex = sex;
flag = true;
notify();
}

public synchronized void get(){
if(!flag){
try{
wait();
}catch(InterruptedException e){
e.printStackTrace();
}
}
System.out.println(name+":"+sex);
flag = false;
notify();
}
}

//输入
class Input implements Runnable{
private Resource r;

public Input(){}
public Input(Resource r){
this.r = r;
}

public void run(){
int x = 0;
while(true){
if(x==0){
r.set("k", "男");
}else{
r.set("q", "女");
}
x = (x+1)%2;
}
}
}

//输出
class Output implements Runnable{
private Resource r ;

public Output(){}
public Output(Resource r){
this.r = r;
}

public void run(){
while(true){
r.get();
}
}
}


二、终止线程的方法

终止线程的方法只有等待该线程执行的run()方法结束(还有一种stop()方法,已废除),而使用多线程主要是因为程序出现阻塞(等待用户输入)或者程序中存在无限循环阻碍了程序的运行,那么就可以通过控制循环来终止线程。目前提倡使用一个布尔型标记控制循环的停止。而如果线程因为执行了wait()方法或者sleep()方法而无法执行到循环代码,就可以使用线程的interrupt()方法强制线程退出等待或休眠状态,这样做会抛出InterruptedException,我们可以在catch块设置改变标记的代码,从而停止无限循环,程序示例:
public class Test43 {

public static void main(String[] args){
StopThread st = new StopThread();
Thread t = new Thread(st);
t.start();

int num = 0;
for(;;){
if(num==50){
//st.setFlag(false); 退出循环以中断线程
t.interrupt();
break;//退出循环以中断主线程
}
System.out.println(Thread.currentThread().getName()+":"+num);
num++;
}
System.out.println("over");
}
}

class StopThread implements Runnable{
private boolean flag = true;

public void run(){
try{
wait();
}catch(InterruptedException e){
e.printStackTrace();
flag = false;
}
while(flag){
System.out.println(Thread.currentThread().getName());
}
}

public boolean isFlag() {
return flag;
}

public void setFlag(boolean flag) {
this.flag = flag;
}
}


三、其他方法

调用线程的join()方法使该线程优先执行完毕;调用线程的setPriority()方法可以修改线程的优先级,优先级分为Thread.MAX_PRIORITY(常数10)、默认的Thread.NORM_PRIORITY(常数5)和Thread.MIN_PRIORITY(常数1),不过这样并不能确保优先级高的线程一定会优先执行;调用线程的yield()方法可以使该线程暂时停止执行,让别的线程先执行,但这样不能确保该线程会暂时停止执行。示例程序:
public class Test44 {

public static void main(String[] args){
JoinThread jt = new JoinThread();
Thread t1 = new Thread(jt,"线程1");
Thread t2 = new Thread(jt,"线程2");
t1.start();
try {
t1.join();//调用join()方法后,线程1先执行,执行完毕后,才轮到线程2和主线程交替执行
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
t2.setPriority(Thread.MAX_PRIORITY);//优先级高,但是也不一定一直执行线程2

for(int i=0;i<50;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}

}
}

class JoinThread implements Runnable{
public void run(){
for(int i=0;i<50;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
//Thread.yield();
}
}
}

四、面试题补充

线程终于结束啦!~虽然还是有点晕,不过对线程的理解比第一次学习时强了很多,学到了很多新知识(比如同步代码块,对象的锁和wait()方法等等),最后再补充两个面试题


//面试题1:下列代码编译能否通过,如不能,错误发生在第几行
class Test1 implements Runnable{
public void run(Thread t){}
}
/*
* 答案:不能。
* 原因:Runnable接口中只存在一个无参run()方法
* 这种定义方式实际上只是创建了一个Test1类的新方法而已
* 并没有实现Runnable接口中的方法
* 所以编译错误应该出现在第一行
* 解决方法:1.将Test1类改为抽象类 2.实现无参run()方法
*/

//面试题2:下列代码最终运行哪个run()方法(这里使用了两个匿名内部类)
public class Test45 {

public static void main(String[] args){
new Thread(new Runnable() {
public void run() {
System.out.println("Runnable实现类的的run()");
}
}){
public void run(){
System.out.println("Thread子类的run()");
}
}.start();
}
}
/*
* 答案:子类的run()方法会被执行
* 原因:实现Runnable接口的run()方法已经被子类覆盖了,当然会调用子类的方法
*/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: