线程相关概念
进程
- 进程是指运行中的程序
- 进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程
线程
线程是由进程创建的,是进程的一个实体
一个进程可以拥有多个线程,如下图
其他相关概念
单线程:同一个时刻,只允许执行一个线程
多线程:同一个时刻,可以执行多个线程,比如:一个QQ进程,可以同时打开多个聊天窗口,一个迅雷进行,可以同时下载多个文件
并发:同一个时刻,多个任务交替执行,造成一种 貌似同时
的错觉,简单的说,单核CPU实现的多任务就是并发
并行:同一个时刻,多个任务同时执行。多核CPU可以实现并行。
注意:并发和并行有可能同时存在
两种实现方式
继承 Thread类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| public class Thread01 { public static void main(String[] args) throws InterruptedException { Cat cat = new Cat(); cat.start();
for (int i = 0; i < 60; i++) { System.out.println("主线程 i=" + i); Thread.sleep(1000); } } }
class Cat extends Thread { int nums = 0;
@Override public void run() { while (true) { System.out.println("我是cat" + (++nums) + "线程名:" + Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if (nums == 60) { break; } } } }
|
实现 Rannable接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| 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 nums = 0; @Override public void run() { while (true) { System.out.println("我是小狗..." + (++nums) + Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if (nums == 10) { break; } } } }
|
Rannable 可以进行资源共享,如果没有特别说明,建议使用 Rannable 接口
卖票
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| public class SellTicket { public static void main(String[] args) {
Test02 t1 = new Test02();
new Thread(t1).start(); new Thread(t1).start(); new Thread(t1).start();
} }
class Test01 extends Thread {
private static int votes = 20;
@Override public void run() { while (true) { if (votes <= 0) { System.out.println("卖光了~"); break; } try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + "剩余票数=" + (--votes)); } } }
class Test02 implements Runnable {
private int votes = 20;
@Override public void run() { while (true) { if (votes <= 0) { System.out.println("卖光了~"); break; } try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + "剩余票数=" + (--votes)); } } }
|
两种方式都会出现 超卖 现象
线程终止
基本说明:
- 当线程完成任务后,会自动退出
- 还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public class ThreadExit { public static void main(String[] args) throws InterruptedException { T1 t1 = new T1(); t1.start();
Thread.sleep(10000); t1.setLoop(false); } }
class T1 extends Thread {
private int count = 0; private boolean loop = true;
@Override public void run() { while (loop) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("T1 运行中...." + (++count)); } }
public void setLoop(boolean loop) { this.loop = loop; } }
|
常用方法
第一组
| 1. setName 2. getName 3. start 4. run 5. setPriority 6. getPriority 7. sleep 8. interrupt
|
注意事项和细节
- start底层会创建新的线程,调用run, run 就是一个简单的方法调用,不会启动新线程
- 线程优先级的范围
- interrupt,中断线程,但并没有真正的结束线程。所以一般用于中断正在休眠线程
- sleep:线程的静态方法,使当前线程休眠
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public class ThreadMethod01 { public static void main(String[] args) throws InterruptedException { T1 t1 = new T1(); t1.setName("我是线程啊"); t1.setPriority(Thread.MIN_PRIORITY); t1.start();
for (int i = 0; i < 5; i++) { Thread.sleep(1000); System.out.println("hi"); } t1.interrupt(); } }
class T1 extends Thread { @Override public void run() { while (true) { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + "吃包子 " + i); } try { System.out.println(Thread.currentThread().getName() + " 休眠中~~~"); Thread.sleep(20000); } catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " 被interrupt了"); } } } }
|
第二组
案例:创建一个子线程,每隔1s输出hello,输出20次,主线程每隔1秒,输出hi,输出20次.要求:两个线程同时执行,当主线程输出5次后,就让子线程运行完毕,主线程再继续,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public class ThreadMethod02 { 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();
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); } } }
|
用户线程和守护线程
用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
常见的守护线程:垃圾回收机制
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public class ThreadMethod03 { public static void main(String[] args) throws InterruptedException { MyDaemonThread t = new MyDaemonThread();
t.setDaemon(true); t.start();
for (int i = 1; i <= 10; i++) { System.out.println("我是线程2号...."); Thread.sleep(1000); } } }
class MyDaemonThread extends Thread { @Override public void run() { for (; ; ) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("哈哈哈哈,我是线程1号......"); } } }
|
线程的生命周期
JDK中用 Thread.State 枚举表示了线程的几种状态
线程状态。线程可以处于以下状态之一:
- [
NEW
]
尚未启动的线程处于此状态。 - [
RUNNABLE
]
在Java虚拟机中执行的线程处于此状态。 - [
BLOCKED
]
被阻塞等待监视器锁定的线程处于此状态。 - [
WAITING
]
正在等待另一个线程执行特定动作的线程处于此状态。 - [
TIMED_WAITING
]
正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。 - [
TERMINATED
]
已退出的线程处于此状态。
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| 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(500); }
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; } } }
|
Synchronized
可以直接在类上加,也可以使用加在代码块上
线程同步机制
- 在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性
- 也可以这样理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作
卖票的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| public class SellTicket { public static void main(String[] args) {
Test03 t1 = new Test03(); new Thread(t1).start(); new Thread(t1).start(); new Thread(t1).start();
} }
class Test03 implements Runnable {
private int votes = 100;
private boolean loop = true;
public synchronized void m() { if (votes <= 0) { System.out.println("卖光了~"); loop = false; return; } try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + "剩余票数=" + (--votes)); }
@Override public void run() { while (loop) { m(); } } }
|
互斥锁
- Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
- 每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
- 关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问
- 同步的局限性:导致程序的执行效率要降低
- 同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
- 同步方法(静态的)的锁为当前类本身。
注意事项和细节
- 同步方法如果没有使用 static 修饰:默认锁对象为 this
- 如果方法使用 static 修饰,默认锁对象:当前类.class
- 实现的步骤
- 需要先分析上锁的代码
- 选择
同步代码块
或同步方法 - 要求多个线程的锁对象为同一个即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| public class SellTicket02 { public static void main(String[] args) {
Test t1 = new Test(); new Thread(t1).start(); new Thread(t1).start(); new Thread(t1).start(); } }
class Test implements Runnable {
private int votes = 50;
private boolean loop = true;
public void m() { synchronized (this) { if (votes <= 0) { System.out.println("卖光了~"); loop = false; return; } try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + "剩余票数=" + (--votes)); }
}
public static void m2() { synchronized (Test.class) { System.out.println("hhh"); } }
@Override public void run() { while (loop) { m(); } } }
|
死锁
基本介绍
多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要逃避死锁的发生
案例:
妈妈:你先完成作业,才让你玩手机
小明:你先让我玩手机,我才完成作业
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| public class DeadLock { public static void main(String[] args) { Demo d1 = new Demo(true); Demo d2 = new Demo(false); d1.start(); d2.start(); } }
class Demo extends Thread { static Object o1 = new Object(); static Object o2 = new Object(); boolean flag;
public Demo(boolean flag) { this.flag = flag; }
@Override public void run() {
if (flag) { synchronized (o1) { System.out.println(Thread.currentThread().getName() + " 进入1"); synchronized (o2) { System.out.println(Thread.currentThread().getName() + " 进入2"); } } } else { synchronized (o2) { System.out.println(Thread.currentThread().getName() + " 进入3"); synchronized (o1) { System.out.println(Thread.currentThread().getName() + " 进入4"); } } } } }
|
释放锁
下面操作会释放锁
案例:上厕所,完事出来
- 当前线程在同步代码块、同步方法中遇到break、return
案例:没有正常的完事,经理叫他修改bug,不得已出来
- 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
案例:没有正常的完事,发现忘带纸,不得已出来
下面操作不会释放锁
线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁
案例:上厕所,太困了,在坑位上眯了一会
线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁。
提示:应尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用