彻底搞懂Java多线程(一)
作者:保护眼睛 发布时间:2023-08-02 10:42:30
Java多线程
线程的创建
1.继承Thread
2.实现Runnable
3.实现Callable
使用继承Thread类来开发多线程的应用程序在设计上是有局限性的,因为Java是单继承。
继承Thread类
public class ThreadDemo1 {
// 继承Thread类 写法1
static class MyThread extends Thread{
@Override
public void run() {
//要实现的业务代码
}
}
// 写法2
Thread thread = new Thread(){
@Override
public void run() {
//要实现的业务代码
}
};
}
实现Runnable接口
//实现Runnable接口 写法1
class MyRunnable implements Runnable{
@Override
public void run() {
//要实现的业务代码
}
}
//实现Runnable接口 写法2 匿名内部类
class MyRunnable2 {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//要实现的业务代码
}
});
}
}
实现Callable接口(Callable + FutureTask 创建带有返回值的线程)
package ThreadDeom;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* user:ypc;
* date:2021-06-11;
* time: 17:34;
*/
//创建有返回值的线程 Callable + Future
public class ThreadDemo2 {
static class MyCallable implements Callable<Integer>{
@Override
public Integer call() throws Exception {
return 0;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建Callable子对象
MyCallable myCallable = new MyCallable();
//使用FutureTask 接受 Callable
FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
//创建线程并设置任务
Thread thread = new Thread(futureTask);
//启动线程
thread.start();
//得到线程的执行结果
int num = futureTask.get();
}
}
也可以使用lambda表达式
class ThreadDemo21{
//lambda表达式
Thread thread = new Thread(()-> {
//要实现的业务代码
});
}
Thread的构造方法
线程常用方法
获取当前线程的引用、线程的休眠
class Main{
public static void main(String[] args) throws InterruptedException {
Thread.sleep(1000);
//休眠1000毫秒之后打印
System.out.println(Thread.currentThread());
System.out.println(Thread.currentThread().getName());
}
}
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-11;
* time: 18:38;
*/
public class ThreadDemo6 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程的ID:" + Thread.currentThread().getId());
System.out.println("线程的名称:" + Thread.currentThread().getName());
System.out.println("线程的状态:" + Thread.currentThread().getState());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"线程一");
thread.start();
Thread.sleep(100);
//打印线程的状态
System.out.println("线程的状态:"+thread.getState());
System.out.println("线程的优先级:"+thread.getPriority());
System.out.println("线程是否存活:"+thread.isAlive());
System.out.println("线程是否是守护线程:"+thread.isDaemon());
System.out.println("线程是否被打断:"+thread.isInterrupted());
}
}
线程的等待
假设有一个坑位,thread1 和 thread2 都要上厕所。一次只能一个人上,thread2只能等待thread1使用完才能使用厕所。就可以使用join()方法,等待线程1执行完,thread2在去执行。👇
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 10:48;
*/
public class ThreadDemo13 {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"🚾");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"出来了");
}
};
Thread t1 = new Thread(runnable,"thread1");
t1.start();
//t1.join();
Thread t2 = new Thread(runnable,"thread2");
t2.start();
}
}
没有join()显然是不行的。加上join()之后:
线程的终止
1.自定义实现线程的终止
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 9:59;
*/
public class ThreadDemo11 {
private static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (!flag){
System.out.println("我是 : " + Thread.currentThread().getName() + ",我还没有被interrupted呢");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("我是 "+Thread.currentThread().getName()+",我被interrupted了");
}
},"thread");
thread.start();
Thread.sleep(300);
flag = true;
}
}
2.使用Thread的interrupted来中断
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 9:59;
*/
public class ThreadDemo11 {
// private static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (!Thread.interrupted()){
System.out.println("我是 : " + Thread.currentThread().getName() + ",我还没有被interrupted呢");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// e.printStackTrace();
break;
}
}
System.out.println("我是 "+Thread.currentThread().getName()+",我被interrupted了");
}
},"thread");
thread.start();
Thread.sleep(300);
thread.interrupt();
// flag = true;
}
}
3.Thraed.interrupted()方法和Threaed.currentThread().interrupt()的区别
Thread.interrupted()
方法第一次接收到终止的状态后,之后会将状态复位,Thread.interrupted()
是静态的,是全局的。
Threaed.currentThread().interrupt()
只是普通的方法。
Thraed.interrupted()
方法
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 10:32;
*/
public class ThreadDemo12 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() ->{
for (int i = 0; i < 10; i++) {
System.out.println(Thread.interrupted());
}
});
thread.start();
thread.interrupt();
}
}
Threaed.currentThread().interrupt()
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 10:32;
*/
public class ThreadDemo12 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() ->{
for (int i = 0; i < 10; i++) {
// System.out.println(Thread.interrupted());
System.out.println(Thread.currentThread().isInterrupted());
}
});
thread.start();
thread.interrupt();
}
}
yield()方法
让出CPU的执行权
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 11:47;
*/
public class ThreadDemo15 {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
Thread.yield();
System.out.println("thread1");
}
});
thread1.start();
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println("thread2");
}
});
thread2.start();
}
}
线程的状态
打印出线程的所有的状态,所有的线程的状态都在枚举中。👇
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 11:06;
*/
public class ThreadDemo14 {
public static void main(String[] args) {
for (Thread.State state: Thread.State.values()) {
System.out.println(state);
}
}
}
NEW
创建了线程但是还没有开始工作RUNNABLE
正在Java虚拟机中执行的线程BLOCKED
受到阻塞并且正在等待某个监视器的锁的时候所处的状态WAITTING
无限期的等待另一个线程执行某个特定操作的线程处于这个状态TIME_WAITTING
有具体等待时间的等待TERMINATED
已经退出的线程处于这种状态
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 11:06;
*/
class TestThreadDemo{
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
System.out.println(thread.getState());
thread.start();
System.out.println(thread.getState());
Thread.sleep(100);
System.out.println(thread.getState());
thread.join();
System.out.println(thread.getState());
}
}
线程的优先级
在Java中线程 的优先级分为1 ~ 10 一共十个等级
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-11;
* time: 21:22;
*/
public class ThreadDemo9 {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t1");
}
});
//最大优先级
t1.setPriority(10);
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t2");
}
});
//最小优先级
t2.setPriority(1);
t2.start();
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t3");
}
});
t3.setPriority(1);
t3.start();
}
}
}
线程的优先级不是绝对的,只是给程序的建议。
线程之间的优先级具有继承的特性,如果A线程启动了B线程,那么B的线程的优先级与A是一样的。👇
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-11;
* time: 20:46;
*/
class ThreadA extends Thread{
@Override
public void run() {
System.out.println("ThreadA优先级是:" + this.getPriority());
ThreadB threadB = new ThreadB();
threadB.start();
}
}
class ThreadB extends ThreadA{
@Override
public void run() {
System.out.println("ThreadB的优先级是:" + this.getPriority());
}
}
public class ThreadDemo7 {
public static void main(String[] args) {
System.out.println("main线程开始的优先级是:" + Thread.currentThread().getPriority());
System.out.println("main线程结束的优先级是:" + Thread.currentThread().getPriority());
ThreadA threadA = new ThreadA();
threadA.start();
}
}
再看👇
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-11;
* time: 20:46;
*/
class ThreadA extends Thread{
@Override
public void run() {
System.out.println("ThreadA优先级是:" + this.getPriority());
ThreadB threadB = new ThreadB();
threadB.start();
}
}
class ThreadB extends ThreadA{
@Override
public void run() {
System.out.println("ThreadB的优先级是:" + this.getPriority());
}
}
public class ThreadDemo7 {
public static void main(String[] args) {
System.out.println("main线程开始的优先级是:" + Thread.currentThread().getPriority());
Thread.currentThread().setPriority(9);
System.out.println("main线程结束的优先级是:" + Thread.currentThread().getPriority());
ThreadA threadA = new ThreadA();
threadA.start();
}
}
结果为👇
守护线程
Java中有两种线程:一种是用户线程,一种就是守护线程。
什么是守护线程?守护线程是一种特殊的线程,当进程中不存在用户线程的时候,守护线程就会自动销毁。典型的守护线程就是垃圾回收线程,当进程中没有了非守护线程,则垃圾回收线程也就没有存在的必要了。
Daemon线程的作用就是为其他线程的运行提供便利的。👇
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-11;
* time: 21:06;
*/
public class ThreadDemo8 {
static private int i = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (true){
i++;
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
//设置守护线程
thread.setDaemon(true);
thread.start();
Thread.sleep(5000);
System.out.println("我是守护线程thread 当用户线程执行完成后 我也就销毁了😭哭了");
}
}
注意:守护线程的设置必须放在start()之前,否则就会报错。
在守护线程中创建的线程默认也是守护线程。
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 9:35;
*/
public class ThreadDemo10 {
public static void main(String[] args) {
Thread thread1 = new Thread(()->{
Thread thread2 = new Thread(() -> {
},"thread2");
System.out.println("thread2是守护线程吗?:" + thread2.isDaemon());
},"thread1");
System.out.println("thread1是守护线程吗?:" + thread1.isDaemon());
//thread1.setDaemon(true);
thread1.start();
// System.out.println("thread1是守护线程吗?:" + thread1.isDaemon());
}
}
再看👇
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 9:35;
*/
public class ThreadDemo10 {
public static void main(String[] args) {
Thread thread1 = new Thread(()->{
Thread thread2 = new Thread(() -> {
},"thread2");
System.out.println("thread2是守护线程吗?:" + thread2.isDaemon());
},"thread1");
System.out.println("thread1是守护线程吗?:" + thread1.isDaemon());
thread1.setDaemon(true);
thread1.start();
System.out.println("thread1是守护线程吗?:" + thread1.isDaemon());
}
}
线程组
为了便于对某些具有相同功能的线程进行管理,可以把这些线程归属到同一个线程组中,线程组中既可以有线程对象,也可以有线程组,组中也可以有线程。使用线程模拟赛跑
public class ThreadDemo5 {
//线程模拟赛跑(未使用线程分组)
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "到达了终点");
}
}, "选手一");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "到达了终点");
}
}, "选手二");
t1.start();
t2.start();
System.out.println("所有选手到达了终点");
}
}
运行结果:
不符合预期效果,就可以使用线程组来实现
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-11;
* time: 18:24;
*/
class ThreadGroup1 {
//线程分组模拟赛跑
public static void main(String[] args) {
ThreadGroup threadGroup = new ThreadGroup("Group");
Thread t1 = new Thread(threadGroup, new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("选手一到达了终点");
}
});
Thread t2 = new Thread(threadGroup, new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("选手二到达了终点");
}
});
t2.start();
t1.start();
while (threadGroup.activeCount() != 0) {
}
System.out.println("所有选手到达了终点");
}
}
线程组常用的方法
线程安全问题
来看单线程情况下让count分别自增和自减10000次
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 12:03;
*/
class Counter {
private static int count = 0;
public void increase(){
for (int i = 0; i < 10000; i++) {
count++;
}
}
public void decrease(){
for (int i = 0; i < 10000; i++) {
count--;
}
}
public int getCount(){
return count;
}
}
public class ThreadDemo16 {
public static void main(String[] args) {
//单线程
Counter counter = new Counter();
counter.increase();
counter.decrease();
System.out.println(counter.getCount());
}
}
结果符合预期
如果想使程序的执行速度快,就可以使用多线程的方式来执行。在来看多线程情况下的问题
public class ThreadDemo16 {
public static void main(String[] args) throws InterruptedException {
//多线程情况下
Counter counter = new Counter();
Thread thread1 = new Thread(()->{
counter.decrease();
});
Thread thread2 = new Thread(()->{
counter.increase();
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(counter.getCount());
/*
//单线程
Counter counter = new Counter();
counter.increase();
counter.decrease();
System.out.println(counter.getCount());
*/
}
}
执行结果:
每次的执行结果是不一样的。这就是多线程的不安全问题
预期的结果是0,但结果却不是。线程不安全问题的原因:
1.CPU的抢占式执行
2.多个线程共同操作一个变量
3.内存可见性
4.原子性问题
5.编译器优化(指令重排)
多个线程操作同一个变量
如果多个线程操作的不是一个变量,就不会发生线程的不安全问题,可以将上面的代码修改如下:👇
public class ThreadDemo16 {
static int res1 = 0;
static int res2 = 0;
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
res1 = counter.getCount();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
res2 = counter.getCount();
}
});
System.out.println(res1 + res2);
/*
//多线程情况下
Counter counter = new Counter();
Thread thread1 = new Thread(()->{
counter.decrease();
});
Thread thread2 = new Thread(()->{
counter.increase();
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(counter.getCount());
*/
/*
//单线程
Counter counter = new Counter();
counter.increase();
counter.decrease();
System.out.println(counter.getCount());
*/
}
}
这样就可以了:
内存不可见问题:看下面的代码,是不是到thread2执行的时候,就会改变num的值,从而终止了thread1呢?
package ThreadDeom;
import java.util.Scanner;
/**
* user:ypc;
* date:2021-06-12;
* time: 13:03;
*/
public class ThreadDemo17 {
private static int num = 0;
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
while (num == 0){}
}
});
thread1.start();
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
Scanner scanner = new Scanner(System.in);
System.out.println("输入一个数字来终止线程thread1");
num = scanner.nextInt();
}
});
thread2.start();
}
}
结果是不能的:
输入一个数字后回车,并没有让thread1的循环结束。这就是内存不可见的问题。
原子性的问题
上面的++和–操作其实是分三步来执行的
假设在第二部的时候,有另外一个线程也来修改值,那么就会出现脏数据的问题了。
所以就会发生线程的不安全问题
编译器优化编译器的优化会打乱原本程序的执行顺序,就有可能导致线程的不安全问题发生。在单线程不会发生线程的不安全问题,在多线程就可能会不安全。
volatile关键字
可以使用volatile关键字,这个关键字可以解决指令重排和内存不可见的问题。
加上volatile关键字之后的运行结果
但是volatile关键字不能解决原子性的问题👇:
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 14:02;
*/
class Counter1 {
private static volatile int count = 0;
public void increase() {
for (int i = 0; i < 10000; i++) {
count++;
}
}
public void decrease() {
for (int i = 0; i < 10000; i++) {
count--;
}
}
public int getCount() {
return count;
}
}
public class ThreadDemo18 {
public static void main(String[] args) throws InterruptedException {
Counter1 counter1 = new Counter1();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
counter1.decrease();
}
});
Thread thread2 = new Thread(() -> {
counter1.increase();
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(counter1.getCount());
}
}
来源:https://blog.csdn.net/qq_45859087/article/details/117821279


猜你喜欢
- 今天写Tab的时候由于TAB的跳转问题去查资料,倒反而发现更有趣的问题,就是如何将TAB放置在屏幕的底端。 <?xml version
- 在Android开发过程中,有时需要获取触摸位置的坐标,以便作进一步处理,比如做炫酷的动画效果,或者响应其他操作。本文简单介绍Android
- 1.背景倒计时的效果在网站或其他平台看到的很多了吧,今天就让我们来看看在OpenHarmony中如何实现它吧!2.效果预览视频效果演示传送门
- 每当想找哪个运算符优先级高时,很多时候总是想找的就没有,真让人气愤!现在,终于有个我个人觉得非常全的,分享给大家,欢迎拍砖!C语言运算符优先
- 好久没有做web了,JSON目前比较流行,闲得没事,所以动手试试将对象序列化为JSON字符(尽管DotNet Framework
- telnet-client太费尽了,比ssh-client费尽的多,搞了一天,凑合能用,还得改。org.apache.commons.net
- 本文实例讲述了C#读取csv格式文件的方法。分享给大家供大家参考。具体实现方法如下:一、CSV文件规则 1 开头是不留空,以行为单
- 今天一直在绞尽脑汁的寻找解决两个字符之间的内容如何输出的问题,刚开始就使用了万能的正则表达式;但是不知哪里的原因自己的数据一直出不来,觉得应
- 题目要求思路一:枚举 + 二分逐一枚举值域内的所有值,然后二分判断是否合法。Javaclass Solution { &nbs
- 本文介绍的库中的侧滑效果借鉴自SwipeMenu,并对SipwMenu的源码做了修改与Bug修复,然后才开发出的SwipeRecyclerV
- 前言:项目中经常会用到类似于QQ侧滑点击删除的效果,网上的开源库也很多。个人感觉SwipeLayout最好用。下面介绍怎么使用。一、首先导入
- Android当道,现在学习Android开发还晚吗?写下这个问题的时间是–2014年6月15号,我会回答:不晚,Android至少还能在活
- 简介JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案。JSON Web Token 入门教程 这篇文章可以帮你了解
- 问题描述Flutter 应用在 Android 端上启动时会有一段很明显的白屏现象,白屏的时长由设备的性能决定,设备性能越差,白屏时间越长。
- 本文实例讲述了C#实现带百分比的进度条功能。分享给大家供大家参考,具体如下:功能需求:如果程序中会执行一个耗时的计算过程,我想在用户点击按钮
- 本文实例为大家分享了Android实现简单点赞动画的具体代码,供大家参考,具体内容如下思路1、找到Activity中DecorView的Ro
- 客户端代码:/// <summary>/// 批量上传图片/// </summary>/// <param n
- 今天学习了Mybatis执行存储,感觉不是那么好用,可能是我没用习惯。我先在SQLSERVER创建存储alter procedure usp
- 在使用手机时,蓝牙通信给我们带来很多方便。那么在Android手机中怎样进行蓝牙开发呢?本文以实例的方式讲解Android蓝牙开发的知识。&
- 本文实例讲述了Android Service中使用Toast无法正常显示问题的解决方法。分享给大家供大家参考,具体如下:在做Service简