彻底搞懂Java多线程(一)
目录
- Java多线程
- 线程的创建
- 线程常用方法
- 线程的终止
- 1.自定义实现线程的终止
- 2.使用Thread的interrupted来中断
- 3.Thraed.interrupted()方法和Threaed.currentThread().interrupt()的区别
- 线程的状态
- 线程的优先级
- 守护线程
- 线程组
- 线程安全问题
- volatile关键字
- 总结
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()); } }
总结
本篇文章就到这里,希望可以帮到你,也希望您能够多多关注我们的其他文章!