万字手撕Java多线程
文章目录
博主简介:
博客主页:Java知识分享博主
Java零基础入门专栏:Java零基础入门专栏
Java交流社区:飞鸟社区
欢迎阅读,如果文章对你有帮助点赞,支持一下!
什么是多线程
程序、进程、线程
-
程序:从所周知的,编写代码组成的。
-
进程:可以理解为运行中的程序,比如启动QQ,就启动一个进程,操作系统会为进程分配内存空间。
-
线程:由进程创建的,是进程的一个实体。
单线程和多线程的区分:
-
同一个时刻,只允许执行一个线程
-
同一个时刻,可以执行多个线程,比如QQ可以同时打开多个聊天窗口
并发和并行(理解)
- 一个多个任务交替执行,一个多个任务同时执行
创建线程的两种方式
通过继承 Thread 的方法和实现 Runnable 接口的方式创建多线程,哪个好?
实现Runnable接口好,原因有两个:
- ①、避免了Java单继承的局限性
- ②、适合多个相同的程序代码去处理同一资源的情况,把线程、代码和数据有效的分离,更符合面向对象的设计思想。
①、继承 Thread 的方法
- 当一个类(cat类)继承了 Thread 类, 该类就可以当做线程使用
- 我们会重写 run 方法,写上自己的业务代码
- run Thread 类 实现了 Runnable 接口的 run
package threaduse;public class Thread01 { public static void main(String[] args) { Cat cat = new Cat(); cat.start();//启动线程 }}class Cat extends Thread{int times=0; public void run () { //重写 run 方法,写上自己的业务逻辑 while(true) { System.out.println("猫会跑"+times++); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if(times==8){ //当时间到第8秒时,自动跳出 break; } } }}
②、runnable接口实现run方法
1、dog.start();这里不能调用 start
2、创建了 Thread 对象,把 dog 对象(实现 Runnable),放入 Thread
package threaduse;public class Thread02 { public static void main(String[] args) { Dog dog = new Dog(); // dog.start();这里不能调用 start //创建了 Thread 对象,把 dog 对象(实现 Runnable),放入 Thread Thread thread = new Thread(dog); thread.start(); }}class Dog implements Runnable{//通过实现 Runnable 接口,开发线程 int count=0; public void run(){ while (true){ System.out.println("小狗会汪汪叫。。"+(++count)+Thread.currentThread().getName()); try { Thread.sleep(1000); //休眠1秒 } catch (InterruptedException e) { e.printStackTrace(); } if(count==8){ break; } } }}
为什么要重写run方法
因为run方法是用来封装被线程执行的代码。
run()
方法和start()
方法有什么区别?
run()
:封装线程执行的代码,直接调用相当于调用普通方法。start()
:启动线程,然后由JVM 调用此线程的run()
方法。
设计模式
package threaduse;public class Thread02 { public static void main(String[] args) {// Dog dog = new Dog();//// dog.start();这里不能调用 start// //创建了 Thread 对象,把 dog 对象(实现 Runnable),放入 Thread// Thread thread = new Thread(dog);// thread.start(); tiger tiger = new tiger(); ThreadProxy threadProxy = new ThreadProxy(tiger); threadProxy.start(); }}class Animal{}class tiger extends Animal implements Runnable{ public void run(){ System.out.println("老虎嗷嗷叫"); }}//线程代理类 , 模拟了一个极简的 Threadclass ThreadProxy implements Runnable{private Runnable target =null;//属性,类型是Runnable public void run() { if(target!=null){ target.run();//动态绑定(运行类型 Tiger) } } public ThreadProxy(Runnable target){ this.target=target;//构造方法 } public void start(){ start0();//这个方法时真正实现多线程方法 } public void start0(){ run(); }}class Dog implements Runnable{//通过实现 Runnable 接口,开发线程 int count=0; public void run(){ while (true){ System.out.println("小狗会汪汪叫。。"+(++count)+Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if(count==8){ break; } } }}
手动实践编程题
编写一个程序,创建两个线程,一个线程每隔1秒输出“helloworld”,输出10次退出, 一个线程每隔1秒输出“hi”,输出5次退出(可以手动敲一下.狗头)
package threaduse;//编写一个程序,创建两个线程,一个线程每隔1秒输出“helloworld”,输出10次退出,// 一个线程每隔1秒输出“hi”,输出5次退出public class Thread03 { public static void main(String[] args) { T1 t1 = new T1(); T2 t2 = new T2(); Thread thread1 = new Thread(t1); Thread thread2 = new Thread(t2); thread1.start(); thread2.start(); }}class T1 implements Runnable{ int count=0; public void run(){ while (true){ System.out.println("helloworld"+(++count)+Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if(count==10){ break; } } }}class T2 implements Runnable{ int count=0; public void run(){ while (true){ System.out.println("hi"+(++count)+Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if(count==5){ break; } } }}
编程模拟三个售票窗口售票100张
package threaduse;//编程模拟三个售票窗口售票100张,// 分别使用继承Thread和实现Runnable方式,并分析有什么问题?public class Thread04 { public static void main(String[] args) { SellTicket sellTicket = new SellTicket(); new Thread(sellTicket).start();//第一个售票窗口 new Thread(sellTicket).start();//第二个售票窗口 new Thread(sellTicket).start();//第三个售票窗口 }}class SellTicket extends Thread{ private static int num=100; public void run(){ while (true){ if(num<=0){ System.out.println("售票结束。。"); break; } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口"+Thread.currentThread().getName()+"售出一张票"+"剩余票数="+(--num)); } }}
手动实践编程题
启动一个线程t,要求main线程中去停止线程t,关键代码块
成员内部类中
package exit;public class ThreadExit { public static void main(String[] args) throws InterruptedException { T t = new T(); new Thread(t).start(); System.out.println("main线程休眠10秒"); Thread.sleep(10*1000); t.setLoop(false); }}class T implements Runnable{ private boolean loop=true; private int count=0; public void run(){ while(loop){ try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("a运行中。。。"+(++count)); } } public void setLoop(boolean loop){ this.loop=loop; }}
线程常用方法
interrupt 中断线程
编程题
两个线程同时执行,当主线程执行输出5次时,让子线程执行剩下15次输出,主线程再次执行。
public class ThreadMethodExercise { public static void main(String[] args) throws InterruptedException { Thread t3 = new Thread(new T3());//创建子线程 for (int i = 1; i <= 10; i++) { System.out.println("hi " + i); if(i == 5) {//说明主线程输出了 5 次 hi t3.start();//启动子线程 输出 hello... t3.join(); t3.join();// 立即将 t3 子线程,插入到 main 线程,让 t3 先执行 } Thread.sleep(1000);//输出一次 hi, 让 main 线程也休眠 1s } }}class T3 implements Runnable { private int count = 0; @Override public void run() { while (true) { System.out.println("hello " + (++count)); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if (count == 10) { break; } } }}
Thread.yield;礼让不一定成功
t3.join();插队一定成功
用户线程和守护线程
//设t为子线程,即设为守护线程,当所有线程结束后,t也自动结束//如果没有设置,那么main线程执行完毕,t也不退出//前提是子线程无限循环t.setDaemon(true);//关键字t.start();
6种线程
public enum State { NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED;}
package state;public class ThreadState { public static void main(String[] args) throws InterruptedException { T t = new T(); System.out.println(t.getName() + " 状态 " + t.getState()); t.start(); while (Thread.State.TERMINATED != t.getState()) { System.out.println(t.getName() + " 状态 " + t.getState()); Thread.sleep(1000); } System.out.println(t.getName() + " 状态 " + t.getState()); }}class T extends Thread { @Override public void run() { while (true) { for (int i = 0; i < 10; i++) { System.out.println("hi " + i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } break; } }}
线程同步机制(重点)
- 编程模拟三个售票窗口售票100张,
- 分别使用继承Thread和实现Runnable方式,并分析有什么问题?
package sys;public class Thread05 { public static void main(String[] args) { SellTicket3 sellTicket3 = new SellTicket3(); new Thread(sellTicket3).start();//第一个售票窗口 new Thread(sellTicket3).start();//第二个售票窗口 new Thread(sellTicket3).start();//第三个售票窗口 }}class SellTicket3 implements Runnable{ private int num=100; private boolean loop =true; public synchronized void sell(){ //同步线程,只允许一个线程执行 if(num<=0){ System.out.println("售票结束。。"); loop=false; return; } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口"+Thread.currentThread().getName()+"售出一张票"+"剩余票数="+(--num)); } public void run(){ while (loop){ sell(); } }}
互斥锁
//1. public synchronized static void m1() {} 锁是加在 SellTicket03.class//2. 如果在静态方法中,实现一个同步代码块/* synchronized (SellTicket4.class) { System.out.println("m2"); }*/ public synchronized static void m1() { } public static void m2() { synchronized (SellTicket4.class) { System.out.println("m2"); }
- 1、 public synchronized void sell() {} 就是一个同步方法
- 2、这时锁在this对象
- 3、 也可以在代码块上写synchronize,同步代码块,互斥锁还是在this对象
package sys;//编程模拟三个售票窗口售票100张,// 分别使用继承Thread和实现Runnable方式,并分析有什么问题?public class Thread06 { public static void main(String[] args) { SellTicket4 sellTicket3 = new SellTicket4(); new Thread(sellTicket3).start();//第一个售票窗口 new Thread(sellTicket3).start();//第二个售票窗口 new Thread(sellTicket3).start();//第三个售票窗口 }}class SellTicket4 implements Runnable{ private int num=50; private boolean loop =true; Object object=new Object(); public void sell() { //同步方法,同一个时刻,只允许一个sell线程执行 synchronized (object) { if (num <= 0) { System.out.println("售票结束。。"); loop = false; return; } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票" + "剩余票数=" + (--num)); } } public void run(){ while (loop){ sell(); } }}
线程的死锁
多个线程都占用对方的锁资源,但不肯互让,导致了死锁
package DeadLock;public class DeadLock{ public static void main(String[] args) { //模拟死锁现象 DeadLockDemo A = new DeadLockDemo(true); A.setName("A 线程"); DeadLockDemo B = new DeadLockDemo(false); B.setName("B 线程"); A.start(); B.start(); }}class DeadLockDemo extends Thread { static Object o1 = new Object();// 保证多线程,共享一个对象,这里使用 static static Object o2 = new Object(); boolean flag; public DeadLockDemo(boolean flag) {//构造器 this.flag = flag; } @Override public void run() {//下面业务逻辑的分析//1. 如果 flag 为 T, 线程 A 就会先得到/持有 o1 对象锁, 然后尝试去获取 o2 对象锁//2. 如果线程 A 得不到 o2 对象锁,就会 Blocked//3. 如果 flag 为 F, 线程 B 就会先得到/持有 o2 对象锁, 然后尝试去获取 o1 对象锁//4. 如果线程 B 得不到 o1 对象锁,就会 Blocked if (flag) { synchronized (o1) {//对象互斥锁, 下面就是同步代码 System.out.println(Thread.currentThread().getName() + " 进入 1"); synchronized (o2) { // 这里获得 li 对象的监视权 System.out.println(Thread.currentThread().getName() + " 进入 2"); } } } else { synchronized (o2) { System.out.println(Thread.currentThread().getName() + " 进入 3"); synchronized (o1) { // 这里获得 li 对象的监视权 System.out.println(Thread.currentThread().getName() + " 进入 4"); } } } }}
编程题
在main方法中启动两个线程
第1个线程循环随机打印100以内的整数
直到第2个线程从键盘读取了“Q”命令
package Homework01;import java.util.Locale;import java.util.Scanner;public class Homework01 { public static void main(String[] args) { A a = new A(); B b = new B(a); a.start(); b.start(); }}class A extends Thread{ private boolean loop =true; public void run(){ while(loop){ System.out.println((int)(Math.random()*100 +1)); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } public void setLoop(boolean loop) { this.loop = loop; }}class B extends Thread{ private A a; private Scanner scanner=new Scanner(System.in); public B(A a){ this.a=a; } public void run() { while (true) { //接受用户输入 System.out.println("请输入(Q)指令结束退出:"); char key=scanner.next().toUpperCase().charAt(0); if(key=='Q'){ //以通知方式结束a线程 a.setLoop(false); System.out.println("b线程退出"); break; } } }}
编程题
有2个用户分别从同一个卡上取钱(总额:1000)
每次都取1000,当余款不足时,就不能取款了
不能出现超取现象 线程同步问题
package Homework02;public class Mon01 { public static void main(String[] args) { A a = new A(); Thread thread1 = new Thread(a); thread1.setName("用户1"); Thread thread2 = new Thread(a); thread2.setName("用户2"); thread1.start();//第一个用户 thread2.start();//第二个用户 }}//涉及多个线程共享资源,使用接口的实现class A implements Runnable{ private int num=10000; public boolean loop=true; public void run(){ while(loop){ synchronized (this){ if(num<0){ System.out.println("余款不足,不能取款"); loop=false; return; } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } num -=1000; System.out.println(Thread.currentThread().getName()+" "+"剩余余款:"+num); } } }}