1、创建线程类的方法
1.1、继承Thread类
- 当一个类继承了Thread类,该类就可以当做线程使用
- 我们会重写run方法,写上自己的业务代码
run方法 是 Thread类实现了 Runnable接口的run方法
1.1.1、入门案例
/*** @author 杨磊* @version 1.0.0* @ClassName Thread01.java* @Description 通过继承Thread类实现多线程* @createTime 2022年01月05日 09:49:00*/public class Thread01{public static void main(String[] args) {Cat cat = new Cat();cat.start();}}class Cat extends Thread{int timeCount = 0;@Overridepublic void run() {while (true) {System.out.println("我是一只小猫咪" + (++ timeCount));try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}if (timeCount == 8){break;}}}}
1.1.2、入门案例线程示意图
1.1.3、为什么调用的是start方法
run方法就是一个普通的方法,没有真正的启动一个线程,就会把run方法执行完毕,才向下执行
- start()方法调用start0() 方法后,该线程并不会立马执行,只是将线程变成了可运行状态。具体什么时候执行,取决于CPU,由CPU统一调度。
- start0()方法是本地方法,是JVM调用,底层是C/C++
-
1.2、实现Runnable接口
1.2.1、入门代码
public class Thread02 {public static void main(String[] args) {Dog dog = new Dog();Thread thread = new Thread(dog);thread.start();}}class Dog implements Runnable{int count = 0;@Overridepublic void run() {while (true){System.out.println("小狗汪汪叫" + (++ count));try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}if (count == 8){break;}}}}
1.2.2、底层原理
底层原理使用的是静态代理模式
public class Thread02 {public static void main(String[] args) {Tiger tiger = new Tiger();ThreadProxy threadProxy = new ThreadProxy(tiger);threadProxy.start();}}class Animal{}class Tiger extends Animal implements Runnable{@Overridepublic void run() {System.out.println("老虎嗷嗷叫....");}}class ThreadProxy implements Runnable{private Runnable runnable = null;@Overridepublic void run() {if (runnable != null){runnable.run();}}public ThreadProxy(Runnable runnable) {this.runnable = runnable;}public void start(){start0();}private void start0() {run();}}
1.2.3、练习案例
编写一个程序,创建两个线程,一个线程每隔一秒输出“hello world”,输出10次,一个线程每隔1秒输出“hi”,输出5次退出 ```java package com.haiyang.threaduse;
/**
- @author 杨磊
- @version 1.0.0
- @ClassName Thread03.java
- @Description 创建两个线程,一个线程每隔一秒输出“hello world”,输出10次,一个线程每隔1秒输出“hi”,输出5次退出
@createTime 2022年01月05日 11:38:00 */ 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; @Override public void run() {
while (true){System.out.println("hi" + Thread.currentThread().getName() + "-" + (++count));try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}if (count == 10){break;}}
} } class T2 implements Runnable{
int count = 0; @Override public void run() {
while (true){System.out.println("hello world" + Thread.currentThread().getName() + "-" + (++count));try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}if (count == 15){break;}}
} }
<a name="uOydI"></a>## 1.3、继承Thread和实现Runnable接口的区别- **从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上是没有区别的,从jdk帮助文档我们可以看到Thread类就实现了Runnable,底层还是去调用了start()方法,start()方法又去调用的start0()方法**- **实现Runnable接口方式更加适应多个线程共享一个资源的情况,并且避免了单继承的局限性,建议使用Runnable**<a name="ej90l"></a># 2、线程的终止- **当线程完成任务以后,会自动关闭**- **还可以通过使用遍历来控制run方法的退出的方式停止线程,即通知方式**<a name="tKB0k"></a>## 2.1、案例```javapublic class ThreadExit_ {public static void main(String[] args) throws InterruptedException {T t = new T();t.start();Thread.sleep(5000);t.setFlag(false);}}class T extends Thread{private int countNum = 0;private boolean flag = true;@Overridepublic void run() {while (flag){try {Thread.sleep(100);System.out.println("hello world!" + countNum);countNum ++;} catch (InterruptedException e) {e.printStackTrace();}if (countNum == 80){break;}}}public void setFlag(boolean flag) {this.flag = flag;}}
3、线程常用的方法
3.1、第一组
- setName : 设置线程的名称,使之与参数的name相同
- getName : 返回线程的名称
- start : 使用该线程开始执行;Java虚拟机底层调用线程的start0方法
- run : 调用线程对象run方法
- setPriority :更改线程的优先级
- getPriority : 获取线程的优先级
- sleep :在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
intterupt : 中断线程,如果现在正在休眠,则会中断它的休眠。
3.2、第二组
yield : 线程的礼让,让出CPU,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功
- join : 线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务。 ```java package com.haiyang.method;
/**
- @author 杨磊
- @version 1.0.0
- @ClassName ThreadMethod01.java
- @Description
- 案例 :
- main线程创建一个子线程,每隔1秒输出hello 输出20次 主线程每隔1秒,输出hi 输出20次 要求两个线程同时执行,当主线程输出5次后,让子线程运行完毕,主线程在继续
@createTime 2022年01月05日 20:43:00 */ public class ThreadMethod01 { public static void main(String[] args) throws InterruptedException {
T1 t1 = new T1();t1.start();for (int i = 1; i <= 20; i++) {Thread.sleep(1000);System.out.println("主线程吃了" + i + "个包子");if (i == 5){System.out.println("主线程让子线程先吃完包子");t1.join(); // 这里相当于t1线程先执行完毕System.out.println("子线程吃完了,主线程开始吃..");}}
} } class T1 extends Thread{ @Override public void run() {
for (int i = 1; i <= 20; i++) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("子线程吃了" + i + "个包子");}
3.3、用户线程和守护线程
- 用户线程 : 也叫工作线程,当线程的任务执行完或通知方式结束
- 守护线程 : 一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束 ```java package com.haiyang.method;
/**
- @author 杨磊
- @version 1.0.0
- @ClassName ThreadMethod03.java
- @Description TODO
@createTime 2022年01月06日 10:21:00 */ public class ThreadMethod03 {
public static void main(String[] args) throws InterruptedException {
MyDaemonThread myDaemonThread = new MyDaemonThread();// 设置子线程为守护线程myDaemonThread.setDaemon(true);myDaemonThread.start();for (int i = 0; i < 10; i++) {System.out.println("主线程 haha。。。");Thread.sleep(1000);}
} } class MyDaemonThread extends Thread{ @Override public void run() {
while (true){System.out.println("hehe......");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}
4、线程的生命周期
- 线程状态。线程可以处于以下状态之一:
- NEW : 尚未启动的线程处于此状态。
- RUNNABLE : 在Java虚拟机中执行的线程处于此状态。
- 可分为 就绪状态和运行状态
- BLOCKED : 被阻塞等待监视器锁定的线程处于此状态。
- WAITING : 正在等待另一个线程执行特定动作的线程处于此状态。
- TIMED_WAITING : 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
- TERMINATED : 已退出的线程处于此状态。
5、线程的同步
5.1、线程的同步机制
在多线程编程,一些敏感的数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。
5.2、同步具体方法-Synchronized
5.2.1、同步代码块
synchronized(对象) { // 得到对象的锁,才能操作同步代码// 需要被同步代码}
5.2.2、同步方法
synchronized 还可以放在方法声明中,表示整个方法为同步方法
public synchronized void m(String name){// 需要被同步的代码}
5.3、卖票案例
```java /**
- @author 杨磊
- @version 1.0.0
- @ClassName SellTicket.java
- @Description 使用多线程模拟三个窗口售票100张
- @createTime 2022年01月05日 17:09:00
*/
public class SellTicket {
public static void main(String[] args) {
SellTicket03 sellTicket01 = new SellTicket03();
new Thread(sellTicket01).start();new Thread(sellTicket01).start();new Thread(sellTicket01).start();}
}
// 实现接口的方式,使用 synchronized 实现线程同步 class SellTicket03 implements Runnable { private int ticketNum = 100; private boolean flag = true;
public synchronized void sell() {if (ticketNum <= 0) {System.out.println("售票结束!");flag = false;return;}try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("窗口" + Thread.currentThread().getName() + " 卖出一张票,剩余票数:" + (--ticketNum));}@Overridepublic void run() { // 同步方法,在同一时刻只能有一个线程执行run方法while (flag) {sell();}}
5.4、同步原理
5.5、互斥锁
- Java语言中,引入了对象互斥的概念,来保证共享数据操作的完整性
- 每一个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任意时刻,只能有一个线程访问该对象。
- 关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时表明该对象在任一时刻只能由一个线程访问。
- 同步的局限性:导致程序执行效率降低
- 同步方法(非静态方法)的锁可以是this,也可以是其他对象(要求是同一对象)
-
5.5.1、注意事项
同步方法如果没有使用static修饰:默认对对象为this
- 如果方法使用static修饰,默认锁对象为 当前类.class
实现的落地步骤
多个线程都占用对方的锁资源,但都不肯想让,导致了死锁,在编程是一定要避免死锁的发生。
7、释放锁
7.1、释放锁的情况
当前线程的同步方法、同步代码块执行结束
- 当前线程在同步代码块、同步方法中遇到break、return.
- 当前线程在同步代码块、同步方法中出现了为处理的Error或者Exception,导致异常结束
当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。
7.2、不会释放锁
线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁
- 线程执行同步代码块时,其他线程调用该线程的suspend()方法将线程挂起,该线程不会释放锁。
