Java多线程编程——多线程技能
Posted Haust_Leone
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java多线程编程——多线程技能相关的知识,希望对你有一定的参考价值。
第一章 Java多线程技能
文章目录
- 第一章 Java多线程技能
- 前言
- 一、进程和多线程概述
- 二、使用多线程
- currentThread()方法
- isAlive方法
- sleep(long millis)方法
- sleep(long millis, int nanos)方法
- StackTraceElement[] getStackTrace()方法
- static void dumpStack()方法
- getId()方法
- 三、停止线程
- 四、暂停线程
- 四、yield()方法
- 五、线程的优先级
- 六、 守护线程
- 总结
前言
作为多线程编程的第一章,主要介绍Thread类的核心方法线程如何启动
如何使线程暂停
线程的优先级
线程安全相关问题
提示:以下是本篇文章正文内容,下面案例可供参考
一、进程和多线程概述
进程是受操作系统管理的基本运行单元。
程序是指令序列,这些指令可以让CPU完成指定的任务。.java程序经编译后形成.class文件,在Windows中启动一个JVM虚拟机相当于创建了一个进程,在虚拟机中加载class文件并运行,在class文件中通过执行创建新线程的代码来执行具体的任务。测试用代码如下:
public class Test1
public static void main(String[] args)
try
Thread.sleep(Integer.MAX_VALUE);
catch (InterruptedException e)
e.printStackTrace();
Test1类在重复运行3次后,可以在任务管理器的进程列表中看到创建了3个javaw.exe进程,说明每执行一次main()方法就创建一个进程,其本质就是JVM虚拟机进程。
线程可以理解为在进程中独立运行的子任务。
使用多线程有什么优点?可以大幅利用CPU的空闲时间来处理其他任务,使用多线程技术可以在同一时间执行更多不同的任务。
在什么场景下使用多线程技术?
1) 阻塞。一旦系统中出现了阻塞现象,则可以根据实际情况来使用多线程技术提高运行效率。
2)依赖。业务分为两个执行过程,分别是A和B。当A业务发生阻塞情况时,B业务的执行不依赖与A业务的执行结果,这时可以使用多线程技术来提高运行效率;如果B业务的执行依赖A业务的运行结果,则可以不适用多线程技术,按顺序进行业务的执行。
二、使用多线程
实现多线程编程主要有两种方式:一种是继承Thread类,另一种是实现Runnable接口。
1.继承Thread类
Thread类的声明结构:public class Thread implements Runnable
从上面的源代码中可以发现,Thread实现了Runnable接口,它们之间具有多态关系,多态结构的示例代码如下:Runnable run1 = new Thread();
Runnable run2 = new MyThread();
Thread t1 = new MyThread();
使用Thread类的方式创建新线程时,最大的局限是不支持多继承,因为Java语言的特点是单根继承,所以为了支持多继承,完全可以实现Runnable接口。
创建一个自定义的线程类MyThread.java,此类继承自Thread,并且重写run()方法。在run()方法中添加新线程要执行的代码如下:
package mythread;
public class MyThread extends Thread
@Override
public void run()
super.run();
System.out.println("MyThread");
运行类代码如下:
package test;
import mythread.MyThread;
public class Run
public static void main(String[] args)
MyThread myThread = new MyThread();
myThread.start();//耗时大
System.out.println("运行结束!");//耗时小
上面代码使用start()方法启动一个线程,线程启动后会自动调用线程对象中的run()方法,run()方法中的代码就是线程对象要执行的任务,是线程任务的入口。
start()方法耗时的原因是执行了多个步骤,具体如下:
1)通过JVM告诉操作系统创建Thread在使用多线程技术时代码的运行结果与代码的执行顺序和调用顺序是无关的。另外,线程是一个子任务,CPU以不确定的方式,或者说是以随机的时间来调用线程中的run()方法。
2)操作系统开辟内存并使用Windows SDK中的creatThread()函数创建Thread线程对象。
3)操作系统对Thread对象进行调度,以确定执行时机。
4)Thread在操作系统中被成功执行。
2.线程随机性的展现
创建自定义线程类MyThread.java的代码如下:
package mythread;
public class MyThread extends Thread
@Override
public void run()
for (int i = 0; i < 10000; i++)
System.out.println("run=" + Thread.currentThread().getName());
再创建运行类Test.java代码:
package test;
import mythread.MyThread;
public class Test
public static void main(String[] args)
MyThread thread = new MyThread();
thread.setName("myThread");
thread.start();
for (int i = 0; i < 10000; i++)
System.out.println("main=" + Thread.currentThread().getName());
Thread.java中的start()方法通知“线程规划器”——此线程已经准备就绪,准备调用线程对象的run()方法。如果调用thread.run()方法,而不是thread.start()方法,其实就不是异步执行了,而是同步进行。
多线程随机输出的原因是CPU将时间片分给不同的线程,线程获得时间片后就执行任务。时间片即CPU分配给各个程序的时间。每个线程被分配一个时间片,在当前的时间片内CPU去执行线程中的任务。CPU在不同的线程进行切换是需要耗时的,所以并不是创建的线程越多,运行效率就越高。
3.实现Runnable接口
如果想创建的线程类已经有一个父类了,就不能再继承Thread类,所以需要实现Runnable接口来解决这样的情况。
创建一个实现Runnable接口的MyRunnable类,代码如下:
package myrunnable;
public class MyRunnable implements Runnable
@Override
public void run()
System.out.println("运行中!");
运行类代码如下:
package test;
import myrunnable.MyRunnable;
public class Run
public static void main(String[] args)
Runnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
System.out.println("运行结束!");
4.使用Runnable接口实现多线程的优点
首先创建业务A类:
package service;
public class AServer
@Override
public void a_save_method()
System.out.println("a中的保存数据方法被执行");
再创建业务B类:
package service;
public class BServer extends AServer implements Runnable
@Override
public void a_save_method()
System.out.println("b中的保存数据方法被执行");
@Override
public void run()
b_save_method();
通过实现Runnable接口,可间接地实现“多继承”的效果。在非多继承的情况下,使用继承Thread类和实现Runnable接口两种方式在去的程序运行的结果上没什么太大区别,一旦出现“多继承”的情况,则采用实现Runnable接口的方式来处理多线程问题是很必要的。
5.实例变量共享造成的非线程安全问题与解决方案
自定义线程类中的实例变量针对其他线程可以有共享和不共享之分,在多个线程之间交互时是很重要的技术点。
不共享数据的情况
每个线程都有各自的变量,各自控制自己的变量,变量不共享,不存在多个线程访问同一个变量的问题。
共享数据的情况
共享数据的情况就是多个线程可以访问同一个变量,不同线程可能同时对一个变量进行处理,就会产生“非线程安全问题”。可以通过在run()方法前加入synchronized关键字,使多个线程在执行run()方法时,以排队的方式进行处理。synchronized关键字可以对任意对象及方法加锁,而加锁的代码被称为“互斥区”或“临界区”。
当一个线程想要执行同步方法里面的代码时,就会首先尝试申请这把锁,如果申请到这把锁,则执行互斥区代码;若申请不到,就会一直申请这把锁,直到申请到为止,而且多个线程会争抢这把锁。
6.Servlet技术造成的非线程安全问题与解决方案
非线程安全问题主要是指多个线程对同一个对象中的同一个实例变量进行操作时会出现值被更改,值不同步的情况,影响程序执行流程。示例如下:
package controller;
public class LoginServlet
private static String usernameRef;
private static String passwordRef;
public static void doPost(String username, String password)
try
usernameRef = username;
if (username.equals("a"))
Thread.sleep(5000);
passwordRef = password;
System.out.println("username=" + usernameRef + " password=" + password);
catch (InterruptedException e)
// TODO Auto-generated catch block
e.printStackTrace();
/*
运行结果1:
b bb
a aa
运行结果2:
a bb
a aa
*/
线程ALogin.java代码如下:
package extthread;
import controller.LoginServlet;
public class ALogin extends Thread
@Override
public void run()
LoginServlet.doPost("a", "aa");
线程BLogin.java代码如下:
package extthread;
import controller.LoginServlet;
public class BLogin extends Thread
@Override
public void run()
LoginServlet.doPost("b", "bb");
运行类Run.java代码如下:
package test;
import extthread.ALogin;
import extthread.BLogin;
public class Run
public static void main(String[] args)
ALogin a = new ALogin();
a.start();
BLogin b = new BLogin();
b.start();
解决如上“非线程安全”问题同样可以使用synchronized关键字,更改代码如下:
package controller;
public class LoginServlet
private static String usernameRef;
private static String passwordRef;
synchronized public static void doPost(String username, String password)
try
usernameRef = username;
if (username.equals("a"))
Thread.sleep(5000);
passwordRef = password;
System.out.println("username=" + usernameRef + " password=" + password);
catch (InterruptedException e)
// TODO Auto-generated catch block
e.printStackTrace();
/*
输出结果:
a aa
b bb
*/
currentThread()方法
currentThread()
方法可返回代码段正在被哪个线程调用。
创建MyThread类,代码如下:
package mythread;
public class MyThread extends Thread
public MyThread()
System.out.println("构造方法的打印:" + Thread.currentThread().getName());
@Override
public void run()
System.out.println("run方法的打印:" + Thread.currentThread().getName());
运行类代码如下:
package run;
import mythread.MyThread;
public class Run
public static void main(String[] args)
MyThread myThread = new MyThread();
myThread.start();
/*
构造方法的打印: main
run方法的打印: Thread—0
*/
MyThread.java类的构造函数是被main线程调用的,而run方法是被Thread-0线程调用的,run()方法是自动调用的方法。
更改代码如下:
package run;
import mythread.MyThread;
public class Run
public static void main(String[] args)
MyThread myThread = new MyThread();
//myThread.start();
myThread.run();
/*
构造方法的打印: main
run方法的打印: main
*/
执行方法run()和start()的区别
1)my.run();:立即执行run()方法,不启动新的线程。
2)my.start();:执行run()的时机不确定,启动新的线程
isAlive方法
isAlive()
方法的功能是判断当前的线程是否存活.
测试代码如下:
package mythread;
public class MyThread extends Thread
@Override
public void run()
System.out.println("run==" + this.isAlive());
package run;
import mythread.MyThread;
public class Run
public static void main(String[] args)
MyThread myThread = new MyThread();
System.out.println("begin==" + myThread.isAlive());
myThread.start();
System.out.println("end==" + myThread.isAlive());
/*
begin==false
end==true
run==true
*/
isAlive()方法的作用是测试线程是否处于活动状态。线程已经启动且尚未终止的状态即活动状态。如果线程处于正在运行或准备开始运行的状态,就认为线程是“存活”的。对于代码:
System.out.println(“end==” + myThread.isAlive());
虽然其输出的值是true,但此值是不确定的,因为此时myThread线程还未执行完毕,如果将代码更改如下:
package run;
import mythread.MyThread;
public class Run
public static void main(String[] args) throws InterruptedException
MyThread myThread = new MyThread();
System.out.println("begin==" + myThread.isAlive());
myThread.start();
Thread.sleep(1000);
System.out.println("end==" + myThread.isAlive());
则代码System.out.println(“end==” + myThread.isAlive());
输出结果为false,因为myThread对象已经在1s之内执行完毕。
sleep(long millis)方法
sleep方法的使用是在指定的时间(毫秒)内让当前“正在执行的线程”休眠(暂停执行),这个“正在执行的线程”是指this.currentThread()返回的线程。
示例代码,类Mythread.java代码:
package mythread;
public class MyThread extends Thread
@Override
public void run()
try
System.out.println("run threadName="
+ this.currentThread().getName() + " begin="
+ System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("run threadName="
+ this.currentThread().getName() + " end="
+ System.currentTimeMillis());
catch (InterruptedException e)
// TODO Auto-generated catch block
e.printStackTrace();
运行类Run1.java代码如下:
package run;
import mythread.MyThread;
public class Run1
public static void main(String[] args)
MyThread myThread = new MyThread();
System.out.println("begin=" + System.currentTimeMillis());
myThread.run();
System.out.println("end=" + System.currentTimeMillis());
/*
begin=1604938554575
run threadName=main begin=1604938554582
run threadName=main end=1604938556589
end=1604938556589
*/
运行类Run2.java代码如下:
package run;
import mythread.MyThread;
public class Run
public static void main(String[] args)
MyThread myThread = new MyThread();
System.out.println("begin=" + System.currentTimeMillis());
myThread.start();
System.out.println("end=" + System.currentTimeMillis());
/*
begin=1604938758015
end=1604938758021
run threadName=Thread-0 begin=1604938758021
run threadName=Thread-0 end=1604938760033
*/
直接调用run()方法,main线程和MyThread线程同步执行。
使用start()方法启动线程,由于main线程与MyThread线程是异步执行的,所以首先输出的信息为begin和end,而MyThread线程是后执行的,在最后两行间隔了2s输出run…begin和run…end相关的信息。
sleep(long millis, int nanos)方法
sleep(long millis, int nanos)
方法的作用是在指定的毫秒数加指定的纳秒数内让当前正在运行的线程休眠(停止执行),此操作受到系统计时器和调度程序的精度和准确性的影响。
StackTraceElement[] getStackTrace()方法
StackTraceElement[] getStackTrace()
方法的作用是返回一个表示该线程堆栈跟踪元素的数组。如果该线程尚未启动或已经终止,则该方法返回一个零长度数组。如果返回的数组不是零长度的,则其第一个元素代表堆栈顶,它是该数组中最新的方法调用。最后一个元素代表堆栈底,是该数组中最旧的方法调用。
static void dumpStack()方法
static void dumpStack()
方法的作用的将当前线程的堆栈跟踪信息输出至标准错误流。该方法仅用于调试。
getId()方法
getId()
方法用于取得线程的唯一标识。
三、停止线程
停止一个线程意味着在线程处理完任务之前停止正在做的操作,也就是放弃当前的操作,必须做好防范措施,以便达到预期的效果。停止一个线程可以使用Thread.stop()
方法,但是这个方法是不安全的,而且是被弃用的。
大多数情况下,停止一个线程使用Thread.interrupt()方法,但这个方法不会终止一个正在运行的线程,还需要加入一个判断才可以完成线程的停止。
在Java中有 3 种方法可以使正在运行的线程终止运行:
1)使用退出标志使线程正常退出。
2)使用stop()方法强制终止线程。
3)使用interrupt()方法终止线程。
1. 终止不了的线程
调用interrupt()方法仅仅是在当前线程中做了一个停止的标志,不是真正的停止线程。
2. 判断线程是否为停止状态
Thread.java 类提供了两个判断方法: 以上是关于Java多线程编程——多线程技能的主要内容,如果未能解决你的问题,请参考以下文章
1)public static boolean interrupted(): 测试currentThread() 是否已经中断。
2)public boolean this.isInterrupted(): 测试 this 关键字所在类的对象是