Java并发编程基础--基本线程方法详解

时间:2021-09-10 04:25:16

什么是线程

线程是操作系统调度的最小单位,一个进程中可以有多个线程,这些线程可以各自的计数器,栈,局部变量,并且能够访问共享的内存变量。多线程的优势是可以提高响应时间和吞吐量。

使用多线程

一个进程正在运行的时候,至少会有一个线程运行。

public class Test {
public static void main(String[] args) {
System.out.println(Thread.CurrentThread().getName()); // 输出main
}
}

上面的程序输出为main,实际上是一个叫做main的线程在执行main方法,输出的main与main方法没有任何关系

继承Thread

实现多线程方式有两种,一种是继承Thread类,另一种是实现Ruannable接口。我们可以看看Thread类的结构

public class Thread implents Runnable

创建自己的线程类MyThread,并且重写run方法。

public class MyThread extends Thread {
@Override
public void run() {
super.run();
System.out.println("Mythread");
}
}
public class Run() {
public static void main(String[] args) {
Mythread myThread = new MyThread();
myThread.start():
System.out.println("运行结束");
}
}

上面的输出为

运行结束
MyThread

实现Runnable接口

如果先创建的线程类已经有一个父类了,那么就不能够继承Thread,因为java只支持单继承。那么捏可以实现Runnable接口。

public class MyRunnable imiplements Runnable {
@Override
public void run() {
System.out.println("运行中");
}
}

怎么运行呢?我们看看Thread的构造器?

Java并发编程基础--基本线程方法详解

可以通过将Runnable接口来创建Thread。

public class Run {
public static void main(String[] args) {
Thread thread = new Thread(runnable);
thread.start();
System.out.println("运行结束!");
}
}

实例变量和线程安全

自定义线程类的实例变量针对其他线程有共享和不共享之分,区分它们对于线程安全非常重要。

线程不共享数据的情况

public MyThread extends Thread {
private int count = 5;
public MyThread(String name) {
super();
this.setName(nanme); //线程的名字
}
@Override
public void run(){
super.run();
while(count > 0 ) {
count--;
System.out.println("由"+this.currentThread().getName() +" 计算. count=" + count);
} }
} public class Run {
public static void main(String[] args) {
MyThread a = new MyThread("A");
MyThread b = new MyThread("B");
MyThread c = new MyThread("C");
a.start();
b.start();
c.start();
}
}

共享数据如何实现呢?



public class MyThread extends Thread {

    private int count = 5;

    @Override
public void run() {
super.run();
while(count > 0) {
count--;
System.out.println("由" + this.currentThread().getName() + "计算,count =" + count);
}
}
}
public class Run() {
public static void main(String[] args) {
MyThread mythread = new MyThread();
Thread a = new Thread(mythread, "A");
Thread b = new Thread(mythread, "B");
Thread c = new Thread(mythread, "C");
a.start();
b.start();
c.start();
} }

上面的线程共享了MyThread中的count数据,所以上面的代码是线程不安全的。解决这种问题,最简单的方式,是通过同步的手段来达到线程安全的目的。

currentThread方法

在Thread类中有一个currentThread方法,这个方法可以查看当前线程的信息,比如当前线程的名称。

isAlive方法

用来判断当前线程是否处于活动状态,什么是活动状态,活动状态就是线程已经启动且没有终止。

sleep()方法

方法sleep方法是指在指定的毫秒数内让当前正在执行的线程来休眠。当前正在执行的线程是指this.currentThread()的结果。

停止线程

Java中有以下方式终止正在进行的线程:

  • 使用退出标识,使线程正常退出,也就是当run方法完成后线程终止
  • 使用stop方法强行终止线程,但是不推荐使用,因为stop和suspend都是过期的方法
  • 使用intterrupt方法来中断线程

停止不了的线程

下面的例子调用intterrupt()方法来停止线程,但是interrupt方法的使用效果并不是for+break那样,调用interrupt方法仅仅是在当前线程中打一个停止的标记

public class MyThread extends Thread {
@Override
public void run() {
super.run();
for(int i = 0 ; i < 50000; i++) {
System.out.println("i = " + (i + 1));
}
}
} public class Run {
public static void main(String[] args) {
try {
MyThread my_thread = new MyThread();
my_thread .start();
Thread.sleep(2000);
my_thread.interrupt();
} catch(InterruptException e) {
System.out.println("main catch");
e.printStackTrace();
}
} }

结果是,仍然打印了50万行的日志,所以调用interrupt方法并没有停止线程。

判断线程是否是停止状态

Thread类中,有两种方法:

  • this.interrupted(),测试当前线程是否已经中断
  • this.isInterrupted(),测试线程是否已经中断

我们来看看this.interrupted()方法的解释:测试当前线程是否已经中断,当前线程是指执行这个方法调用的线程。

public class MyThread extends Thread {
@Override
public void run() {
super.run();
for(int i = 0; i < 50000; i++) {
System.out.println("i = " + ( i + 1));
}
}
}
public class Run {
public static void main(String[] args) {
try {
MyThread thread = new MyThread();
thread.start();
thread.interrupt();
System.out.println("是否停止1 ? ="+ thread.interrupted());
System.out.println("是否停止2 ? =" + thread.interrupted());
} catch(InterruptedException e) {
System.out.prinln("main catch");
e.printStackTrace();
}
System.out.println("end");
}
}

我们通过thread.interrupted()用来测试当前线程是否是已经中断,而在这里当前线程为main线程,而它从未中断过。所以打印的结果是两个false

看看下面的代码:

public class Run2 {
public static void main(String[] args) {
Thread.currentThread().interrupt();
System.out.println("是否停止1 ? " + Thread.interrrupted()); // true
System.out.println("是否停止2 ? " + Thread.interrrupted()); // false
System.out.println("end");
}
}

方法interrupted()的确判断出当前线程是否停止,但为什么第二个为false值呢?看看interrrupted方法的手册说明:

测试当前线程是否已经中断。线程中断状态由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断前,当前线程再次中断的情况除外)。

Tests whether the current thread has been interrupted. The interrupted status of the thread is cleared by this method. In other words, if this method were to be called twice in succession, the second call would return false (unless the current thread were interrupted again, after the first call had cleared its interrupted status and before the second call had examined it)

也就是所interrupted()方法具有清除状态的方法,所以第二次调用interrupted()方法返回的值是false。

我们来看看interrupted()方法后再来看看isInterrupted()方法,声明如下:

public boolean isInterrupted();

从声明中可以看出isInterrupted()方法不是static的。

public class Run3 {
public static void main(String[] args) {
try {
MyThread thread = new MyThread();
thread.start();
Thread.sleep(1000);
thread.interrupt();
System.out.println("是否停止1 ?==" + thread.isInterrupted()); // true
System.out.println("是否停止2? ==" + thread.isInterrupted()); // true
} catch(InterruptedException e) {
System.out.println("main catch");
e.printStackTrace();
}
System.out.println("end")
} }

我们可以看到this.isInterrrupted()并未清除状态标志,所以打印两个true。

我们总结一下,两个方法的解释:

  • this.interrupted() : 测试当前线程是否已经是中断的状态,执行后具有将状态标志清除为false的功能。
  • this.isInterrupted(): 测试线程Thread对象是否已经是中断状态,但不清除状态标志。

能停止的线程--异常法

public calss MyThread extends Thread {
@Override
public void run() {
super.run();
for(int i = 0; i < 50000; i++) {
if(this.interrupted()) {
System.out.println("已经是停止状态!我要退出!");
break;
}
System.out.println("i=" + (i + 1));
}
}
}
public class Run {
public static void main(String[] args) {
try {
MyThread thread =new MyThread();
thread.start();
Thread.sleep(2000);
thread.interrupt();
} catch(InterruptedException e) {
System.out.println("main catch");
e.printStackTrace();
}
}
}

上面的示例虽然停止了线程,但如果for语句下面还有语句,还是会继续运行的。


public calss MyThread extends Thread {
@Override
public void run() {
super.run();
for(int i = 0; i < 50000; i++) {
if(this.interrupted()) {
System.out.println("已经是停止状态!我要退出!");
break;
}
System.out.println("i=" + (i + 1));
}
System.out.println("我被输出,如果此代码是for又继续运行,线程并未停止!");
}
} public class Run {
public static void main(String[] args) {
try {
MyThread thread = new MyThread() ;
thread.start();
Thread.sleep(2000);
thread.interrupt();
} catch(InterruptedException e) {
System.out.println("main catch");
e.printStackTrace();
} }
}

上面的代码可以将我被输出,如果此代码是for又继续运行,线程并未停止!输出了。说明,及时停止了线程,从for循环中跳出,for后面的语句也会执行。这说明了一个什么问题:thread.interrupt只是打一个标志,表示通知该线程要停止,而this.interrupted()则只是能够检测这个标志,并且将这个标志服务清除。

那么怎么讲线程停止呢?

public calss MyThread extends Thread {
@Override
public void run() {
try {
super.run();
for(int i = 0; i < 50000; i++) {
if(this.interrupted()) {
System.out.println("已经是停止状态!我要退出!");
throw new InterruptedException();
break;
}
System.out.println("i=" + (i + 1));
}
System.out.println("我被输出,如果此代码是for又继续运行,线程并未停止!");
} catch (InterrruptedException e) {
System.out.println("进MyThread.java类run方法中的catch了");
}
}
} public class Run {
public static void main(String[] args) {
try {
MyThread thread = new MyThread() ;
thread.start();
Thread.sleep(2000);
thread.interrupt();
} catch(InterruptedException e) {
System.out.println("main catch");
e.printStackTrace();
} }
}

在睡眠中停止

如果线程在sleep状态下,会有什么效果呢?

public calss MyThread extends Thread {
@Override
public void run() {
try {
super.run();
System.out.println("run begin")
Thread.sleep(20000); //睡觉20s
System.out.println("run end");
} catch (InterrruptedException e) {
System.out.println("在睡觉中被停止,进入catch!" + this.isInterrupted()); // 结果打印false。
}
}
}
public class Run {
public static void main(String[] args) {
try {
MyThread thread = new MyThread() ;
thread.start();
Thread.sleep(2000);
thread.interrupt();
} catch(InterruptedException e) {
System.out.println("main catch");
e.printStackTrace();
} }
}

从结果上看,如果在sleep状态下停止某一线程,那么,会进入到catch语句,并且清楚停止状态值,使其成为false。

暂停线程

暂停线程意味着线程可以恢复执行,在java多线程中,可以使用suspend()方法暂停线程,使用resume()方法恢复线程的执行。

public class MyThread extends Thread {
private long i = 0;
public long getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
@Override
public void run() {
while(true) {
i++;
}
}
} public class Run {
public static void main(String[] args) {
try {
MyThread thread = new MyThread();
thread.start();
Thread.sleep(5000);
thread.suspend();
System.out.println("A=" + System.currentTimeIillis() + " i =" + thread.getI());
Thread.sleep(5000);
System.out.println("A=" + System.currentTimeMIillis() + " i =" + thread.getI()); // B段
thread.resume();
Thread.sleep(5000);
// C段
thread.suspend();
System.out.println("B= " + System.currentTimeMillis() + " i = " + thread.getI());
Thread.sleep(5000);
System.out.println("B = " + System.currentTimeMillis() + " i = " + thread.getI() ); } catch(InterruptedException e) {
e.printStackTrace();
}
} }

suspend和resumen方法的缺点--独占

如果一个线程获取了某个资源的锁,但是其suspend,那么后续的线程就无法获取该锁。

yield方法

这个方法用来放弃当前的CPU资源,将它然给其他的任务去占用CPU执行时间。但是放弃的时间不确定,有可能刚放弃,马上又获得了时间片。

A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore this hint.