文章目录
- 1.线程相关概念
- 1.程序
- 2.进程
- 3.线程
- 4.单线程
- 5.多线程
- 6.并发
- 7.并行
- 查看当前电脑cpu数量
- 2.线程基本使用
- 1.线程类图
- 2.继承Thread创建线程
- 细节说明
- 代码实例
- 3.实现Runnable来创建线程(包括静态代理演示)
- 代码实例
- 3.多线程机制
- 简要介绍
- 代码实例
- 为什么使用start开启线程?
- 追源码
- 示意图
- 4.多个子线程案例
- 题目1
- 代码实例
- 结果
- 继承Thread和实现Runnable的区别
- 题目2:多线程售票问题
- 代码实例
- 结果
- 问题
- 5.线程终止
- 通知线程终止方法
- 代码实例
- 结果
- 6.线程常用方法
- 常用方法第一组(线程中断)
- 注意事项
- 代码实例
- 结果
- 常用方法第二组(线程插队)
- 代码实例
- 结果
- 线程插队练习
- 题目
- 代码
- 结果
- 用户线程和守护线程
- 基本介绍
- 设置守护线程的代码实例
- 结果
- 7.线程的七大状态
- 状态介绍
- 代码实例
- 结果
- 8.线程同步机制
- 基本介绍
- 实现同步的具体方法
- 互斥锁
- 使用两种方法实现卖票(在方法上加锁)
- 注意事项
- 代码实例
- 使用synchronized代码块的方式加锁(推荐)
- 细节说明
- 代码实例
- 结果
- 9.线程的死锁
- 基本介绍
- 代码实例
- 结果
- 10.释放锁
- 以下操作会释放锁
- 以下操作不会释放锁
- 11.多线程练习题
- 题目一
- 代码
- 结果
- 题目二
- 代码
- 结果
1.线程相关概念
1.程序
为完成特定任务、用某种语言编写的一组指令的集合,简单来说就是我们写的代码
2.进程
- 指运行中的程序,没启动一个进程,操作系统就会为这个进程分配新的内存空间。比如启动了迅雷,系统就会为他分配内存空间
- 进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程,有他自身产生、存在和消亡的过程
3.线程
- 线程是由进程创建的,是进程的一个实体
- 一个进程可以拥有多个线程
- 就相当于迅雷是一个进程,创建了多个下载线程
4.单线程
同一个时刻,只允许执行一个线程
5.多线程
同一个时刻,可以执行多个线程,比如:一个qq进程可以同时打开多个聊天窗口
6.并发
同一时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简单的说,单核cpu实现的多任务就是并发
7.并行
同一时刻,多个任务并发执行,多个cpu可以实现并行
查看当前电脑cpu数量
/*** @author 孙显圣* @version 1.0*/
public class CpuNum {public static void main(String[] args) {Runtime runtime = Runtime.getRuntime(); //单例模式,获取Runtime实例int cpuNum = runtime.availableProcessors(); //获取当前cpu数量System.out.println(cpuNum);}
}
2.线程基本使用
1.线程类图
2.继承Thread创建线程
细节说明
- 当一个类继承了Thread类,该类就可以当做线程使用
- 我们会重写run方法,写上自己的业务代码
- Thread类,实现了Runnable接口的run方法
代码实例
package Thread_;/*** @author 孙显圣* @version 1.0*/
public class Thread01 {public static void main(String[] args) {Cat cat = new Cat(); //创建线程对象cat.start();//线程开始}
}class Cat extends Thread { //继承Thread类,重写run方法来写自己的逻辑int num = 0; //统计次数@Overridepublic void run() {while (true) {System.out.println("猫猫在跳舞" + (++num));try {Thread.sleep(1000); //线程休眠1秒} catch (InterruptedException e) {throw new RuntimeException(e);}if (num == 8) { //次数为8次则退出循环,从而结束run方法,结束线程break;}}}
}
3.实现Runnable来创建线程(包括静态代理演示)
代码实例
package Thread_;import org.junit.jupiter.api.Test;/*** @author 孙显圣* @version 1.0*/
public class Thread02 {public static void main(String[] args) {Dog dog = new Dog(); //创建线程类的实例Thread thread = new Thread(dog); //将线程类的实例传到Thread中thread.start(); //启动线程调用线程类的run方法}//用来测试静态代理模式@Testpublic void test() {Tiger tiger = new Tiger();Thread_ thread = new Thread_(tiger); //将实现了Runnable接口的类的实例传进去thread.start();}
}//继承Runnable接口来创建线程
class Dog implements Runnable {@Overridepublic void run() {System.out.println("Dog线程启动");}
}class Tiger implements Runnable {@Overridepublic void run() {System.out.println("老虎在叫");}
}//模拟Thread,静态代理模式
class Thread_ implements Runnable {private Runnable target = null; //使用属性接受那个实现了Runnable接口的实例public Thread_(Runnable target) { //传入一个实现了Runnable接口的实例this.target = target;}@Overridepublic void run() {if (target != null) {target.run();}}public void start() { //1.创建当前实例,调用start方法start0();}public void start0() { //2.调用runrun();}}
3.多线程机制
简要介绍
- 当程序开始执行的时候,系统会开启一个进程,可以在Terminal输入jconsole可以查看
- 这个进程会先开启main线程,然后在下面的代码实例中可以看出main线程开启了一个子线程Cat
- 这样主线程并不会被阻塞,而是会和子线程交替执行
代码实例
package Thread_;/*** @author 孙显圣* @version 1.0*/
public class Thread01 {public static void main(String[] args) throws InterruptedException {Cat cat = new Cat(); //创建线程对象cat.start();//线程开始//当main线程启动一个子线程的时候,主线程不会阻塞,会继续执行for (int i = 0; i < 60; i++) {System.out.println("主线程" + i);Thread.sleep(1000); //主线程休眠}}
}class Cat extends Thread { //继承Thread类,重写run方法来写自己的逻辑int num = 0; //统计次数@Overridepublic void run() {while (true) {System.out.println("猫猫在跳舞" + (++num));try {Thread.sleep(1000); //线程休眠1秒} catch (InterruptedException e) {throw new RuntimeException(e);}if (num == 80) { //次数为8次则退出循环,从而结束run方法,结束线程break;}}}
}
为什么使用start开启线程?
追源码
- 打断点
- 跳入,走几步
- 这里实际上是调用了start0方法,而这个方法是native的方法,是虚拟机调用的,用来启动线程,可以理解为是虚拟机调用了run方法
- 注意:如果直接调用run方法并不能开启线程,只能说是串行化执行,它将会作为一个普通方法在main线程里串行化执行
示意图
4.多个子线程案例
题目1
代码实例
package Thread_;/*** @author 孙显圣* @version 1.0*/
public class Thread03 {public static void main(String[] args) {//启动第一个线程Thread1 thread1 = new Thread1();thread1.start();//启动第二个线程Thread2 thread2 = new Thread2();Thread thread = new Thread(thread2);thread.start();}
}// 第一个线程继承Thread类
class Thread1 extends Thread {private int count = 0;@Overridepublic void run() {while (true) {System.out.println("hello world");count++;try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}if (count == 10) {break;}}}
}// 第二个线程实现Runnable接口
class Thread2 implements Runnable {private int count = 0;@Overridepublic void run() {while (true) {System.out.println("hi");count ++;try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}if (count == 5) {break;}}}
}
结果
继承Thread和实现Runnable的区别
- 通过继承Thread和实现Runnable接口来创建线程本质上并没有区别
- 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制(建议使用)
题目2:多线程售票问题
代码实例
package Thread_;/*** @author 孙显圣* @version 1.0* 多线程售票*/
public class SellTicket {public static void main(String[] args) {//创建三个线程
// for (int i = 0; i < 3; i++) {
// new SellWin01().start();
// }//创建三个线程,使用实现接口的方法for (int i = 0; i < 3; i++) {new Thread(new SellWin02()).start();}}
}class SellWin01 extends Thread {public static int tickets = 100; //多个实例共享100张票@Overridepublic void run() { //线程操作:售票while (true){ //循环卖票//判断退出条件if (SellWin01.tickets == 0) {return;}//卖票System.out.println(Thread.currentThread().getName() + "卖出一张票,还剩" + (--SellWin01.tickets) + "张票");//休眠try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}class SellWin02 extends SellWin01 implements Runnable {
}
结果
问题
- 多个线程出现卖的是一张票的问题
- 说明在第一个线程没有卖完,第二个线程就进去了并且也卖票了
5.线程终止
通知线程终止方法
- 通过使用变量,来控制run方法退出
- 将一个布尔型变量设置为私有的,然后设置他的set方法
- 使用这个布尔型变量控制循环,这样就可以在其他线程来设置这个变量从而控制线程退出
代码实例
package Thread__;/*** @author 孙显圣* @version 1.0*/
public class ThreadExit {public static void main(String[] args) throws InterruptedException {//开启子线程ThreadExit_ threadExit = new ThreadExit_();new Thread(threadExit).start();Thread.sleep(10*1000); //主线程睡眠10s//通知子线程退出threadExit.setLoop(false);}
}//写一个线程
class ThreadExit_ implements Runnable {private boolean loop = true; //用来控制循环public void setLoop(boolean loop) { //使用主线程来控制这个循环条件this.loop = loop;}@Overridepublic void run() {while (loop) {System.out.println("子线程运行中");try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("子线程退出");}
}
结果
6.线程常用方法
常用方法第一组(线程中断)
注意事项
- start底层会创建新的线程,调用run,run是一个简单的方法调用,不会启动新的线程
- 线程优先级的范围(1到10)
- interrupt,中断线程,但是并没有真正的结束线程,所以一般用于中断正在休眠的线程
- sleep:线程的静态方法,使当前线程休眠
代码实例
package Thread__;/*** @author 孙显圣* @version 1.0*/
public class ThreadMethod01 {public static void main(String[] args) {//主线程设置一个子线程ThreadM threadM = new ThreadM();// setName 设置线程名字threadM.setName("Sun");// setPriority 设置线程优先级threadM.setPriority(Thread.MIN_PRIORITY); //这里是调用了Thread的默认静态常量设置的优先级为1// 获取线程名字和优先级System.out.println(threadM.getName() + "的优先级为" + threadM.getPriority());//启动线程threadM.start();//主线程打印十个hellofor (int i = 0; i < 10; i++) {System.out.println("hello");}//调用线程中断,中断线程的休眠,执行中断逻辑threadM.interrupt();}
}//写一个线程类
class ThreadM extends Thread {@Overridepublic void run() {//线程一直执行while (true) {//吃十个包子for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName() + "吃包子" + i); //获取当前线程的名字}//然后休眠10stry {Thread.sleep(10*1000);} catch (InterruptedException e) { //这个捕捉的就是中断异常,在main线程中调用中断则会触发// 这里可以写上自己的业务逻辑System.out.println("吃包子的休眠被中断了!!!");}//在这里中断逻辑处理完之后会继续循环}}
}
结果
常用方法第二组(线程插队)
代码实例
package Thread__;/*** @author 孙显圣* @version 1.0*/
public class ThreadMethod02 {public static void main(String[] args) throws InterruptedException {//启动子线程ThreadQueueCutting threadQueueCutting = new ThreadQueueCutting();Thread thread = new Thread(threadQueueCutting);thread.start();//主线程每隔一秒输出hi,输出20次int count = 0;for (int i = 0; i < 20; i++) {//在输出完五次的时候,让子线程插队,会先执行完子线程所有的内容,然后再继续执行主线程if (count == 5) {
// thread.join(); //线程插队Thread.yield(); //线程礼让,如果资源丰富则不会成功}System.out.println("hi");count ++;try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}//子线程,每隔一秒输出hello, 输出20次
class ThreadQueueCutting implements Runnable {@Overridepublic void run() {for (int i = 0; i < 20; i++) {System.out.println("hello");//间隔一秒try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
结果
线程插队练习
题目
代码
package Thread__;/*** @author 孙显圣* @version 1.0*/
public class ThreadMethodExercise {public static void main(String[] args) throws InterruptedException {//启动子线程ThreadCut threadCut = new ThreadCut();threadCut.start();//主线程,每隔1s,输出hi,一共10次for (int i = 0; i < 10; i++) {if (i == 5) {threadCut.join(); //当执行完五次之后子线程插队}System.out.println(Thread.currentThread().getName() + " : hi!");Thread.sleep(1000);}}
}//子线程,每隔1s输出hello,输出10次
class ThreadCut extends Thread {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.cur entThread().getName() + " : hello!");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
结果
用户线程和守护线程
基本介绍
- 用户线程:也叫工作线程,当线程的任务执行完成或以通知方式结束
- 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
- 常见的守护线程:垃圾回收机制
设置守护线程的代码实例
package Thread__;/*** @author 孙显圣* @version 1.0*/
public class ThreadMethod03 {public static void main(String[] args) throws InterruptedException {//设置子线程为守护线程,这样即使他是无限循环的,但是,只要没有线程正在执行,那么他就会退出DaemonThreads daemonThreads = new DaemonThreads();Thread thread = new Thread(daemonThreads);thread.setDaemon(true);thread.start();//主线程for (int i = 0; i < 10; i++) {System.out.println("宝强在辛苦的工作");Thread.sleep(1000);}}
}//守护线程
class DaemonThreads implements Runnable {@Overridepublic void run() {for (;;) { //无限循环System.out.println("马蓉和宋吉在快乐的聊天");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
结果
7.线程的七大状态
状态介绍
- NEW
- Runnable
- Ready
- Running
- TimeWaiting(超时等待,调用sleep时)
- Waiting(等待,调用join时)
- Blocked
- Teminated(终止)
代码实例
package Thread__;/*** @author 孙显圣* @version 1.0*/
public class ThreadState {public static void main(String[] args) throws InterruptedException {ThreadS threadS = new ThreadS();System.out.println(threadS.getState()); //NEW状态threadS.start();while (threadS.getState() != Thread.State.TERMINATED) { //只要子线程的状态不是终止状态,就一直输出子线程的状态System.out.println(threadS.getState());Thread.sleep(1000);}//当子线程结束时输出子线程的状态System.out.println(threadS.getState());}
}//子线程
class ThreadS extends Thread {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println("执行" + i);try {Thread.sleep(500); //可以引起超时等待状态TimneWaiting} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
结果
8.线程同步机制
基本介绍
实现同步的具体方法
互斥锁
- 使用关键字synchronized来与对象的互斥锁联系
- 虽然实例方法的代码是所有实例共享的,但是在并发中,每个实例对象拥有独立的实例方法(这么理解)
- 非静态同步:直接使用synchronized,这样的同步是对象锁,也就是给每个对象的实例方法加锁,这样其他非本类型的实例只能同时有一个访问本类型对象的实例方法
- 静态同步:使用synchronized static, 这样的同步是类锁,由于加了static,则这个方法是被本类型的所有实例共享的,这样本类型的所有实例只能同时有一个访问这个静态方法
- 简单来说静态同步是对本类型实例实现互斥的,非静态同步是对非本类型实例实现互斥的
使用两种方法实现卖票(在方法上加锁)
注意事项
- 这个继承Thread类的就相当于一个线程,所以要创建三个线程实例并访问run方法的sell方法,这就是本类型的互斥(静态同步)
- 而实现了Runnable接口的类X并不是一个线程,而Thread类的对象才是一个线程,如果是创建了X的对象,并创建了三个Thread类的对象来调用X的方法,那么对于X的方法sell来说就是要实现非本类型的互斥(非静态同步)
- 同步方法如果没用static修饰,默认锁对象为this
- 同步方法如果使用static修饰,默认锁对象为当前类.class
代码实例
package Thread__;/*** @author 孙显圣* @version 1.0*/
public class Syn {public static void main(String[] args) {// for (int i = 0; i < 3; i++) { //三个线程对象,访问三个对象各自的方法,所以要使用静态同步
// new SellTickets().start();
// }SellTickes_ sellTickes = new SellTickes_(); //三个线程访问一个对象的同一个,所以使用非静态同步(对象锁)即可for (int i = 0; i < 3; i++) {new Thread(sellTickes).start();}}
}// synchronized解决售票问题,使得同时只能有一个线程售票// 使用静态同步
class SellTickets extends Thread {public static int ticket = 100; //100张票@Overridepublic void run() {while (true) { //一直售票,当票数为0则退出try {sell();Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}}}//售票方法:同时只能有一个线程调用,这里必须将方法设置成静态的,因为只有这样所有的实例才共享这个方法的锁public synchronized static void sell() throws InterruptedException {if (SellTickets.ticket <= 0) {System.exit(0); //只要有一个线程进入这个方法,判断票数不够,就直接系统退出}System.out.println(Thread.currentThread().getName() + "售出一张票,剩余 " + (--SellTickets.ticket));}
}// 使用对象锁
class SellTickes_ implements Runnable {@Overridepublic void run() {while (true) { //一直售票,当票数为0则退出try {sell();Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}}}public synchronized void sell() throws InterruptedException { //对象锁if (SellTickets.ticket <= 0) {System.exit(0); //只要有一个线程进入这个方法,判断票数不够,就直接系统退出}System.out.println(Thread.currentThread().getName() + "售出一张票,剩余 " + (--SellTickets.ticket));}
}
使用synchronized代码块的方式加锁(推荐)
细节说明
- 类锁:当多个实例访问一个类来调用run方法的时候使用
- 对象锁:当多个实例访问一个实例来调用那个实例的run方法的时候使用,一般使用this,但是其实只要这个对象是多个实例共享的也可以,是几个对象访问的都要是同一个,可以理解为这个对象就是门的钥匙,但是只有一把
- 对象锁举一个例子:就是我下面的那个对象锁,使用的是Dog类(随便的一个类),原因是使用实现Runnable的方式其实是几个Thread实例访问SellT2的实例,从而调用run方法,所以需要使用对象锁,而他们每次访问的一定是一个对象,所以我在类中初始化了一个Dog类,这个类是属于对象的在内存中唯一的一个实例,所以就相当于把这个对象当做一个钥匙,其他实例通过抢它才能够得到进入代码块的机会
- 再简单点来说,只要是多个线程共享的那个资源就可以当锁,因为线程需要取得那个资源才能进入代码块
- 一个线程类的实例就相当于一个线程,每个实例都拥有自己的run方法代码(这样理解)
代码实例
package Thread__;/*** @author 孙显圣* @version 1.0*/
public class Syn_ {public static void main(String[] args) {
// for (int i = 0; i < 3; i++) {
// new SellT1().start(); //开启线程
// }SellT2 sellT2 = new SellT2();for (int i = 0; i < 3; i++) {new Thread(sellT2).start();}}
}//使用继承Thread的方式——类锁
class SellT1 extends Thread {public static int sticket = 100; //100张票@Overridepublic void run() {while (true) {//这个方式是创建三个实例来调用本类的方法,所以应该使用类锁synchronized (SellT1.class) {//在这里写售票逻辑if (SellT1.sticket <= 0) {return;}System.out.println(Thread.currentThread().getName() + "已经售出一张票,剩余 " + (-- SellT1.sticket));}try {Thread.sleep(10); //休眠一会} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}//使用实现Runnable的方式——对象锁
class SellT2 implements Runnable {public static int sticket = 100;Dog dog = new Dog();@Overridepublic void run() {while (true) {//这个方式是创建三个Thread类型的实例来访问一个类型的实例,所以使用对象锁synchronized (dog) { //这个实例锁,可以是任何类型,只要是几个实例共享的即可if (SellT2.sticket <= 0) {return;}System.out.println(Thread.currentThread().getName() + "已经售出一张票,剩余 " + (-- SellT2.sticket));}try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
结果
9.线程的死锁
基本介绍
代码实例
package Thread__;/*** @author 孙显圣* @version 1.0*/
public class DeadLock {public static void main(String[] args) {new DeadLockDemo(false).start();new DeadLockDemo(true).start();}
}// 线程类
class DeadLockDemo extends Thread {//两个Object类型的互斥锁,满足多个线程共享的条件private static Object lock1 = new Object();private static Object lock2 = new Object();boolean flag;public DeadLockDemo(boolean flag) {this.flag = flag;}@Overridepublic void run() {if (flag == false) {synchronized (lock1) { //第一个线程会抢占这个lock1,再想抢lock2但是lock2被第二个线程先抢到了,所以无法释放lock1System.out.println("进入1");synchronized (lock2) {System.out.println("进入2");}}}else {synchronized (lock2) { //第二个线程会抢占这个lock2,再想抢lock1但是lock1被第一个线程先抢到了,所以无法释放lock2System.out.println("进入2");synchronized (lock1) {System.out.println("进入1");}}}}
}
结果
10.释放锁
以下操作会释放锁
以下操作不会释放锁
11.多线程练习题
题目一
代码
package Thread__;import java.util.Scanner;/*** @author 孙显圣* @version 1.0*/
public class HomeWork01 {public static void main(String[] args) {Th1 th1 = new Th1();new Th2(th1).start();th1.start();}
}class Th1 extends Thread {//设置一个私有属性通知变量private boolean loop = true;//设置get和set方法public boolean isLoop() {return loop;}public void setLoop(boolean loop) {this.loop = loop;}@Overridepublic void run() {while (loop){System.out.println(((int) (Math.random() * 100))); // 0 到 100try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}class Th2 extends Thread {//线程一类型的属性private Th1 th1;//设置一个构造方法获取线程一的实例public Th2(Th1 th1) {this.th1 = th1;}@Overridepublic void run() {Scanner scanner = new Scanner(System.in);while (true) {System.out.print("请输入英文字母:");char re = scanner.next().charAt(0);if (re == 'Q') {System.out.println("通知线程一退出");th1.setLoop(false);}}}
}
结果
题目二
代码
package Thread__;/*** @author 孙显圣* @version 1.0*/
public class HomeWork02 {public static void main(String[] args) {Tori tori = new Tori();for (int i = 0; i < 2; i++) {new Thread(tori).start();}}
}// 取钱线程类
class Tori implements Runnable {//设计一个实例变量即可,因为这个实例变量就是两个线程共享的了(原因是这个是实现接口的方式)private double sal = 10000;@Overridepublic void run() {//一直取钱while (true) {//取钱逻辑需要加锁synchronized (Tori.class) { //这里加一个类锁就可以,因为是多线程共享的if (sal <= 0) {return;}System.out.println(Thread.currentThread().getName() + "取出1000,剩余" + (sal -= 1000));}try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}