标签:代码 停止线程 自定义 head i++ 四种 inf package pen
本章主要介绍线程和进程的相关概念,多线程的实现和停止,以及Thread类中的核心方法。
一个可并发执行的具有独立功能的程序关于某个数据集合的一次执行过程,也是操作系统进行资源分配和保护的基本单位。简单的说,进程就是一个程序的一次执行过程。
操作系统采用进程机制使得多任务能够并发执行,提高了资源使用和系统效率。在早期操作系统中,进程是系统进行资源分配的基本单位,也是处理器调度的基本单位,进程在任一时刻只有一个执行控制流,这种结构称为单线程进程。单线程进程调度时存在进程时空开销大、进程通信代价大、进程并发粒度粗、不适合于并发计算等问题,操作系统引入线程机制来解决这些问题。线程机制的基本思路是,把进程的两项功能——独立分配资源和被调度分派执行分离开来,后一项任务交给线程实体完成。这样,进程作为系统资源分配与保护的独立单位,不需要频繁切换;线程作为系统调度和分派的基本单位会被频繁的调度和切换。
线程是操作系统进程中能够独立执行的实体,是处理器调度和分派的基本单位。线程是进程的组成部分,每个进程内允许包含多个并发执行的线程。同一个进程中所有的线程共享进程的主存空间和资源,但是不拥有资源。
线程就是进程中的一个负责程序执行的一个控制单元(执行路径)。一个进程中可以有多个执行路径,称之为多线程。
多线程的实现主要有四种
- 继承Thread类
- 实现Runnable接口
- 使用Callable接口,需要采用FutureTask类实现中间传递功能,默认带返回值
- 线程池实现
继承Thread类
1)定义一个类继承Thread类。
(2)覆盖Thread类中的run方法。(方法run称为线程体)
(3)直接创建Thread类的子类对象创建线程。
(4)调用start方法,开启线程并调用线程的任务run方法执行。
注意:run()方法和start()方法的区别。start()方法来启动线程,run()方法当作普通方法的方式调用,程序还是顺序执行。
public class MyThread extend Thread{
@Override
public void run () {
for (int i = 0; i <500000; i++) {
if(this.isInterrupted()){
System.out.println("我结束了");
break;
}
System.out.println("i="+i+1);
}
}
}
2.实现Runnable接口
(1)定义类实现Runnable接口
(2)覆盖接口中的run方法,将线程的任务代码封装到run方法中。
(3)通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread构造函数的参数进行传递。线程的任务都封装在Runnable接口子类对象的run方法中。所以要在线程对象创建时就必须明确要运行的任务。
(4)调用线程对象的start方法开启线程。
public class MyThread implements Runnable{
@Override
public void run () {
System.out.println("运行中");
}
}
3.使用Callable接口,需要采用FutureTask类实现中间传递功能,默认带返回值
class MyThread implements Callable<Integer>{
@Override
public Integer call () throws Exception {
System.out.println("*****call in call method ");
return 1024;
}
}
public class Run{
public static void main (String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer > futureTask = new FutureTask(new MyThread2());
new Thread(futureTask, "A").start();
Integer integer = futureTask.get();//获取返回值
System.out.println(integer);
}
}
4.线程池实现
非线程安全问题主要指多个线程对同一对象中的同一个实例变量进行操作的时候会出现
值被更改的、值不同步的情况,进而影响程序执行流程。下面通过一个案例来学习如何
解决该问题
创建t4_threadsafe项目,实现非线程安全的环境
package com.ybzn.thread01.t4_servlet;
public class LoginServlet {
private static String usernameRef;
private static String passwordRef;
public static void doPost(String username,String password) throws InterruptedException {
usernameRef=username;
if("a".equals(username)){
Thread.sleep(5000);
}
passwordRef=password;
System.out.println("username="+usernameRef+"\npassword="+passwordRef);
}
}
线程ALogin.java,代码如下:
package com.ybzn.thread01.t4_servlet;
public class ALogin extends Thread{
@Override
public void run () {
super.run();
try {
LoginServlet.doPost("a", "aa");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
线程BLogin.java,代码如下:
public class BLogin extends Thread{
@Override
public void run () {
try {
LoginServlet.doPost("b","bb");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
测试运行代码:
public class Run {
public static void main (String[] args) {
ALogin a =new ALogin();
a.start();
BLogin bLogin = new BLogin();
bLogin.start();
}
}
执行结果如下
原因分析:
运行结果是错误的,首先,两个线程的同一个对象的public static void doPost(String username,String password)
方法传参数的时候,方法的参数是不会被覆盖的,方法的参数值会绑定到当前线程上的。(具体分析请看《Java多线程编程核心技术》P22)
采用synchronized锁对象,更改LoginServlet.java代码如下:
public class LoginServlet {
private static String usernameRef;
private static String passwordRef;
// public static void doPost(String username,String password) throws InterruptedException {
// usernameRef=username;
// if("a".equals(username)){
// Thread.sleep(5000);
// }
// passwordRef=password;
// System.out.println("username="+usernameRef+"\npassword="+passwordRef);
// }
/**
* 升级版,添加synchronized锁对象
*/
synchronized public static void doPost (String username, String password) throws InterruptedException {
usernameRef = username;
if ("a".equals(username)) {
Thread.sleep(5000);
}
passwordRef = password;
System.out.println("username=" + usernameRef + "\npassword=" + passwordRef);
}
}
执行结果如下,注意,在Web开发中,Servlet本身就是单例的,所以为了不出现非线程安全问题,建议不要在Servlet中出现实例变量
通过细化println()
方法与i–-
联合使用可能会出现另外一种异常,代码如下:
public class MyThread extends Thread{
private int i =5;
@Override
public void run () {
System.out.println("i="+(i--)+" threadName="+Thread.currentThread().getName());
//注意:代码i单独一行运行
//被改成当前项目中println()方法直接进行输出
}
}
运行代码:
public class Run {
public static void main (String[] args) {
MyThread myThread = new MyThread();
Thread thread1 = new Thread(myThread);
Thread thread2 = new Thread(myThread);
Thread thread3 = new Thread(myThread);
Thread thread4 = new Thread(myThread);
Thread thread5 = new Thread(myThread);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread5.start();
}
}
方法内部的同步的,但是和i - - 一同使用还是会出现非线程安全的问题
采用同步方法,自定义一个方法
public void println(String x){
synchronized(this){
print(x);
newLine();//换行
}
}
currentThread()方法能够返回代码段正在被那个线程所调用
通过一个项目来演示
public class Run1 {
public static void main (String[] args) {
System.out.println(Thread.currentThread().getName());
}
}
运行结果如下图
该结果说明main()方法被名为main线程调用
继续测试,创建一个MyThread.java
类,代码如下
public class MyThread extends Thread {
@Override
public void run () {
System.out.println("run 打印" + Thread.currentThread().getName());
}
MyThread () {
System.out.println("构造方法打印: " + Thread.currentThread().getName());
}
}
运行类代码Run2.java
public class Run2 {
public static void main (String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
结果如图所示:
将代码Run2.java修改如下
public class Run2 {
public static void main (String[] args) {
MyThread myThread = new MyThread();
myThread.start();
myThread.run();
}
}
运行结果如下
对比上面的,发现操作run()方法线程 变成了main,
执行run()和start()方法有两个区别:
再来测试一个比较复杂的情况,创建一个``CountOperate.java`,代码如下:
public class CountOperate extends Thread {
public CountOperate(){
System.out.println("countOperate ---begin");
System.out.println("Thread.currentThread().getName()="+Thread.currentThread().getName());
System.out.println("this.getName="+this.getName());
System.out.println("countOperate ---end");
}
@Override
public void run () {
System.out.println("run---begin");
System.out.println("Thread.currentThread().getName()="+Thread.currentThread().getName());
System.out.println("this.getName="+this.getName());
System.out.println("run---end");
}
}
启动类Run.java
public class Run {
public static void main (String[] args) {
CountOperate c= new CountOperate();
Thread thread = new Thread(c);
thread.setName("A");
thread.start();
}
}
运行结果如下:
结果分析:
* 运行结果:
* countOperate ---begin
* Thread.currentThread().getName()=main 构造方法属于main对象
* this.getName=Thread-0 main方法的线程对象是Thread-0
* countOperate ---end
*
* run---begin
* Thread.currentThread().getName()=A run方法属于该线程A
* this.getName=Thread-0 main方法的线程对象任然是Thread-0 当前线程
* run---end
? currentThread()
返回的线程
getId()方法:获取当前正在执行线程的ID
this.currentThread():返回的线程。
yield()方法:放弃当前的CPU资源,将它让给其他的任务去占用CPU执行时间。但是放弃时间不确定,有可能刚刚放弃,马上有获得CPU时间片。
停止线程是多线程开发的一个很重要的技术点。停止一个线程意味着线程处理完任务之前停止正在做的操作,也就是放弃当前操作。停止一个线程可以采用Thread.stop()
方法,但不推荐使用该方法,此方法是不安全的,该方法正在被弃用。
在大多数的情况下,停止一个线程,采用Thread.interrupt()
方法,但是这个方法不会终止一个正在运行的线程,需要添加一些判断方法。
在Java中有3种方法,可以使当前正在运行的线程结束:
1) 使用退出标志使线程正常退出;
? 2)使用stop()方法强行终止线程,但是不推荐使用,发生线程不安全问题
? 3)使用interrupt()方法终止线程【推荐】
本案例采用interrupt()方法来终止线程运行,但是结果发现,其无法终止正在运行的线程,需要添加一些判断。
创建项目名字为t11的,MyThread.java代码如下
/**
* 停止不了的线程
*/
public class MyThread extends Thread {
@Override
public void run () {
for (int i = 0; i <50000 ; i++) {
System.out.println("i="+(i+1));
}
}
}
运行类Run.java代码如下
public class Run {
public static void main (String[] args) throws InterruptedException {
MyThread myThread =new MyThread();
myThread.start();
Thread.sleep(200);
myThread.interrupt();
myThread.isInterrupted();
System.out.println("ZZZZZZZZZ");
}
}
运行结果如下
通过将输出结果导出控制台,再其他记录查看软件上面可以发现,interrupt
方法并没有将线程停止。如何将线程停止呢?
在学习如何将线程停止之前,先学两个方法。
判断线程的状态是否为停止状态,Java提供了两种方法:
1)public static boolean interrupted()
:测试currentThread()是否已经中断
2)public boolean this.isInterrupted()
:测试this关键字所在的类的对象是否已经中断
这两个方法都可以判断线程是否停止,那么有什么区别呢?
下面进行项目分析,创建t12项目
public class MyThread extends Thread {
@Override
public void run () {
super.run();
for (int i = 0; i <50000 ; i++) {
System.out.println("i="+(i+1));
}
}
}
创建运行Run.java类
public class Run {
public static void main (String[] args) throws InterruptedException {
MyThread myThread=new MyThread();
myThread.start();
Thread.sleep(1000);
myThread.interrupt();
Thread.currentThread().interrupt();
System.out.println("线程是否停止1="+myThread.isInterrupted());//判断 myThread线程是否结束
System.out.println("线程是否停止2="+ Thread.interrupted());//判断当前线程是否结束
System.out.println("线程是否isAlive2="+ Thread.currentThread().isAlive());
System.out.println("线程getName="+ Thread.currentThread().getName());
System.out.println("end");
}
}
运行结果如下:
采用 myThread.interrupt();
方法,并没有使得myThread线程结束,但是采用Thread.currentThread().interrupt();
却可以使得线程结束,从控制台输出的结果来看,线程并没有停止,Thread.interrupt()只能使得当前线程停止,因为当前线程是main
而不是myThread 所以输出false
Thread.interrupted()方法能够判断当前线程是否为停止状态。但是它会将线程中断的状态清除,如果第二次在使用该方法,得到的结果将会与之前相反
this.isInterrupted():测试线程Thread对象是否是中断状态,不清楚状态标志。
isInterruped()方法不是静态方法,所以需要对象来调用,但是其调用了结果,并不能再当前Run类中判断,而是要到原对象MyThread中去判断.
通过for循环判断线程是否处于停止状态即可判断后面的代码是否可以运行。如果处于停止状态,后面的代码不在执行
创建t13项目,MyThread.java
/**
* 线程停止了,但是还会执行后面代码,所以是虚假停止
*/
public class MyThread extends Thread{
@Override
public void run () {
for (int i = 1; i <=500000; i++) {
if(this.isInterrupted()){
System.out.println("已结是停止状态了,我要退出了");
break;
}
System.out.println("i="+i);
}
System.out.println("我被输出了,我是for后面执行的代码,线程并未停止");
}
启动运行Run.java
public class Run {
public static void main (String[] args) throws InterruptedException {
MyThread myThread =new MyThread();
myThread.start();
Thread.sleep(800);
myThread.interrupt();
System.out.println("end");
}
}
运行结果
虽然停止了线程,但是如果for后面还有语句,他还是会执行的,这个是一个小缺陷,下面我们来解决这个问题。重新创建一个MyThread.java代码
/**
* 通过抛出异常 正常停止线程
*/
public class MyThread extends Thread {
@Override
public void run () {
try {
for (int i = 1; i <= 500000; i++) {
if (this.isInterrupted()) {
System.out.println("已结是停止状态了,我要退出了");
throw new InterruptedException();
}
System.out.println("i=" + i);
System.out.println("我被输出了,我是for 后面执行的代码");
}
} catch (InterruptedException e) {
System.out.println("进入MyThread中的Catch类了");
e.printStackTrace();
}
}
}
启动类Run.java
public class Run {
public static void main (String[] args) {
MyThread myThread =new MyThread();
myThread.start();
try {
Thread.sleep(800);
myThread.interrupt();
} catch (InterruptedException e) {
System.out.println("main catch");
e.printStackTrace();
}
System.out.println("end");
}
}
运行结果
由运行结果看出,线程最终被正常停止了,这种方法就是采用interrupt()方法中断线程。
线程在sleep状态下,停止线程会自动进入catch语句,并且停止状态值为false
java.lang.ThreadDeath
异常,但是在通常的情况下,不会被显性捕捉总结,在java中有以下三种方法可以终止正在运行的线程。
(1)使用退出标记,使线程正常退出,也就是当run方法完成之后线程终止。
(2)使用stop方法强行终止线程,但是不推荐使用这种方法,因为stop和suspend及resume一样,都是作废过期的方法,使用它们可能产生不可预料的结果。
(3)使用interrupt方法中断线程。需要注意的是interrupt方法仅仅是在当前线程中打了一个停止的标记,并不是真正的停止线程,需要与标记一起使用来停止线程。
暂停线程意味此线程可以恢复运行,在多线程中采用suspend()方法来悬挂暂停线程和resume()方法唤醒来执行。
创建t16项目,MyThread.java
/**
*/
public class MyThread extends Thread{
private long i=0;
public long getI(){
return i;
}
public void setI (long i) {
this.i = i;
}
@Override
public void run () {
while(true){
i++;
}
}
}
运行结果类:
public class Run {
public static void main (String[] args) {
try {
MyThread myThread = new MyThread();
myThread.start();
Thread.sleep(5000);
//A 段
myThread.suspend();//悬挂
System.out.println("A="+System.currentTimeMillis()+" i= "+myThread.getI());
Thread.sleep(5000);
System.out.println("A="+System.currentTimeMillis()+" i= "+myThread.getI());
//B段
myThread.resume();//唤醒
Thread.sleep(5000);
//C段
myThread.suspend();
System.out.println("B="+System.currentTimeMillis()+" i= "+myThread.getI());
Thread.sleep(5000);
System.out.println("B="+System.currentTimeMillis()+" i= "+myThread.getI());
} catch (Exception e) {
System.out.println("线程进入main catch块");
e.printStackTrace();
}
System.out.println("end");
}
}
运行结果
从控制台来看,线程虽然被暂停了,但是可以恢复成原来的样子,
suspend()方法来暂停线程
resume()方法来恢复线程
suspend\resume的缺点
1. 在于独占 同步块, 导致其他线程无法访问公共同步块,所以他们两个就被废弃了
2. 数据不完整性, 暂停线程容易导致数据不完整性的出现
目前使用线程暂停与恢复,使用==wait()、notify()或者notifyAll()三种法==
在java中有两种线程,一种是用户线程,另一种是守护线程。
守护线程是一种特殊的线程,当进程中不存在非守护线程则守护线程自动销毁。典型的守护线程就是垃圾回收线程,当线程中没有非守护线程了,则垃圾回收线程也就没有存在的必要了,自动销毁。
该方法必须在启动线程前调用。守护线程和其他的线程城在开始和运行都是一样的,轮流抢占cpu的执行权,结束时不同。正常线程都需要手动结束,对于后台线程,如果所有的前台线程都结束了,后台线程无论处于什么状态都自动结束
标签:代码 停止线程 自定义 head i++ 四种 inf package pen
原文地址:https://www.cnblogs.com/blogger-Li/p/14224217.html