第5章Java多线程与并发库
5.1 创建线程的方式
5.1.1概念
1、进程:一个具有一定独立功能的程序关于某个数据集合的一次运行活动。
⑴、进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(textregion)、数据区域(data region)和堆栈(stackregion)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。
⑵、进程是一个“执行中的程序”。
2、线程:进程中的负责程序执行的一个控制单元(或执行路径)
⑴、一个进程中可以有多个执行路径,称为多线程。
⑵、开启多个线程是为了同时运行多部分代码
⑶、一个进程中,至少有一个线程
⑷、线程要执行的任务:每一个线程都有自己运行的内容
5.1.2创建线程的方式
1 JVM启动时,至少启动俩个线程
⑴、执行main的线程:该线程的任务代码都定义在main中
⑵、负责垃圾回收的线程
2、继承Thread类
⑴、步骤:①、定义一个类继承Thread类
②、覆盖Thread类中的run方法
继承Thread并复写run的原因:创建线程的目的是为了开启一条执行路径,实现指定的路径和其他代码同时运行,而运行的指定的代码就是这个执行路径的任务。
JVM创建的主线程的任务都定义在方法中,而自定义的线程的任务在哪儿?Thread类用于描述线程,而线程是需要任务的,此任务通过Thread类中的方法run来体现,即对run方法覆盖。
③、直接创建thread的子类对象创建线程
④、调用start方法开启并调用线程的任务run方法执行
⑵、代码示例:
class Demo extends Thread{
private String name;
Demo(Stringname){
super(name);//如此可以定义线程的名称
}
publicvoidrun(){
for (int i = 0; i <= 10; i++) {
Sop("i="+i+"..name="+Thread.currentThread().getName());
//获取当前线程的名称
//Sop("i="+i+"..name="+getName());
//当我们创建线程子类对象的时候,就完成了对线程的命名
}
}
}
public class ThreadDemo {
publicstaticvoidmain(String[] args) {
Demo d1 = new Demo("hufei");
Demo d2 = new Demo("胡王飞");
d1.start();
d2.start();//线程与线程之间是相互独立的,互不影响。
Sop(4/0);//为了证明线程之间的独立性,可以先结束
for (int i = 0; i <= 20; i++) {
Sop(i+".....over...."+Thread.currentThread().getName());
}
}
}
⑶、如何区分当前线程?可以通过Thread的getName方法获取线程的名称:Thread-编号(从0开始)。
3、实现runnable接口(创建线程常用该方法)
⑴、什么时候用:当程序已经继承了其他的父类时,只能使用接口实现。
⑵、步骤:①、定义类实现runnable接口。
②、覆盖接口中的run方法,将线程的任务的代码封装到run方法中。
③、通过Thread的类创建线程对象,并将runnable接口的子类对象作为Thread类的构造方法的参数进行传递。原因是线程的任务都封装在Runnable接口中子类的对象的run方法中,所以要在线程对象创建时就必须明确要运行的任务。
④、调用线程对象的start方法开启线程。
class Demo implements Runnable{
publicvoidrun(){
show();
}
publicvoidshow(){
for (int i = 0; i <= 40; i++) {
Sop("i="+i+"..name="+Thread.currentThread().getName());
}
public class ThreadDemo {
publicstaticvoidmain(String[] args) {
//但要使用我们自己定义的run,就必须利用Demo创建的对象调用
Demod3 = new Demo();
//创建线程必须用Thread类创建,将Demo创建的对象以参数的形式传入。
Threadt1 = new Thread(d3);
Threadt2 = new Thread(d3);
t1.start();
t2.start();
}
}
⑶、好处:①、将线程的任务从线程的子类中分离出来,按照面向对象的思想进行单独封装
②、避免了java单继承的局限性
4、面试题:下面代码运行结果是什么?
public class ThreadDemo {
publicstaticvoidmain(String[] args) {
new Thread(new Runnable(){
@Override
publicvoidrun() {
for (int i = 0; i <= 40; i++) { System.out.println("runnable"+"..name="
+Thread.currentThread().getName());
}
}}){
publicvoidrun(){
for (int i = 0; i <= 40; i++) {
System.out.println("thread"+"..name="
+Thread.currentThread().getName());
}
}}.start();
}
}
其运行结果:它运行的是自己的run方法,而不是runnable的run方法
5、定时器的应用
⑴、设定指定任务task在指定时间time执行schedule(TimerTask task, Date time)
publicclasstraditionalTimerTest {
publicstaticvoid main(String[] args) {
new Timer().schedule(new TimerTask(){
@Override
publicvoid run() {
System.out.println("bombing……");
}},10000);
//while的作用是将当前时间打印出来,更清晰的让我们看到程序的运行
while(true) {
System.out.println(new Date().getSeconds());
try {
Thread.sleep(1000);
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
⑵、设定指定任务task在指定延迟delay后进行固定延迟peroid的执行:schedule(TimerTask task, long delay, long period)
publicclasstraditionalTimerTest {
publicstaticvoid main(String[] args) {
new Timer().schedule(new TimerTask(){
@Override
publicvoid run() {
System.out.println("bombing……");
}},10000,3000);
}
}
⑶、不同时间间隔执行的定时器
publicclasstraditionalTimerTest {
privatestaticintcount;
publicstaticvoid main(String[] args) {
class MyTimerTask extends TimerTask {
@Override
publicvoid run() {
count = (count+1)%2;
System.out.println("bombing……");
new Timer().schedule(
new MyTimerTask(),2000+2000*count);
}
}
new Timer().schedule(new MyTimerTask(),2000);
}}
以上2013/10/17完成的学习,没有完成目标
原因:自己没有坚持,偷懒了,以后的控制住
⑷、设定指定任务task在指定延迟delay后进行固定频率peroid的执行:scheduleAtFixedRate(TimerTask task, long delay, long period),该方法和第二种方法没有区别
publicclassTraditionalTimeTest {
publicstaticvoid main(String[] args) {
Timertimer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
publicvoid run() {
System.out.println("bombing……");
}
}, 1000, 2000);
}
}
⑸、安排指定的任务task在指定的时间firstTime开始进行重复的固定速率period执行:Timer.scheduleAtFixedRate(TimerTasktask,Date firstTime,long period)
publicclassTraditionalTimeTest {
publicstaticvoid main(String[] args) {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, 12); // 控制时
calendar.set(Calendar.MINUTE, 0); // 控制分
calendar.set(Calendar.SECOND, 0); // 控制秒
Date time = calendar.getTime();//得出执行任务的时间,此处为今天的12:00:00
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
publicvoid run() {
System.out.println("bombing……");
}
}, time, 1000 * 60 * 60 * 24);// 这里设定将延时每天固定执行
}
}
5.2 线程互斥技术
5.2.1、线程的状态
1、新建状态(start):新创建了一个线程对象。
2、临时阻塞状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):临时阻塞状态的线程获取了CPU执行资格及执行权。
4、冻结状态(Blocked):冻结状态是线程因为某种原因释放CPU执行权与执行资格。直到线程进入临时阻塞状态,才有机会转到运行状态。阻塞的情况分三种:
⑴、等待冻结:运行的线程执行wait()方法,JVM会把该线程放入等待池中。可以利用notify()唤醒
⑵、同步冻结:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
⑶、其他冻结:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为冻结状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入临时阻塞状态。
5、消亡状态(stop):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
6、wait和sleep的区别
⑴、wait可以指定时间也可以不指定;但sleep必须指定时间
⑵、在同步中时,对cpu的执行权和锁的处理不同:wait释放执行权,释放锁;sleep释放执行权,不释放锁。
5.2.2 线程安全
1、前提:多个线程在操作共享数据,而且操作共享数据代码有多条。
2、原因:当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题。
3、解决:
⑴、利用同步代码块可以解决。
①、格式:
synchronized(对象){
//对象的理解:对象是任意的创建的
需要被同步的代码
}
②、好处:解决线程的安全问题
③、弊端:相对降低了效率,因为同步外的线程都会判断同步锁。
④、前提:同步必须有多个线程并使用同一个锁。
⑤、代码示例
class Ticket implements Runnable{
private intnum = 100;
Object obj = new Object();//放在这意味着只有一个锁
publicvoidrun(){
sale();
}
publicvoidsale(){
//Object obj = new Object();放在这意味着有多个锁
while(true){
synchronized(obj){
if(num>0){
try {Thread.sleep(10);} catch (InterruptedException e) {}
Sop(Thread.currentThread().getName()+"..sale.."+num );
}
}
}
}
}
publicclass SaleTicket {
//需求:买票
publicstaticvoidmain(String[] args) {
Ticket huoche = new Ticket();
Ticket dongche = new Ticket();
Thread t1 = new Thread(huoche);
Thread t2 = new Thread(huoche);
Thread t3 = new Thread(dongche);
Thread t4 = new Thread(dongche);
t1.start();
//t1.start();多次启动同一个线程会发生illegalThreadStateExcption
t2.start();
t3.start();
t4.start();
}
}
⑵、同步方法
①、同步方法的锁是this,它的原因是方法需要被对象调用,那么方法都有一个所属对象引用。就是this
②、静态同步方法锁:类进入内存时,虽然没有new对象,但java自身有个特点是对进内存的字节码文件先封装对象,所有的对象建立他们都有自己所属的字节码文件对象,即当前class文件所属的对象,可以用getClass方法获取,也是静态同步方法所使用的锁。怎么表示呢?this.getClass()或者当前类名.class
③、代码示例
/*class Bank{
private int sum;
private Object obj = new Object();
public void add(int num){
//有人想这样太麻烦,不如直接synchronized(new Object),
//这样是不行的,因为每来一个对象就new一个锁,问题依然会在。
synchronized(obj){
sum = sum + num;
//-->在这可能发生安全隐患。所以要用同步代码块将其封装起来
Sop("sum="+sum);
}
}
}*/
class Bank{
privateintsum;
publicsynchronizedvoid add(int num){//同步方法的锁是this
sum = sum + num;
Sop("sum="+sum);
}
}
class Cus implements Runnable{
privateBank b = new Bank();
publicvoid run() {
for (int i = 0; i < 3; i++) {
b.add(100);
}
}
}
publicclass BankDemo {
publicstaticvoidmain(String[] args) {
Cus c = new Cus();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
⑶、同步方法与同步代码块的区别:前者的锁是固定的,而后者是任意的。建议使用同步代码块。
5.2.3 单例设计涉及的多线程问题
class Singleton{
privateintnum;
publicint getNum() {
returnnum;
}
publicvoid setNum(int num) {
this.num = num;
}
privateSingleton(){}
privatestatic Singleton s = null;
//public static synchronized Singleton getInstance(){
//这样加虽解决同步问题,但是每次进来都得判断同步,效率低。为了提高效率就用以下方法
publicstatic Singleton getInstance(){
if(s==null){//为了解决效率问题
synchronized(Singleton.class){//解决安全问题,这个里面不能用
//this.getClass()锁,因为getClass()不是静态的
if(s==null)
s = new Singleton();
}
}
returns;
}
}
5.3 线程间的通讯
1、线程之间的通信简介
一般而言,在一个应用程序中(即进程),一个线程往往不是孤立存在的,常常需要和其它线程通信,以执行特定的任务。如主线程和次线程,次线程与次线程,工作线程和用户界面线程等。这样,线程与线程间必定有一个信息传递的渠道。这种线程间的通信不但是难以避免的,而且在多线程编程中也是复杂和频繁的。
总结:多个线程在处理同一资源,但任务去不同
2、涉及方法
wait():让线程处于冻结状态,被wait的线程被存储到线程池(等待集)中
notify():唤醒线程池中的一个线程
notifyAll():唤醒线程池中的所有线程
这些方法都必须定义在同步中,因为这些方法是用于操作线程的,必须明确到底操作的是哪个线程
为什么这些方法都定义在Object类中?
因为这些方法在操作同步中的线程时,都必须要标识他们的所操作线程的锁,只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒,不可以对不同锁中的线程进行唤醒,也就是说等待和唤醒必须是同一个锁,锁可以是任意对象,可以被任意的对象调用的方法定义在Object类中。
3、等待唤醒机制
⑴、同步代码块实现
class Resource{
String name;
String sex;
booleanflag = false;
}
class Input implements Runnable{
Resouce r = new Resouce();//如此建立对象,与输出的对象不一样,这样建立对象,很明显不行,因为输入和输出得是同一个对象,但是又必须用它,怎么办?所以可以通过另外一种形式——参传递,先把对象建立好,通过参数传递进来。能接受参数传递有两种方式:一种是一般方法,一种是构造方法。由于任务处理资源,这就意味着任务一对象初始化,就得有资源。
Resource r;
Input(Resource r){
this.r = r;
}
publicvoid run() {
int x = 0;
while(true){
synchronized(r){
if(r.flag)
try {r.wait();} catch (InterruptedException e) {}
if(x==0){
r.name = "hufei";
r.sex = "M";
}
else{
r.name = "燕儿";
r.sex = "女";
}
r.flag = true;
r.notify();
}
x = (x+1)%2;
}} }
class Outputimplements Runnable{
Resouce r = new Resouce();//如此建立对象,与输入的对象不一样,
Resource r;
Output(Resourcer){
this.r = r;
}
publicvoid run() {
while(true){
synchronized(r){
if(!r.flag)
//为什么是wait而不是sleep?因为到底要等多久是不确定的,要等到其他线程执行完才可以。
try {r.wait();} catch (InterruptedException e) {}
Sop(r.name+"...."+r.sex);
r.flag = false;
r.notify();
} }}}
⑵、同步方法的实现,也是对以上代码的优化
class Resource{
privateString name;
privateString sex;
booleanflag = false;
publicsynchronizedvoid set(String name,String sex){
if(flag)
//while(flag)会出现死锁的原因:
try {this.wait();} catch (InterruptedException e) {}
this.name = name;
this.sex = sex;
flag = true;
this.notify();
}
publicsynchronizedvoid out(){
if(!flag)
try {this.wait();} catch (InterruptedException e) {}
Sop(this.name+"...."+this.sex);
flag = false;
this.notify();
}
}
class Input implements Runnable{
Resource r;
Input(Resource r){
this.r = r;
}
publicvoid run() {
int x = 0;
while(true){
if(x==0){
r.set("hufei", "man");
}
else{
r.set("燕儿","女");
}
x = (x+1)%2;
}
}
}
class Outputimplements Runnable{
Resource r;
Output(Resourcer){
this.r = r;
}
publicvoid run() {
while(true){
r.out();
}}}
5.4 死锁
1、定义
集合中的每一个进程都在等待只能由本集合中的其他进程才能引发的事件,那么该组进程是死锁的。也就是在多个线程并发执行使用多个锁来同步时,有可能互相冲突,导致程序无法继续执行。
2、死锁的发生必须具备以下四个必要条件。
⑴、互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
⑵、请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
⑶、不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
⑷、环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
3、面试题:写一个死锁程序
class Test implements Runnable{
privatebooleanflag;
Test(boolean flag){
this.flag = flag;
}
publicvoid run() {
if(flag){
while(true){
synchronized(MyLock.locka){
Sop(Thread.currentThread().getName()+"...if locka");
synchronized(MyLock.lockb){
Sop(Thread.currentThread().getName()+"..iflockb");
}
}
}
}else{
while(true){
synchronized(MyLock.lockb){
Sop(Thread.currentThread().getName()+"...else locka");
synchronized(MyLock.locka){
Sop(Thread.currentThread().getName()+"...else lockb");
}
}
}
}
}
}
class MyLock{
publicstaticfinal Object locka = new Object();
publicstaticfinal Object lockb = new Object();
}
class DeadLockTest{
publicstaticvoid main(String[] args){
Testa = new Test(true);
Test b= new Test(false);
Threadt1 = new Thread(a);
Threadt2 = new Thread(b);
t1.start();
t2.start();
}
}
5.5 多生产者与多消费者的问题
当t0进来,判断flag为false,于是就生产了烤鸭1,然后把标记置于了true,而当t0再一次进来时,flag为true,所以就被等待了,即放入线程池中。 t1进来时也为true,所以也被等待了。
于是,t2进来判断flag为true,于是就消费了烤鸭1,然后把标记置于了false,然后t2就notify()了,就会唤醒t0或t1中的一个。假如t0被唤醒了,但可能t2会继续执行,当t2再一次进来时,flag为false,所以就被等待了,即放入线程池中。T3进来时也为false,所以也被等待了。
此时只剩t0,而t0不再判断了,因为if只判断一次,而wait在if的后面。直接进行生产,于是生产了烤鸭2,然后t0就notify()了,就会唤醒t2、t1或t3中的一个。如果唤醒的是对方那还好,不会出现错误。假如被唤醒的是t1。而t1不会判断了,继续生产了烤鸭3,如此,烤鸭2就没有被消费。
所以我们不能用if,而选择while。但这样就有可能t1也被wait了。所以所有线程全部被wait在线程中了。这样就出现了死锁。如何解决呢?可以利用notifyAll()。但是全唤醒了,就意味着者在同步中有多个线程,就会出现安全问题,但为什么又没事呢?是因为在同步中必须要持有锁才能执行,而一次只能有一个线程持有这个锁,所以才不会出现线程安全问题。
class Resource{
privateString name;
privateintcount = 1;
privatebooleanflag = false;
publicsynchronizedvoid set(String name) {
while(flag)
//用if(flag)会出现连续生产但不消费的情况,因为if只判断一次,而while可以循环判断。
//但如果只是while(flag)会出现死锁。因为有可能出现所有线程都被wait了的情况。所以
//判断标记一定要用while,因为他安全,于是我们可以配合notifyAll来解决这个问题
try {this.wait();} catch (InterruptedException e) {}
this.name = name + count;
count++;
Sop(Thread.currentThread().getName()+".生产者."+this.name);
flag = true;
notifyAll();
}
publicsynchronizedvoid out(){
while(!flag)
try {this.wait();} catch (InterruptedException e) {}
Sop(Thread.currentThread().getName()+".消费者."+this.name);
flag = false;
notifyAll();//如果只加用notifyAll()
}
}
class Producer implements Runnable{
private Resource r;
Producer(Resource3 r){
this.r = r;
}
publicvoid run() {
while(true){
r.set("烤鸭");
}
}
}
class Consumer implements Runnable{
private Resource r;
Consumer(Resource r){
this.r = r;
}
publicvoidrun() {
while(true){
r.out();
}
}
}
publicclass ProducerConsumerDemo {
publicstaticvoidmain(String[] args) {
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t0 = new Thread(pro);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(con);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
总结:if判断标记,只有一次,会导致不该运行的线程运行了,出现数据错误的情况。而while判断标记解决了线程获取执行权后是否要执行的问题。
notify只能唤醒一个线程,如果本方唤醒了本方没有意义,而且while+notify会导致死锁。notifyAll()解决了本方线程一定会唤醒了对方线程
5.6 JDK1.5新特性
1、Lock
⑴、缘由:在多生产多消费的问题中,notifyAll()不仅唤醒了对方,还唤醒了本方,这样效率有点低。于是就产生了Lock接口,它的出现替代了同步代码块或同步方法。
在同步代码块中,对于锁的操作是隐式的,而现在Lock提供了lock()和unlock()俩个方法将其显化。即将同步和锁封装成对象,并将操作锁的隐式方法定义到该对象中,将隐式动作变成显示动作。
⑵、synchronized方法和Lock的区别
Object obj = new Object();//在同步代码块中,对于锁的操作是隐式的
void show(){
synchronized(obj){
code ;
}
}
Lock lock = new ReentrantLock();//将同步和锁封装成对象,并将操作锁的隐式方法定义到该对象中,将隐式动作变成显示动作。
voidshow(){
try{
lock.lock();
code ;
//如果发生异常,程序就会跳转。但后面的unlock一定要执行。所以就把它放在finally中。
}finally{
lock.unlock();
}
}
2、condition
condition 替代了 Object 监视器方法,将 Object 监视器方法(wait、notify 和notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用。
可以一个锁上挂多个监视器。
3、代码体现
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
importjava.util.concurrent.locks.ReentrantLock;
class Resource{
privateString name;
privateintcount = 1;
privatebooleanflag = false;
Lock lock = new ReentrantLock();//创建一个互斥锁。
//通过已有的锁对象获取该锁上的监视器对象
//Condition Con = lock.newCondition();
//通过已有的锁对象获取俩组监视器对象:一组监视生产者,一组监视消费者
Condition proCon = lock.newCondition();
Condition cusCon = lock.newCondition();
public voidset(String name) {
lock.lock();
try{
while(flag)
try {proCon.await();} catch (InterruptedException e) {}
this.name = name + count;
count++;
Sop(Thread.currentThread().getName()+".生产者." +this.name);
flag = true;
cusCon.signal();
}finally{
lock.unlock();
}
}
public voidout(){
lock.lock();
try{
while(!flag)
try {cusCon.await();} catch (InterruptedException e) {}
Sop(Thread.currentThread().getName()+".消费者."+this.name);
flag = false;
proCon.signal();
}finally{
lock.unlock();
}
}
}
class Producer implements Runnable{
private Resource r;
Producer(Resource r){
this.r = r;
}
publicvoid run() {
while(true){
r.set("烤鸭");
}
}
}
class Consumer implements Runnable{
private Resource r;
Consumer(Resource r){
this.r = r;
}
publicvoidrun() {
while(true){
r.out();
}
}
}
publicclass LockDemo {
publicstaticvoidmain(String[] args) {
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t0 = new Thread(pro);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t4 = new Thread(con);
t0.start();
t1.start();
t2.start();
t4.start();
}
}
5.7 停止线程
5.7.1停止线程方式
1、stop方法:已过时
2、定义标记
怎么控制线程结束?任务中都会有循环结构。只要控制循环就可以结束任务。而控制循环通常就用定义标记完成。
class StopThread implements Runnable{
privatebooleanflag = true;
publicvoidrun() {
while(flag){
Sop("run");
}
}
publicvoidsetFlag(){
this.flag = false;
}
}
publicclass StopThreadDemo {
publicstaticvoid main(String[] args) {
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.start();
int num = 1;
for (; ; ) {
if(++num==50){
st.setFlag();
break;
}
Sop("main...."+num);
}
Sop("over");
}
}
3、interrupt
但是如果线程处于冻结状态,无法读取标记时,该如何结束?可以使用interrupt将线程从冻结状态,强制恢复到运行状态中来让线程具备cpu的执行资格
class StopThread implements Runnable{
privatebooleanflag = true;
publicvoid run() {
while(flag){
try {
wait();
//如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long,int)、sleep(long) 或 sleep(long,int) 方法过程中受阻,则其中断状态将被清除,让线程恢复到具有执行资格。它还将收到一个InterruptedException。
} catch (InterruptedException e) {
Sop(Thread.currentThread().getName()+"..."+e);
flag = false;//强制唤醒后,再通过标记来结束任务。
}
Sop(Thread.currentThread().getName()+"run");
}
}
}
publicclass StopThreadDemo {
publicstaticvoidmain(String[] args) {
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.start();
int num = 1;
for (; ; ) {
if(++num==50){
//st.setFlag();
t1.interrupt();
//可以使用它将线程从冻结状态,强制恢复到运行状态中来让线程具备cpu的执行资格
t2.interrupt();
break;
}
Sop("main...."+num);
}
Sop("over");
}
}
5.7.2线程类中其他方法
1、setDaemon方法
void setDaemon(boolean on):on
- 如果为 true
,则将该线程标记为守护线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
2、join方法
调用该方法的线程通过该方法申请加入进来,即获得cpu的执行权和执行资格,而被挤掉的线程就在等待该线程终止。所以当你想要临时加入一个线程运算时,可以使用join方法。
3、toString方法
返回该线程的字符串表示形式,包括线程名称、优先级和线程组
4、setPriority方法
更改线程的优先级,几个特殊的优先级:
⑴、MAX_PRIORITY:线程可以具有的最高优先级
⑵、MIN_PRIORITY:线程可以具有的最低优先级
⑶、NORM_PRIORITY:分配给线程的默认优先级
5、yield方法
暂停当前正在执行的线程对象,即释放执行权,并执行其他线程。
6、面试题
⑴、class Test implementsRunnable{
publicvoid run(Thread t){
}
}
如果错误,错误发生在哪一行?错误在第一行,没有覆盖接口中的方法,所以该类应该是抽象的,而该类没有被abstract修饰。
⑵、publicclassInterviewTest {
publicstaticvoid main(String[] args) {
newThread(new Runnable(){
publicvoidrun(){
Sop("runnable run");
}
}){
publicvoidrun(){
Sop("subThread run");
}
}.start();
}
}
其打印结果是:subThread run,以子类为主,如果没有子类,就以任务为主,如果连没有都没有,那就以Thread自己。