彻底搞懂Java多线程(一)
1.继承Thread
2.实现Runnable
3.实现Callable
使用继承Thread类来开发多线程的应用程序在设计上是有局限性的,因为Java是单继承。
继承Thread类
public class ThreadDemo1 { // 继承Thread类 写法1 static class MyThread extends Thread{@Overridepublic void run() { //要实现的业务代码} } // 写法2 Thread thread = new Thread(){@Overridepublic void run() { //要实现的业务代码} };}
实现Runnable接口
//实现Runnable接口 写法1class 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 + Futurepublic class ThreadDemo2 { static class MyCallable implements Callable<Integer>{@Overridepublic Integer call() throws Exception { return 0;} } public static void main(String[] args) throws ExecutionException, InterruptedException {//创建Callable子对象MyCallable myCallable = new MyCallable();//使用FutureTask 接受 CallableFutureTask<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() {@Overridepublic void run() { System.out.println('t1');} }); //最大优先级 t1.setPriority(10); t1.start(); Thread t2 = new Thread(new Runnable() {@Overridepublic void run() { System.out.println('t2');} }); //最小优先级 t2.setPriority(1); t2.start(); Thread t3 = new Thread(new Runnable() {@Overridepublic 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()); }}
总结本篇文章就到这里,希望可以帮到你,也希望您能够多多关注好吧啦网的其他文章!
相关文章: