java三猴分桃多线程_Java多线程知识总结

本文介绍了多线程的基本概念及其在程序中的作用,包括如何利用多线程提高程序执行效率,同时也探讨了多线程可能导致的问题。此外,还详细讲解了Java中创建多线程的三种方式以及Thread类中的常用方法。

多线程的作用(优点)

多线程就是为了充分利用cpu的资源,可能会提高程序执行效率。这里强调的是可能,使用多线程并不一定会提高程序的运行效率。

例如去超市结账时需要排队:

不使用多线程时,执行下图中的操作要耗费153秒,即先排队等待结账耗费100秒,结账后,给老婆发微信耗费3秒,之后给母亲打电话耗费50秒。

3b177c319edfeca74da0c3975b05d720.png

使用多线程时,执行下图中的操作会耗费100秒,在排队等待结账的过程中,给老婆发微信耗费3秒,之后给母亲打电话耗费50秒,这里虽然耗费了53秒,但是这是在排队等待结账的空闲时间操作中执行的,所以执行完这些操作总共只耗费了100秒

89c6b7d3120cb905d1eadba505a6f532.png

多线程的缺点

线程多的话,cpu会频繁的在线程之间切换,影响性能,可能也会出现一些bug。

想象你正在结账的时候,要给母亲打电话,同时还得给老婆发微信,此时要频繁的在结账、打电话、发微信三个操作之间进行切换,这样可能就会影响执行效率了。

c801354ffbf398f40eb0887c4f11369a.png

通过下面代码示例演示一下使用多线程不一定会提高性能:

创建一个带有main方法的类,写两个for循环,分别打印一些字符串,然后统计一下该操作总共耗时:

public class Test01 {

public static void main(String[] args) {

//JDK8新增的时间处理类

//记录代码开始时的系统时间

LocalTime begin = LocalTime.now();

for (int i = 0; i < 10000; i++) {

System.out.println("monkey1024");

}

for (int i = 0; i < 10000; i++) {

System.out.println("java");

}

//记录代码结束时的系统时间

LocalTime end = LocalTime.now();

Duration time = Duration.between(begin, end);

System.out.println("共耗时:" + time.toMillis());

}

}

之后创建一个多线程的程序,使用多线程的方式来执行上面程序的操作:

public class MyThread extends Thread {

@Override

public void run() {

for (int i = 0; i < 10000; i++) {

System.out.println("monkey1024");

}

}

}

修改一下Test01中的main方法:

import java.time.Duration;

import java.time.LocalTime;

public class Test01 {

public static void main(String[] args) {

MyThread mt = new MyThread();

//JDK8新增的时间处理类

//记录代码开始时的系统时间

LocalTime begin = LocalTime.now();

mt.start();

for (int i = 0; i < 10000; i++) {

System.out.println("java");

}

//记录代码结束时的系统时间

LocalTime end = LocalTime.now();

Duration time = Duration.between(begin, end);

System.out.println("共耗时:" + time.toMillis());

}

}

以上程序的运行结果可能根据机器的差异而不同,我的机器配置是i5+8g内存,上面使用多线程执行的时间一般会比不使用多线程的执行的时间长一点。

创建多线程的三种方式

继承Thread

优点:可以直接使用Thread类中的方法,代码简单

缺点:继承Thread类之后就不能继承其他的类

实现Runnable接口

优点:即时自定义类已经有父类了也不受影响,因为可以实现多个接口

缺点:在run方法内部需要获取到当前线程的Thread对象后才能使用Thread中的方法

实现Callable接口

优点:可以获取返回值,可以抛出异常

缺点:代码编写较为复杂

Thread类中常用的方法

start()

启动线程,该方法调用后,线程不会立即执行,当JVM调用run方法时才会真正执行。举例:start是开车前的打火操作,此时汽车不会走的。run方法相当于挂挡抬离合

Thread t = new Thread(){

@Override

public void run() {

System.out.println("monkey1024");

}

};

t.start();

setName(String name)

设置线程的名字

getName()

获取线程的名字

Thread t = new Thread(){

@Override

public void run() {

System.out.println(this.getName());

}

};

t.start();

t.setName("小强");

System.out.println(t.getName());

currentThread()

获取当前线程的对象,在使用实现Runnable接口去创建线程的时候,就可以使用该方法获取线程的对象了。

Runnable r = new Runnable() {

@Override

public void run() {

System.out.println(Thread.currentThread().getName());

}

};

Thread t = new Thread(r);

t.setName("runnable线程");

t.start();

setPriority(int newPriority)

设置线程的优先级,1~10之间的整数,数字越大优先级越高。

Runnable r = new Runnable() {

@Override

public void run() {

for (int i = 0; i < 100; i++) {

System.out.println(Thread.currentThread().getName());

}

}

};

Thread t1 = new Thread(r);

t1.setName("runnable线程1");

t1.setPriority(1);

Thread t2 = new Thread(r);

t2.setName("runnable线程2");

t2.setPriority(10);

t1.start();

t2.start();

sleep(long millis)

使当前线程睡眠,线程睡眠后,里面的任务不会执行,待睡眠时间过后会自动苏醒,从而继续执行任务。下面代码每隔2秒打印一次monkey1024

for (int i = 0; i < 10; i++) {

Thread.sleep(2000);

System.out.println("monkey1024");

}

interrupt()

如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true。被设置中断标志的线程可能不会立即终端,而是继续正常运行,不受影响。小时后家人叫你回家吃饭,你可以选择在外面继续玩耍一会之后再回去吃饭。

Runnable r = new Runnable() {

@Override

public void run() {

for (int i = 0; i < 1000; i++) {

System.out.println(i);

}

}

};

Thread t1 = new Thread(r);

t1.setName("runnable线程1");

t1.setPriority(1);

t1.start();

Thread.sleep(2000);

t1.interrupt();

唤醒正在睡眠的线程,调用interrupt方法会抛出一个InterruptedException的异常

Runnable r = new Runnable() {

@Override

public void run() {

for (int i = 0; i < 1000; i++) {

try {

Thread.sleep(2000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(i);

}

}

};

Thread t1 = new Thread(r);

t1.setName("runnable线程1");

t1.setPriority(1);

t1.start();

t1.interrupt();

Thread类中的stop方法已经不建议使用了,该方法过于暴力。而上面中的interrupt方法并不能停止线程,那么该如何正确的停止线程呢?Thread类中有一个isInterrupted()方法,它会返回一个boolean类型的值,当调用interrupt()方法之后,isInterrupted()方法会返回true。我们可以在多线程的代码中添加判断,当isInterrupted()方法会返回true时,手动的抛出一个异常,通过这种方式去停止线程。

Runnable r = new Runnable() {

@Override

public void run() {

try {

for (int i = 0; i < 10000; i++) {

if (Thread.currentThread().isInterrupted()) {

System.out.println("需要将线程停止");

throw new InterruptedException();

}

System.out.println(i);

}

} catch (InterruptedException e) {

System.out.println("线程停止了");

e.printStackTrace();

}

}

};

Thread t1 = new Thread(r);

t1.setName("runnable线程1");

t1.setPriority(1);

t1.start();

Thread.sleep(100);

t1.interrupt();

yield()

当前线程在执行该方法之后会进行礼让。即本来CPU会执行A线程,但是在A线程中调用了yield()方法,此时CPU会放弃A线程的执行权,但是放弃的时间不确定,有可能刚放弃,A线程马上又获得了CPU的执行权。

举例:坐公交车或地铁时,看到老人上车后,你会起身让座,从你起身到老人坐下,这段时间是不确定的,并且也有可能你刚起身让座,老人表示一站就到目的地不想做了,此时你会继续坐回座位上。Runnable r = new Runnable() {

@Override

public void run() {

LocalTime begin = LocalTime.now();

for (int i = 0; i < 10000; i++) {

//将执行权让给别的线程

Thread.yield();

System.out.println(i);

}

LocalTime end = LocalTime.now();

Duration time = Duration.between(begin, end);

System.out.println("耗时:" + time.toMillis());

}

};

Thread t1 = new Thread(r);

t1.start();

join()

线程加入,可以理解为两个线程的合并,有两个线程A和B,A线程需要等B线程执行完成之后再执行,此时就可以使用join方法。当B线程调用join方法后,A线程内部相当于调用了wait方法进入到等待状态,直到B线程执行结束后,A线程才会被唤醒。这时A和B两个线程可以看成合并为一个线程而进行同步执行。

Runnable r = new Runnable() {

@Override

public void run() {

Random r = new Random();

int second = r.nextInt(10) * 1000;

System.out.println(second);

try {

Thread.sleep(second);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

};

Thread t1 = new Thread(r);

t1.start();

//将t1线程加入,此时main主线程会进入wait状态进入等待

t1.join();

System.out.println("我希望等t1线程结束后再打印");

Java多线程的关键字

volatile

保证多个线程之间变量的可见性,每个线程在执行的时候会有自己独有的一块内存区域,假设有A,B两个线程,他们可以访问堆内存中一个int 类型的变量age,系统为了提高执行性能,当A线程访问变量age的时候,会将该变量age在A线程自己的内存区域中拷贝一份,以后线程执行的时候,都会去自己内存区域中访问变量age。此时倘若B线程修改了堆内存中的变量age之后,A线程是无法获知的,这样就导致了数据不一致的问题。

为了解决上面的问题,可以使用volatile关键字修饰堆内存中的变量age,这样A线程每次访问age的时候都会去堆内存访问,而不会在自己的内存区域中拷贝age了。

567ea5e5ec24f998f563266067e0b1f6.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值