目录
1.线程相关概念
1.1 程序
1.2 进程
1.3 线程
1.4 并发与并行
2.线程的基本使用
2.1 创建线程的两种方式
2.2 线程应用案例1.继承Thread类
2.3 线程应用案例2-实现Runnable接口
2.4 线程使用应用案例-多线程执行
2.5 继承Thread类 和 实现Runnable接口的区别
3. 线程常用方法
3.1 常用方法第一组
3.2 常用方法第二组
4. 用户线程和守护线程
4.1 基本介绍:
4.2 如何将一个线程设置成守护线程(setDaemon)
5. 线程的生命周期
5.1 线程的几种状态
5.2 编写程序查看线程的几种状态
6. 线程的同步
6.1 线程同步机制
6.2 同步具体方法(Synchronized )
6.3 同步分析
编辑
7. 互斥锁
7.1 互斥锁基本介绍
7.2 实现的步骤
7.3 多线程模拟售票问题
8. 线程的死锁
9. 释放锁
是为了完成特定任务,用某种语言编写的一组指令的集合。
简单的说,就是我们写的代码
线程是由进程创建的,是进程的一个实体
一个进程可以拥有多个线程,如下:
单线程:同一个时刻,只允许执行一个线程
多线程:同一个时刻,可以执行多个线程,比如:一个qq进程,可以同时打开多个聊天窗口,一个迅雷进程,可以下载多个文件
并发:同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简单地说,单核cpu实现的多任务就是并发。(比如当一个人在开车时,他不可以做其他事情,比如打电话)
并行:同一个时刻,多个任务同时执行,多核cpu可以实现并行,并发和并行
在Java中线程常用的有两种方法
要求:编写一个线程,该线程每隔一秒,在控制台输出“喵喵,我是小猫咪”。当输出10次时,结束该线程。可以使用JConsole监控线程执行情况。
public class Thread01 {
public static void main(String[] args) throws InterruptedException {
//创建 Cat 对象,可以当做线程使用
Cat1 cat = new Cat1();
cat.start();
//启动线程-> 最终会执行 cat 的 run 方法
//run 方法就是一个普通的方法, 没有真正的启动一个线程,就会把 run 方法执行完毕,才向下执行
// 说明: 当 main 线程启动一个子线程 Thread-0, 主线程不会阻塞, 会继续执行
//这时 主线程和子线程是交替执行..
System.out.println("主线程继续执行" + Thread.currentThread().getName());
for (int i = 0; i < 10; i++) {
System.out.println("主线程 i=" + i);
//让主线程休眠
Thread.sleep(1000);
}
}
//1. 当一个类继承了 Thread 类, 该类就可以当做线程使用
//2. 我们会重写 run 方法,写上自己的业务代码
//3. Thread 类 实现了 Runnable 接口的 run 方法
}
class Cat1 extends Thread {
int times = 0;
@Override
public void run() {//重写 run 方法,写上自己的业务逻辑
while (true) {
//该线程每隔 1 秒。在控制台输出 “喵喵, 我是小猫咪”
System.out.println("喵喵, 我是小猫咪" + (++times) + " 线程名=" + Thread.currentThread().getName());
//让该线程休眠 1 秒
try {//此时有异常,需要处理或抛出
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (times == 10) {
break;//当 times 到 10, 退出 while, 这时线程也就退出..
}
}
}
}
运行结果:
主线程继续执行main
喵喵, 我是小猫咪1 线程名=Thread-0
主线程 i=0
主线程 i=1
喵喵, 我是小猫咪2 线程名=Thread-0
喵喵, 我是小猫咪3 线程名=Thread-0
主线程 i=2
主线程 i=3
喵喵, 我是小猫咪4 线程名=Thread-0
主线程 i=4
喵喵, 我是小猫咪5 线程名=Thread-0
主线程 i=5
喵喵, 我是小猫咪6 线程名=Thread-0
喵喵, 我是小猫咪7 线程名=Thread-0
主线程 i=6
喵喵, 我是小猫咪8 线程名=Thread-0
主线程 i=7
主线程 i=8
喵喵, 我是小猫咪9 线程名=Thread-0
主线程 i=9
喵喵, 我是小猫咪10 线程名=Thread-0
可以看出,主线程和cat线程是交替执行的。
->start()方法的底层调用了start0方法。
(暂时不懂没关系)
说明:
应用案例:编写程序,该程序每隔一秒,在控制台输出“hi”,当输出10次后,自动退出。
public class Thread2 {
public static void main(String[] args) {
Hi hi = new Hi();
//hi.start; 不能这样做
Thread thread = new Thread(hi);
thread.start();
}
}
class Hi implements Runnable {
int count = 1;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("hi" + count++);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:(每隔一秒输出一次)
hi1
hi2
hi3
hi4
hi5
hi6
hi7
hi8
hi9
hi10
要求:请编写一个程序,创建两个线程,一个线程每隔1秒输出“hello,world”,输出10次,退出,另一个线程每隔一秒输出“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();//启动第 1 个线程
thread2.start();//启动第 2 个线程
}
}
class T1 implements Runnable {
int count = 0;
@Override
public void run() {
while (true) {
//每隔 1 秒输出 “hello,world”,输出 10 次
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() {
//每隔 1 秒输出 “hi”,输出 5 次
while (true) {
System.out.println("hi " + (++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 5) {
break;
}
}
}
}
运行结果:
hello,world 1
hi 1
hello,world 2
hi 2
hi 3
hello,world 3
hello,world 4
hi 4
hi 5
hello,world 5
hello,world 6
hello,world 7
hello,world 8
hello,world 9
hello,world 10
-> 可以看出两个线程都存在时,是交替执行的
setName 设置线程名称
getName 返回该线程的名称
start 使线程开始执行
run 调用线程对象run方法
setPriority 更改线程的优先级
getPriority 获取线程的优先级
sleep 让当前正在执行的线程休眠指定毫秒数
interrupt 中断线程
-> 测试join方法:
public class ThreadMethodExercise {
public static void main(String[] args) throws InterruptedException {
T2 t2 = new T2();
Thread thread = new Thread(t2);
for (int i = 1; i <= 5; i++) {
Thread.sleep(1000);
System.out.println("hi"+ i );
if(i==3){
thread.start();
thread.join();//线程插队
}
}
}
}
class T2 implements Runnable {
private int count = 0;
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello" + (++count));
if (count == 5) {
break;
}
}
}
}
运行结果:
hi1
hi2
hi3
hello1
hello2
hello3
hello4
hello5
hi4
hi5
/**
* 把一个线程设置为守护线程
* 守护线程:当所有的用户线程结束时,守护线程自动结束。
* 这里MyDaemonThread为守护线程
* main线程为用户线程44
*/
public class ThreadMethod02 {
public static void main(String[] args) throws InterruptedException {
MyDaemonThread myDaemonThread = new MyDaemonThread();
//如果我们希望当main线程结束后,子线程自动结束
//将子线程设置为守护线程即可
myDaemonThread.setDaemon(true); //daemon 守护线程
myDaemonThread.start();
for (int i = 1; i <= 3 ; i++) {
System.out.println("主线程......");
Thread.sleep(1000);
}
}
}
class MyDaemonThread extends Thread{//守护线程
@Override
public void run() {
while(true){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("守护线程....");
}
}
}
运行结果:
public class ThreadState_ {
public static void main(String[] args) throws InterruptedException {
TT t = new TT();
System.out.println(t.getName() + " 状态 " + t.getState());
t.start();
while (Thread.State.TERMINATED != t.getState()) { //当t的状态不是TERMINATED时
System.out.println(t.getName() + " 状态 " + t.getState());
Thread.sleep(500);
}
//线程结束的状态
System.out.println(t.getName() + " 状态 " + t.getState());
}
}
class TT extends Thread {
@Override
public void run() {
while (true) {
for (int i = 0; i < 3; i++) {
System.out.println("hi " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
}
}
}
运行结果:
Thread-0 状态 NEW
Thread-0 状态 RUNNABLE
hi 0
Thread-0 状态 TIMED_WAITING
hi 1
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TIMED_WAITING
hi 2
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TERMINATED
在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步线程访问技术,保证数据在任意同一时刻,最多有一个线程访问,以保证数据的完整性
也可以这样理解:当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才可对该内存地址进行操作
1.同步代码块
Synchronized(对象){ //得到对象的锁,才能操作同步代码)
//需要同步代码
}
2. 同步方法
public Synchronized void m(String name){
//需要被同步的代码
}
->多个线程会去争夺一把锁,每次只能有一个线程进入
/**
* 使用多线程,模拟三个接口同时售票
* 若不使用线程同步机制,会导致票数超卖,小伙伴们可以自己去尝试
*/
public class SellTicket {
public static void main(String[] args) {
//多个线程的锁对象必须为同一个
SellTicked01 sellTicked01 = new SellTicked01();
new Thread(sellTicked01).start();
new Thread(sellTicked01).start();
new Thread(sellTicked01).start();
// 错误的写法
// SellTicked01 sellTicked01 = new SellTicked01();
// SellTicked01 sellTicked02 = new SellTicked01();
// SellTicked01 sellTicked03 = new SellTicked01();
//
// sellTicked01.start();
// sellTicked02.start();
// sellTicked03.start();
// new了三个对象 所以锁不住
}
}
//使用synchronized实现线程同步 在方法上加锁
class SellTicked01 implements Runnable {
private /*static*/ int tickNum = 10; //让多个线程共享tickNum
private /*static*/ boolean loop = true;
// 同步方法,在同一时刻,只能有一个线程来执行sell 方法
// 这时锁在this对象
// 也可以在代码块上写synchronized -> 同步代码块
public /*static*/ synchronized void sell() { //在静态方法中只能访问静态属性或静态方法
if (tickNum <= 0) {
System.out.println("售票结束");
loop = false;
return;
}
//休眠50毫秒,模拟
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票 " + "剩余票数=" + (--tickNum));
}
@Override
public void run() {
while (loop) {
sell(); //sell方法是一个同步方法
}
}
}
//使用synchronized实现线程同步 同步代码块实现
class SellTicked04 implements Runnable {
private int tickNum = 10; //让多个线程共享tickNum
private boolean loop = true;
// 同步方法,在同一时刻,只能有一个线程来执行sell 方法
// 这时锁在this对象
// 也可以在代码块上写synchronized -> 同步代码块 互斥锁还是加在this对象
public /*synchronized */ void sell() {
synchronized (this) {
if (tickNum <= 0) {
System.out.println("售票结束");
loop = false;
return;
}
//休眠50毫秒,模拟
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票 " + "剩余票数=" + (--tickNum));
}
}
@Override
public void run() {
while (loop) {
sell();
}
}
}
运行结果:
窗口Thread-0售出一张票 剩余票数=9
窗口Thread-0售出一张票 剩余票数=8
窗口Thread-0售出一张票 剩余票数=7
窗口Thread-2售出一张票 剩余票数=6
窗口Thread-2售出一张票 剩余票数=5
窗口Thread-2售出一张票 剩余票数=4
窗口Thread-2售出一张票 剩余票数=3
窗口Thread-2售出一张票 剩余票数=2
窗口Thread-2售出一张票 剩余票数=1
窗口Thread-2售出一张票 剩余票数=0
售票结束
售票结束
售票结束
->基本介绍:死锁是指两个或两个以上的进程(线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程(线程)称为死锁进程(线程)。
->如下图:线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。
模拟线程死锁:
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");
}
}
}
}
}
运行结果:(我们会发现程序会堵塞,进入了死锁状态,无法继续运行)
会释放锁的操作:
不会释放锁的操作: