Java-Thread多线程的使用
- 一、线程,进程,并发,并行的概念
- 1.进程
- 2.线程
- 3.并发和并行
- 二、线程的创建和使用
- 1.通过继承Thread类,重写run方法
- 2.实现Runnable接口,重写run方法
- 3.使用案例
- 三、线程的常用方法
- 四、线程的退出和中断
- 1.线程的退出
- 2.线程的中断
- 五、用户线程和守护线程
- 1.用户线程
- 2.守护线程
- 六、线程的生命周期和线程的状态
- 1.线程的生命周期
- 2.线程的状态
- 七、线程同步sync及线程的死锁
- 1.线程的同步 synchronized
- 售票问题(超卖产生的原因以及互斥锁解决超卖)
- 2.线程的死锁
一、线程,进程,并发,并行的概念
1.进程
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
程序是指令、数据及其组织形式的描述,进程是程序的实体。
进程是程序的一次执行过程,或者正在运行的程序,具有动态的生命周期,从创建,运行到消亡。
如下图:IDEA,浏览器,微信,任务管理器…都是一个进程
2.线程
线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
线程是由进程创建的,是进程的一个实体,一个进程中可以拥有多个线程。
单线程:同一时刻,只允许执行一个线程。
多线程:同一时刻可以执行多个线程。
3.并发和并行
并行,指的是多个事情,在同一时间点上同时发生了( 并行的多个任务之间是不互相抢占资源的, 只有在多CPU的情况中,才会发生并行)。
并发,指的是多个事情,在同一时间段内同时发生了( 并发的多个任务之间是互相抢占资源的)。
同一时刻,多个任务交替执行,“同时拥有主机”的错觉,单核CPU实现的多任务就是并发。
二、线程的创建和使用
1.通过继承Thread类,重写run方法
class Demo extends Thread{
@Override
public void run() {
super.run();
}
}
在run方法中写入自定义的业务代码即可。
通过继承Thread类的方式实现多线程,开启多线程时需要直接调用类对象.start()即可。
Demo demo = new Demo();
demo.start();
2.实现Runnable接口,重写run方法
class Demo implements Runnable{
@Override
public void run() {
}
}
在run方法中写入自定义的业务代码即可。
通过实现Runnable接口的方式实现多线程,开启多线程时,需要将实现类对象丢给Thread类,以静态代理的模式去,然后使用线程类对象.start()启动多线程。
Demo demo = new Demo();
Thread thread = new Thread(demo);
thread.start();
3.使用案例
package com.robin;
public class ThreadInitDemo {
public static void main(String[] args) {
// 线程的创建 两种方式
// 1.继承Thread 通过start方法开启线程
TInit01 thread1 = new TInit01();
thread1.start();
// 2.实现Runnable接口
TInit02 t2 = new TInit02();
Thread thread2 = new Thread(t2);
thread2.start();
}
}
class TInit01 extends Thread{
@Override
public void run() {
for (int i = 0; i < 60; i++) {
// 休眠100毫秒
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程名 ==== "+Thread.currentThread().getName()+" "+i);
}
}
}
class TInit02 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
// 休眠100毫秒
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程名 ==== "+Thread.currentThread().getName()+" "+i);
}
}
}
三、线程的常用方法
方法名 | 功能 |
---|---|
setName | 用来设置当前线程的名称 |
getName | 用来获取当前线程的名称 |
start | 使该线程开始执行,底层是start0()由jvm虚拟机进行调度 |
run | 调用线程对象的run方法 |
setPriority | 设定线程的优先级 |
getPriority | 获取线程的优先级 |
sleep | 使当前线程休眠指定的毫秒数 |
interrupt | 中断线程(中断正在休眠的进程,其实就是wakeUp唤醒进程) |
四、线程的退出和中断
1.线程的退出
线程退出:
- 当线程执行完成任务之后,会自动退出。
- 通过使用变量控制run()方法退出的方式。
package com.robin;
public class ThreadExit01 {
public static void main(String[] args) throws InterruptedException {
// 线程退出
// 1.当线程执行完任务之后,会自动退出
// 2.通过使用变量控制run()方法退出的方式 通知方式
TExit tExit = new TExit();
// 启动线程
Thread thread = new Thread(tExit);
thread.start();
// 原本的run()方法是无限循环,
// 现在通过在主线程休眠10秒后,然后变量flag置为false,从而使run方法退出
Thread.sleep(10*1000);
tExit.setFlag(false);
}
}
class TExit implements Runnable{
// run方法中的循环控制变量
private boolean flag = true;
public void setFlag(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
while (flag){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("舒克舒克~~~~~");
}
}
}
2.线程的中断
线程的中断:
- yield 线程的礼让,让出cpu,但是让出cpu的时间不确定,所以不一定成功
- join 线程的插队(“中断”),插队线程插入成功后,立刻执行插入的线程,直到执行完插入的线程之后,才接着执行被中断的线程。
package com.robin;
public class ThreadInterrupt01 {
public static void main(String[] args) throws InterruptedException {
// 线程的中断
// 1.yield 线程的礼让,让出cpu,但是让出cpu的时间不确定,所以不一定成功
// 2.join 线程的插队(“中断”),插队线程插入成功后,立刻执行插入的线程,直到执行完插入的线程之后,才接着执行被中断的线程
InterruptT t1 = new InterruptT();
// 开启多线程
Thread thread = new Thread(t1);
thread.start();
// main 线程,当 main线程的for执行5次后,中断执行子线程thread(原本两人是交替执行的,执行中断以后,子线程执行完毕以后,主线程才会接着执行)
for (int i = 0; i < 20; i++) {
Thread.sleep(1000);
System.out.println("线程==>"+Thread.currentThread().getName()+"===>hi!!!!!"+i);
if(i==5){
thread.join();
}
}
}
}
class InterruptT implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程==>"+Thread.currentThread().getName()+"==>hello,"+i);
}
}
}
五、用户线程和守护线程
1.用户线程
用户线程,也叫工作线程,当线程的任务执行完毕后或者使用通知方式结束
2.守护线程
守护线程,为工作线程服务,当所有的用户线程结束后,守护线程自动结束。
可以通过线程对象.setDaemon()的方式来设置一个普通的用户线程为一个守护线程,然后需要注意的是,必须在线程启动之前设置否则会抛出异常 IllegalThreadStateException
package com.robin;
public class ThreadDaemon01 {
public static void main(String[] args) throws InterruptedException {
ThreadDaemon_t threadDaemon_t = new ThreadDaemon_t();
// 守护线程的设置可以通过,线程.setDaemon()方法来设置,且必须是在线程启动之前
Thread thread = new Thread(threadDaemon_t);
thread.setDaemon(true); // ture为设置守护线程
thread.start();
for (int i = 0; i < 10; i++) {
System.out.println("舒克在开飞机=========");
// 设置休眠时间
Thread.sleep(1000);
}
}
}
class ThreadDaemon_t implements Runnable{
@Override
public void run() {
// 设置一个无限循环,不停的输出
while (true){
// 设置一个线程休眠时间,不然因为循环太少不好观察
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("贝塔在开坦克~~~~");
}
}
}
六、线程的生命周期和线程的状态
1.线程的生命周期
tip: 这里就直接用韩老师的图了…
2.线程的状态
在Java中,线程的状态一共有六种,其实也可以算作七种,因为RUNNABLE可以分为 就绪态 (ready)和 运行态 (running)。
- 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
- 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。 - 阻塞(BLOCKED):表示线程阻塞于锁。
- 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
- 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
- 终止(TERMINATED):表示该线程已经执行完毕。
七、线程同步sync及线程的死锁
1.线程的同步 synchronized
线程同步机制,即保证数据在任何同一时刻内,只能最多被一个线程访问,以保证数据的完整性。
在Java中提供了关键字 synchronized ,用于实现线程的同步。
-
同步代码块
// synchronized 其实提供了一把锁 synchronized(对象){ // 同步执行的代码,或者共享的资源 }
-
同步方法
synchornized 放到方法声明中,表示该方法为同步方法
public synchornized void 方法名( 参数){ // 同步执行的代码,或者共享的资源 }
互斥锁
- 1.Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
- 2.每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任意时刻,只能有一个线程访问该对象。
- 3.关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。
- 4.同步的局限性:导致程序的执行效率降低
- 5.同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
注意事项:
- 1.如果同步方法没有使用static修饰,那么默认的锁对象为this
- 2.如果同步方法有static修饰,那么默认的锁对象为:当前类.class
售票问题(超卖产生的原因以及互斥锁解决超卖)
某车站售卖车票,分4个窗口,同时进行售卖,一共有两百张票:
package com.robin;
public class SellTicketSync {
public static void main(String[] args) {
// 利用线程同步来解决之前的多线程售卖票数超卖的问题
SellT sellT = new SellT();
// 开启线程
new Thread(sellT).start();
new Thread(sellT).start();
new Thread(sellT).start();
new Thread(sellT).start();
}
}
class SellT implements Runnable{
private int ticketNums = 200;
@Override
public void run() {
while (true){
if(ticketNums<=0){
System.out.println("票卖光了");
break;
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"窗口"+"售卖了一张票,还剩余"+(--ticketNums));
}
}
}
运行结果:出现了票是负数的情况。
原因是,在票数为1的时候,四个线程可以同时进入,因为它们当前的访问到的票数都是大于0的,然后4个线程进行票数减减操作,所以会出现负数。
利用互斥锁,解决上面票数超卖的问题:
package com.robin;
public class SellTicketSync {
public static void main(String[] args) {
// 利用线程同步来解决之前的多线程售卖票数超卖的问题
SellT sellT = new SellT();
// 开启线程
new Thread(sellT).start();
new Thread(sellT).start();
new Thread(sellT).start();
new Thread(sellT).start();
}
}
class SellT implements Runnable{
private int ticketNums = 200;
private boolean flag = true;
// 使用 synchronized 使sell()方法成为一个同步方法块儿
public synchronized void sell(){
if(ticketNums<=0){
System.out.println("票卖光了");
flag = false;
return;
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"窗口"+"售卖了一张票,还剩余"+(--ticketNums));
}
@Override
public void run() {
while (flag){
sell();
}
}
}
2.线程的死锁
死锁产生的原因,就是同时竞争同一个共享资源,多个线程都占用了对方的锁资源,谁都不想让谁先获取,导致了死锁,
比如最常见的 ,线程A占有资源1,同时想占有资源2,线程B占有资源2,也想占有资源1,但是资源1和资源2都只有一份便形成了死锁。
产生死锁的片段:
if (flag){
synchronized (o1){
System.out.println(Thread.currentThread().getName()+"拿到了 o1 ");
synchronized (o2){
System.out.println(Thread.currentThread().getName()+"拿到了 o2 ");
}
}
}else{
synchronized (o2){
System.out.println(Thread.currentThread().getName()+"拿到了 o2 ");
synchronized (o1){
System.out.println(Thread.currentThread().getName()+"拿到了 o1 ");
}
}
}
完整的死锁demo:
package com.robin;
public class DeadLock01 {
public static void main(String[] args) {
// 模拟死锁,就是同时对共享资源的争夺
DeadT t1 = new DeadT(true);
DeadT t2 = new DeadT(false);
//开启多线程
new Thread(t1).start();
new Thread(t2).start();
// 产生死锁 ,打印下面两句后无法继续执行
//Thread-0拿到了 o1
//Thread-1拿到了 o2
}
}
class DeadT implements Runnable{
// 定义两个资源,必须用static,否则资源就不是这两个了
static Object o1 = new Object();
static Object o2 = new Object();
// 循环标志
private boolean flag ;
public DeadT(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag){
synchronized (o1){
System.out.println(Thread.currentThread().getName()+"拿到了 o1 ");
synchronized (o2){
System.out.println(Thread.currentThread().getName()+"拿到了 o2 ");
}
}
}else{
synchronized (o2){
System.out.println(Thread.currentThread().getName()+"拿到了 o2 ");
synchronized (o1){
System.out.println(Thread.currentThread().getName()+"拿到了 o1 ");
}
}
}
}
}