java多线程-多线程技能
继承Thread类
一个类继承Thread类后创建新线程
package mycode.chapter1;
/**
* @author 29467
* @date 2022/9/2 22:30
*/
public class MyThread extends Thread{
@Override
public void run() {
super.run();
System.out.println("MyThread");
}
}
package mycode.chapter1;
/**
* @author 29467
* @date 2022/9/2 15:12
*/
public class Run3 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
System.out.println("运行结束!");
}
}
运行结果可以看出,新线程创建比运行结束!文字输出要慢
运行结束!
MyThread
如果让主线程休眠200ms,那样顺序将会反过来
package mycode.chapter1;
/**
* @author 29467
* @date 2022/9/2 15:12
*/
public class Run3 {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
// 主线程休眠三秒
Thread.sleep(200);
System.out.println("运行结束!");
}
}
运行结果
MyThread
运行结束!
使用常见的命令分析现成的信息
- 使用jps+jstack,在java的bin目录下打开cmd,运行以下代码后输入jps即可看到现成的状态,输入jstack -l 进程id后可以看指定id的状态
package mycode.chapter1;
/**
* @author 29467
* @date 2022/9/2 22:40
*/
public class Run {
public static void main(String[] args) {
for(int i = 0; i<5; i++){
new Thread(){
@Override
public void run() {
try {
Thread.sleep(500000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}.start();
}
}
}
- 利用官方工具jmc查看
- 利用工具jvisualvm查看
线程随机性的展现
package mycode.chapter1;
/**
* @author 29467
* @date 2022/9/2 22:50
*/
public class MyThread2 extends Thread{
@Override
public void run() {
for(int i =0; i<10000;i++){
System.out.println("run="+Thread.currentThread().getName());
}
}
}
package mycode.chapter1;
/**
* @author 29467
* @date 2022/9/2 22:51
*/
public class Run2 {
public static void main(String[] args) {
MyThread2 thread2 = new MyThread2();
thread2.setName("thread2");
thread2.start();
for (int i = 0; i < 10000; i++) {
System.out.println("main=" + Thread.currentThread().getName());
}
}
}
通过输出可见,main和thread2两个线程随机打印
执行start()的顺序不代表执行run()的顺序
实现Runnable接口
package mycode.chapter1;
/**
* @author 29467
* @date 2022/9/2 23:03
*/
public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("运行中");
}
}
package mycode.chapter1;
/**
* @author 29467
* @date 2022/9/2 23:04
*/
public class Run4 {
public static void main(String[] args) {
Runnable runnable =new MyRunnable();
Thread thread =new Thread(runnable);
thread.start();
System.out.println("运行结束");
}
}
使用Runnable接口实现多线程的优点
可以实现接口来达到“多继承”的功能
实现Runnable接口与继承Thread类的内部流程
实现Runnable接口法在执行过程上相比继承Thread法稍微复杂一些
实例变量共享造成的非线程安全问题与解决方案
- 不共享数据的情况
package mycode.chapter1;
/**
* @author 29467
* @date 2022/9/2 23:22
*/
public class MyThread3 extends Thread{
private int count = 5;
public MyThread3(String name) {
super();
this.setName(name);
}
@Override
public void run() {
super.run();
while (count>0){
count--;
System.out.println("由"+ currentThread().getName()+"计算,count="+count);
}
}
}
package mycode.chapter1;
/**
* @author 29467
* @date 2022/9/2 23:26
*/
public class Run5 {
public static void main(String[] args) {
MyThread3 a = new MyThread3("A");
MyThread3 b = new MyThread3("B");
MyThread3 c = new MyThread3("C");
a.start();
b.start();
c.start();
}
}
从运行结果可见,他们的count值是非共享的,每个线程单独负责自己的count
由C计算,count=4
由A计算,count=4
由B计算,count=4
由A计算,count=3
由C计算,count=3
由A计算,count=2
由C计算,count=2
由B计算,count=3
由C计算,count=1
由A计算,count=1
由C计算,count=0
由B计算,count=2
由A计算,count=0
由B计算,count=1
由B计算,count=0
- 共享数据的情况
package mycode.chapter1;
/**
* @author 29467
* @date 2022/9/2 23:22
*/
public class MyThread4 extends Thread{
private int count = 5;
@Override
public void run() {
super.run();
while (count>0){
count--;
System.out.println("由"+ currentThread().getName()+"计算,count="+count);
}
}
}
package mycode.chapter1;
/**
* @author 29467
* @date 2022/9/2 23:30
*/
public class Run6 {
public static void main(String[] args) {
MyThread4 myThread4 = new MyThread4();
Thread a = new Thread(myThread4,"A");
Thread b = new Thread(myThread4,"B");
Thread c = new Thread(myThread4,"C");
Thread d = new Thread(myThread4,"D");
Thread e = new Thread(myThread4,"E");
a.start();
b.start();
c.start();
d.start();
e.start();
}
}
这样的话多个线程将会共用数据,因为不加锁,会导致一系列问题,改进方案如下:添加关键字synchronized在run()方法前
package mycode.chapter1;
/**
* @author 29467
* @date 2022/9/2 23:22
*/
public class MyThread4 extends Thread{
private int count = 5;
// 添加关键字synchronized
@Override
synchronized public void run() {
super.run();
while (count>0){
count--;
System.out.println("由"+ currentThread().getName()+"计算,count="+count);
}
}
}
Servlet技术造成的非线程安全问题与解决方案
多线程调用servlet会导致赋值时机错误问题,可在线程调用的servlet方法前添加synchronized关键字加锁解决
留意System.out.println()出现的非线程安全问题
println()方法在内部是同步的,作用范围仅限内部
currentThread()方法
返回代码段正在被哪个线程调用
isAlive()方法
判断当前的线程是否存活
sleep(long millis)方法
sleep()方法能让“正在执行的进程”休眠x毫秒
sleep(long million, int nanos)方法
毫秒+纳秒
StackTraceElement[] getStackTrace()方法
返回一个表示该线程的堆栈跟踪元素数组。第一个元素表示栈顶,是该数组中的最新方法调用,最后一个元素是栈底,表示最早的方法调用。
static void dumpStack()方法
将当前线程的堆栈跟踪信息输出至标准错误流。
static Map<Thread,StackTraceElement[]> geyAllStackTrace()方法
返回所有活动线程的堆栈跟踪的一个映射。
getId()方法
可以取得线程的唯一标识
停止线程
- interrupted():测试currentThread()是否已经中断,执行后具有清除状态标志值的功能
- isInterrupted():测试this关键字所在类的对象是否已经中断,不清除状态标志值
用stop()方法暴力停止线程
该方法并不能确定停止的位置,调用时会抛出java.lang.ThreadDeath异常,不需要显式捕捉。
使用stop()释放锁给数据造成不一致的结果
使用"return"语句停止线程的缺点与解决方案
多个判断条件下,使用return会导致代码冗余,推荐抛异常throw new InterruptedException()
暂停线程
使用suspend()方法暂停线程,使用resume()方法来恢复线程的执行。
suspend()方法与resume()方法的缺点—独占
在独占期间,其他线程无法访问
suspend()与resume()方法的缺点—数据不完整
yield()方法
yield()方法的作用是放弃当前的CPU资源,让其他任务去占用CPU执行时间,放弃的时间不确定
线程的优先级
优先级较高的线程获得的cpu资源更多,设置线程的优先级用setPriority()方法,优先级分为1-10个等级,超出范围会报错
线程优先级的继承特性
线程的优先级具有继承性,线程继承后优先级相同
优先级的规律性
优先级可以用setPriority()方法设置,cpu尽量给优先级较高的线程
优先级的随机性
优先级高的线程并不是先执行完毕
优先级对线程运行速度的影响
优先级高的运行速度快
守护线程
Java中分为用户线程(非守护线程),和守护线程
当进程中不存在非守护线程了,则守护线程自动销毁
凡是调用setDaemon(true)代码并且传入值的线程才是守护线程