Java多线程总结
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了Java多线程总结,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含11637字,纯文字阅读大概需要17分钟。
内容图文
![Java多线程总结](/upload/InfoBanner/zyjiaocheng/617/de420c5b23934b5aac5139a636d1daa5.jpg)
创建线程的三种方式
1.继承Thread类
public class ThreadTest01 extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("run--->" + i);
}
}
public static void main(String[] args) {
Thread thread = new ThreadTest01();
thread.start();
for (int i = 0; i < 10; i++) {
System.out.println("main====>" + i);
}
}
}
不建议使用,避免OOP单继承的局限性
2.实现Runnable接口
public class ThreadTest02 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("run--->" + i);
}
}
public static void main(String[] args) {
ThreadTest02 t = new ThreadTest02();
Thread thread = new Thread(t);
thread.start();
for (int i = 0; i < 10; i++) {
System.out.println("main====>" + i);
}
}
}
推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
使用匿名内部类简化代码:
interface Test {
void fun();
}
public class AnonymousClassesTest {
public static void main(String[] args) {
// 匿名内部类
Test test = new Test() {
public void fun() {
System.out.println("匿名内部类");
}
};
test.fun(); // 输出:匿名内部类
// 使用匿名内部类创建线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("abc");
}
}).start(); // abc
}
使用lambda表达式简化代码:
任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。对于函数式接口,我们可以通过lambda表达式来创建该接口的对象。
interface Test {
void fun();
}
public class LambdaTest {
public static void main(String[] args) {
// lambda表达式
// 花括号中的内容相当于重写接口中的方法,代码只有一行时,可以去掉花括号
test = () -> {
System.out.println("i like lambda");
};
test.fun(); // 输出:i like lambda
// 使用lambda表达式创建线程
new Thread( () -> System.out.println("abc") ).start(); // abc
}
}
3.实现Callable接口
public class ThreadTest04 implements Callable<String> {
@Override
public String call() throws Exception {
return "返回值";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<String> c1 = new ThreadTest04();
Callable<String> c2 = new ThreadTest04();
Callable<String> c3 = new ThreadTest04();
// 创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(3);
// 提交执行
Future<String> f1 = ser.submit(c1);
Future<String> f2 = ser.submit(c2);
Future<String> f3 = ser.submit(c3);
// 获取结果
System.out.println(f1.get());
System.out.println(f2.get());
System.out.println(f3.get());
// 关闭服务
ser.shutdown();
}
}
线程的优先级
在Java线程中,通过一个整型成员变量priority来控制优先级,优先级从低到高是1 ~ 10,默认的优先级是5。可以通过 setPriority(int) 来设置优先级。优先级高的线程分配的时间片的数量要多于优先级低的线程。但是线程优先级不能作为程序正确性的依赖。
java线程的6种状态
- NEW :初始状态,线程被构建,但是还没有调用start()方法
- RUNNABLE :运行状态,Java线程将操作系统中的就绪和运行两种状态笼统的称为“运行中”
- BLOCKED :阻塞状态,表示线程阻塞于锁
- WAITING :表示线程进行等待状态,进入该状态表示当前线程需要等待其它线程做出一些特定动作(通知或中断)
- TIME_WAITING :超时等待状态,该状态不同于WAITING,它是可以在指定的时间自行返回的
- TERMINATED : 终止状态,表示当前线程已经执行完毕
阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态,但是阻塞在java.concurrent包中的Lock接口的线程却是等待状态,因为java.concurrent包中的Lock接口对于阻塞的实现均使用了LockSupport类中的相关方法。
Daemon线程
Daemon(守护线程)是一种支持型线程,因为它主要被作用程序中后台调度以及支持性工作。这意味着,当一个Java虚拟机中不存在非Daemon线程的时候,Java虚拟机将会退出。所以在构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑,因为finally块中的内容不一定会执行。可以调用 setDaemonn(true)(参数默认是false)将线程设置为Daemon线程。
public class TestDaemon {
public static void main(String[] args) {
Thread thread = new Thread(new MyDaemon(), "守护线程");
thread.setDaemon(true);
thread.start();
}
}
class MyDaemon implements Runnable {
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("我是守护线程");
}
}
}
执行上述代码会发现没有任何打印。
中断
一个线程执行完毕之后会自动结束,如果在运行过程中发生异常也会提前结束。
通过调用一个线程的 interrupt() 来中断该线程,如果该线程处于阻塞、等待或者超时等待状态,那么就会抛出 InterruptedException,从而提前结束该线程。但是不能中断 I/O 阻塞和 synchronized 锁阻塞。
线程通过方法isInterrupted()来进行判断是否被中断,如果该线程已经处于终止状态,即使该线程被中断过,方法的返回结果依旧为false。
public class Interrupted {
public static void main(String[] args) throws InterruptedException {
// sleepThread不断尝试睡眠
Thread sleepThread = new Thread(new SleepRunner(), "sleepThread");
// BusyThread不停运行
Thread busyThread = new Thread(new BusyRunner(), "busyThread");
sleepThread.setDaemon(true);
busyThread.setDaemon(true);
sleepThread.start();
busyThread.start();
// 休眠5秒,让sleepThread和BusyThread充分运行
Thread.sleep(5000);
sleepThread.interrupt();
busyThread.interrupt();
System.out.println("sleepThread interrupted is " + sleepThread.isInterrupted()); // false
System.out.println("busyThread interrupted is " + busyThread.isInterrupted()); // true
// 防止sleepThread和BusyThread立刻退出
Thread.sleep(2000);
}
}
class SleepRunner implements Runnable {
@Override
public void run() {
while (true) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class BusyRunner implements Runnable {
@Override
public void run() {
while (true) {
}
}
}
运行结果:
sleepThread interrupted is false
busyThread interrupted is true
java.lang.InterruptedException: sleep interrupted
sleepThread处于超时等待状态,被中断会抛出异常并且提前结束该线程,此时判断是否被中断,返回为false。
等待/通知机制
一个线程修改了一个对象的值,而另一个线程感知到了变化,然后进行相应的操作,整个过程开始于一个线程,而最终执行又是另一个线程。前者是生产者,后者就是消费者,这种模式隔离了“做什么”(what)和“怎么做”(how),在功能层面上实现了解耦,体系结构上具备良好的伸缩性。
Java通过等待/通知机制实现类似的功能。等待/通知相关的方法是任意Java对象都具备的,因为这些方法被定义在所有对象的超类java.lang.Object上,方法如下:
- notify() :通知一个在对象上等待的线程,使其从wait()方法返回,而返回的前提是该线程获取到了对象的锁
- notifyAll() :通知所有等待在该对象上的线程
- wait() :调用该方法的线程进入WAITING状态,只有等待另外线程的通知或被中断才会返回,需要注意,调用wait()方法后,会释放对象的锁。
- wait(long) :超过一段时间,这里的参数是毫秒,也就是等待长达n毫秒,如果没有通知就超时返回。
- wait(long, int) :对于超时时间更细粒度的控制,可以达到纳秒。
等待/通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用的对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。
import java.text.SimpleDateFormat;
import java.util.Date;
public class WaitNotify {
static Object lock = new Object();
static boolean flag = true;
public static void main(String[] args) throws InterruptedException {
Thread waitThread = new Thread(new Wait(), "waitThread");
waitThread.start();
Thread.sleep(1000);
Thread notifyThread = new Thread(new Notify(), "notifyThread");
notifyThread.start();
}
static class Wait implements Runnable {
@Override
public void run() {
synchronized (lock) {
// 条件不满足时,继续wait,同时释放锁
while (flag) {
try {
System.out.println(Thread.currentThread().getName() + " flag is true wait @ " +
new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 条件满足,完成工作
System.out.println(Thread.currentThread().getName() + "flag is false running @ " +
new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
}
static class Notify implements Runnable {
@Override
public void run() {
synchronized (lock) {
// 获取lock的锁,然后进行通知,通知时不会释放lock的锁
System.out.println(Thread.currentThread().getName() + "hold lock notify @ " +
new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.notifyAll();
flag = false;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 再次加锁
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + "hold lock again @ " +
new SimpleDateFormat("HH:mm:ss").format(new Date()));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
执行结果:
waitThread flag is true wait @ 17:33:39
notifyThreadhold lock notify @ 17:33:40
notifyThreadhold lock again @ 17:33:42
waitThreadflag is false running @ 17:33:44
第三行和第四行的输出顺序可能会互换。
上述例子说明调用wait()、notify()以及notifyAll()时需要注意的细节:
- 使用wait()、notify()和notifyAll()需要先对调用对象加锁。
- 调用wait()方法后,线程状态由RUNNING变成WAITING,并将当前线程放置到对象的等待队列。
- notify()或notifyAll()调用后,等待线程依旧不会从wait()放回,需要调用notify()或notifyAll()的线程释放锁之后,等待线程才有机会从wait()放回。
- notify()方法将等待队列中的一个等待线程从等待队列中移动到同步队列中,而notifyAll()方法将等待队列中的所有的线程全部移到同步队列,被移动的线程状态由WAITING变成BLOCKED。
- 从wait()方法返回的前提是获得了调用对象的锁。
wait、notify图解:
等待/通知的经典范式
等待方(消费者)遵守如下原则:
- 获取对象的锁
- 如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件
- 条件满足则执行对应的逻辑
// 对应的伪代码
synchronized(对象) {
while(条件不满足) {
对象.wait();
}
对应的处理逻辑
}
通知方(生产者)遵守如下原则:
- 获得对象的锁
- 改变条件
- 通知所有等待在对象上的线程
synchronized(对象) {
改变条件
对象.notifyAll();
管道输入/输出流
管道输入/输出流和普通的文件输入/输出流或者网络输入/输出流不同之处在于,它主要用于线程之间的数据传输,而传输媒介为内存。
管道输入/输出流包括4种具体的实现:PipedInputStream、PipedOutputStream、PipedReader和PipedWriter,前两种面向字节,而后两种面向字符。
public class Piped {
public static void main(String[] args) throws IOException {
PipedReader reader = new PipedReader();
PipedWriter writer = new PipedWriter();
// 将输入流和输出流进行连接,否则在使用时会抛出IOException
reader.connect(writer);
Thread printThread = new Thread(new Print(reader), "printThread");
printThread.start();
int receive = 0;
try {
while ((receive = System.in.read()) != -1) {
writer.write(receive);
}
} finally {
writer.close();
}
}
}
class Print implements Runnable {
private PipedReader reader;
public Print(PipedReader reader) {
this.reader = reader;
}
@Override
public void run() {
int receive = 0;
try {
while ((receive = reader.read()) != -1) {
System.out.print((char) receive);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行程序,输入字符串,可以看见字符串被原样输出:
Hello World!
Hello World!
Thread.join()的使用
如果一个线程A执行了thread.join()语句,其含义是,当前线程A等待thread线程终止之后才从thread.join()中返回。线程Thread()除了提供join()方法之外,还提供了join(long millis)和join(long millis, int nanos)两个具备超时特性的方法。这两个超时方法表示,如果线程thread在给定的超时时间里没有终止,那么将会从该超时方法中返回。
public class TestJoin {
public static void main(String[] args) {
Thread one = new Thread(new JoinOne(), "线程one");
Thread two = new Thread(new JoinTwo(one), "线程two");
one.start();
two.start();
}
}
class JoinOne implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "---> " + i);
}
}
}
class JoinTwo implements Runnable {
private Thread thread;
public JoinTwo(Thread thread) {
this.thread = thread;
}
@Override
public void run() {
try {
thread.join(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "---> " + i);
}
}
}
执行以上代码会发现,线程two的打印会在线程one结束后开始。
在Thread.join()源码中,join()方法会调用重载的方法join(long millis),传入参数0,所以Thread.join()和Thread.join(0)是一样的。
join方法底层调用的是wait()方法
public final synchronized void join() throws InterruptedException {
// 条件不满足,继续等待
while (isAlive()) {
wait(0);
}
// 条件符合,方法返回
}
内容总结
以上是互联网集市为您收集整理的Java多线程总结全部内容,希望文章能够帮你解决Java多线程总结所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。