一、线程
1、概念介绍



2、单线程和多线程

3、并发和并行

二、线程基本使用
1、创建线程的两种方式


(1)案例1:继承 Thread 类

注:在多线程编程里面,不是main方法结束了,就意味着进程就结束了
//演示通过继承 Thread 类创建线程
public class Thread01 {
public static void main(String[] args) throws InterruptedException {
//创建一个Cat 对象,可以当成线程使用
Cat cat = new Cat();
cat.start();//启动线程
// cat.run(); // run方法就是一个普通的方法,并没有真正的启动一个线程,
// 就会把run方法执行完毕,才会向下执行, 即会阻塞
//说明:当main线程启动一个子线程后,主线程不会阻塞,会继续执行
//这时,主线程和子线程是交替执行的
System.out.println("主线程继续执行"+"名字为:"+Thread.currentThread().getName());//名字为main
for (int i = 0; i < 60; i++) {
System.out.println("主线程 i+"+i);
//让主线程休眠
Thread.sleep(1000);
}
}
}
//说明
//1、当一个类继承了 Thread 类,该类就可以当做线程使用
//2、我们会重写 run 方法,写上自己的业务代码
//3、run Thread 类实现了 Runnable 接口的run方法
class Cat extends Thread{
int times = 0;
@Override
public void run() { //重写 run 方法,写上自己的逻辑
while (true) {
//每隔一秒,在控制台输出“喵喵,我是小猫咪”
System.out.println("喵喵,我是小猫咪"+(++times)+"线程名:"+Thread.currentThread().getName());
//让该线程休眠1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(times == 80){
break;//当times 等于 80,退出 while循环,这时线程也就退出了
}
}
}
}

案例2:实现 Runnable 接口


//通过实现接口 Runnable 来开发线程
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;
@Override
public void run() {
while (true) {
System.out.println("hi"+"次数="+(++count)+Thread.currentThread().getName());
//休眠1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(count == 10){
break;
}
}
}
}
案例3:多线程执行

//在main线程启动两个子线程
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() {
//每隔一秒输出“Hello,world”,输出10次
while (true) {
System.out.println("Hello,world "+(++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() {
//每隔一秒输出“hi”,输出5次
while (true) {
System.out.println("hi "+(++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 5){
break;
}
}
}
}
2、继承 Thread 与实现 Runnable 的区别

3、线程终止

案例:

public class ThreadExit_ {
public static void main(String[] args) throws InterruptedException {
T t1 = new T();
t1.start();
//如果希望main线程去控制t1线程的终止,必须可以修改 loop(设置一个set方法)
//让t1退出run方法,从而终止t1线程 -> 通知方式
//让主线程休眠 10s,再通知t1线程退出
Thread.sleep(10*1000);
t1.setLoop(false);
}
}
class T extends Thread{
private int count = 0;
//设置一个控制变量
private boolean loop = true;
@Override
public void run() {
while (loop) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("T在运行中"+(++count));
}
}
public void setLoop(boolean loop) {
this.loop = loop;
}
}
4、线程常用方法




public class ThreadMethod01 {
public static void main(String[] args) throws InterruptedException {
T2 t2 = new T2();
t2.start();
for (int i = 0; i <= 20; i++) {
Thread.sleep(1000);
System.out.println("主线程" + i);
if (i == 5) {
System.out.println("主线程让子线程");
t2.join();//这里相当于让 t2 线程先执行完毕
//Thread.yield();//礼让,不一定成功
System.out.println("子线程运行完毕");
}
}
}
}
class T2 extends Thread {
@Override
public void run() {
for (int i = 0; i <= 20; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程" + i);
}
}
}
5、用户线程和守护线程

案例:如何将一个线程设置为守护线程

三、线程的生命周期(七种状态)

线程状态转化图:

四、线程同步机制(Synchronized)
1、线程同步机制

2、同步具体方法---Synchronized

案例:三个窗口同时卖票
/使用多线程模拟三个窗口同时售票 100 张
public class SellTicket {
public static void main(String[] args) {
SellTicket03 sellTicket03 = new SellTicket03();
new Thread(sellTicket03).start();
new Thread(sellTicket03).start();
new Thread(sellTicket03).start();
}
}
//使用接口方式,使用同步方法 synchronized 实现线程同步
class SellTicket03 implements Runnable {
private int ticketNum = 100;//让多个线程共享ticketNum
private boolean loop = true;//控制run方法的变量
public synchronized void sell(){//用的同步方法,在同一时刻,只能有一个线程来执行 sell 方法
if (ticketNum <= 0){
System.out.println("售票结束");
loop = false;
return;//退出方法
}
//休眠50毫秒,模拟
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 "+ Thread.currentThread().getName()+" 售出一张票"
+" 剩余票数"+(--ticketNum));
}
@Override
public void run() {
while (loop) {
sell();//sell是一个同步方法
}
}
}
同步原理:

3、互斥锁

//使用多线程模拟三个窗口同时售票 100 张
public class SellTicket {
public static void main(String[] args) {
SellTicket03 sellTicket03 = new SellTicket03();
new Thread(sellTicket03).start();
new Thread(sellTicket03).start();
new Thread(sellTicket03).start();
}
}
//使用接口方式,使用同步方法 synchronized 实现线程同步
class SellTicket03 implements Runnable {
private int ticketNum = 100;//让多个线程共享ticketNum
private boolean loop = true;//控制run方法的变量
Object object = new Object();
//同步方法(静态的)的锁为当前对象
//说明:
//1、public synchronized static void m1(){} 锁是加在 SellTicket03.class 上的
//2、如果在静态方法中,实现一个同步代码块
/*
synchronized(SellTicket03.class){
System.out.println("m2");
}
*/
public synchronized static void m1(){
}
public static void m2(){
synchronized(SellTicket03.class){
System.out.println("m2");
}
}
//说明:
//1、public synchronized void sell(){} 就是一个同步方法
//2、这时锁在 this 对象
//3、也可以在代码块上写 synchronized,同步代码块,互斥锁还是在 this 对象
public /*synchronized*/ void sell() {//用的同步方法,在同一时刻,只能有一个线程来执行 sell 方法
//三个线程操作的是同一个Object
synchronized (/*this*/object) {//同步代码块,互斥锁还是在 this 对象
if (ticketNum <= 0) {
System.out.println("售票结束");
loop = false;
return;//退出方法
}
//休眠50毫秒,模拟
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
+ " 剩余票数" + (--ticketNum));
}
}
@Override
public void run() {
while (loop) {
sell();//sell是一个同步方法
}
}
}
互斥锁的注意事项:

注:1、要求多个线程的锁对象为同一个
2、尽量选择同步代码块,因为同步代码块锁的范围小,效率高
五、线程死锁


注:死锁非常危险,写代码时一定要避免
六、释放锁
1、下面操作会释放锁

2、下面操作不会释放锁

练习1:

public class HomeWork01 {
public static void main(String[] args) {
A a = new A();
a.start();
B b = new B(a);//一定要注意传入a
b.start();
}
}
//创建A线程类
class A extends Thread{
private boolean loop = true;
public void setLoop(boolean loop) {//可以修改loop变量
this.loop = loop;
}
@Override
public void run() {
//输出1到100
while (loop) {
System.out.println((int)(Math.random()*100+1));
//休眠
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//直到第2个线程从键盘读取了“Q”命令
class B extends Thread{
private A a;
private Scanner scanner = new Scanner(System.in);
public B(A a) {//构造器中,直接传入A类对象
this.a = a;
}
@Override
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:(一定要理解原理)

public class HomeWork02 {
public static void main(String[] args) {
T t = new T();
Thread thread1 = new Thread(t);
thread1.setName("t1");
Thread thread2 = new Thread(t);
thread2.setName("t2");
thread1.start();
thread2.start();
}
}
//编辑取款线程
//1、因为这里涉及到多个线程共享资源,所以我们使用实现Runnable方式
class T implements Runnable {
private int money = 10000;
@Override
public void run() {
while (true) {
//1、这里使用了synchronized实现了线程同步
//2、当多个线程执行到这里时,就会去争夺 this 对象锁
//3、哪个线程争夺到(获取到)this对象锁就执行 synchronized 代码块,执行完后,会释放this对象锁
//4、争夺不到的this对象锁的就会被阻塞(blocked),准备继续下次争夺
synchronized(this) {
//判断余额是否足够
if (money < 1000) {
System.out.println("余额不足");
break;
}
money -= 1000;
System.out.println(Thread.currentThread().getName()
+ "取出了1000块,当前余额= " + money);
}
//休眠
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

这篇博客详细介绍了Java中的线程基础知识,包括线程的概念、创建方式(继承Thread类和实现Runnable接口)、线程的生命周期及状态转换,重点讨论了线程同步机制如Synchronized关键字和互斥锁,并提醒了避免线程死锁的重要性。同时,文章还提到了线程锁的释放条件和相关练习题,帮助读者深入理解线程管理。
1228

被折叠的 条评论
为什么被折叠?



