多线程基础知识

课程内容

1.线程基础知识:线程创建。线程状态.线程转换关系,方法
2.并发与并行,并发的特征
3.Synchronized
4.volatile关键字 5。线程间通信 notify,notifyAll,wait
6.生产者,消费者类型

线程基础知识介绍

程序,进程和线程

程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一 段静态的代码,静态对象。

进程:是计算机运行的一个独立的应用程序,进程是一个动态的概念 必须是运行状态,如果一个应用成组没有启动,那就不是一个进程
如:运行中的QQ,运行中的MP3播放器
程序是静态的,进程是动态的
进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域

线程
若一个进程同一时间并行执行多个线程,就是支持多线程的
线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开
销小
一个进程中的多个线程共享相同的内存单元/内存地址空间它们从同一堆中分配对象,可以
访问相同的变量和对象。这就使得线程间通信更简便、高效。
但多个线程操作共享的系统资源可能就会带来安全的隐患

在这里插入图片描述

进程和线程区别

1.内存空间的区别
进程是有独立空间的,在线程创建时就会分配空间。每个进程的空间是相互独立的,互不影响
线程有共享的空间,也有独立的空间
收到
堆空间:线程共有 虚拟机栈:线程私有
2.线程安全性
进程之间是相互独立的,一个线程的崩溃不会影响到其他线程的执行,进程之间是安全的
线程之间存在内存共享,一个线程的崩溃可能会影响到其他线程的执行,线程的安全性不如进程
java中很少使用到进程的概念,但也可以使用

 //启动一个进程
     Rutime runtime = Runtime.getRuntime();

线程的创建和启动

在这里插入图片描述

注意点:

  1. 如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。
  2. run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU 调度决定。
    3.想要启动多线程,必须调用start方法。
  3. 一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上 的异常“IllegalThreadStateException”。

构造函数

//创建新的Thread对象
public Thread() {}
//常见线程并制定线程实例名
public Thread(String name) {}   
//指定创建线程的目标对象,它实现了Runnable接
//口中的run方法
public Thread(Runnable target) {
  }
//创建新的Thread对象
 public Thread(Runnable target, String name)  

线程创建的三种方式

1. 继承Thread类

public class Thread212 extends Thread {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 子线程正在执行");
    }

}
class Testdemo{
    public static void main(String[] args) {
        Thread212 thread212 = new Thread212();
       // thread212.setName("");
        thread212.start();
        //如何实现多线程
        new Thread212().start();
        new Thread212().start();
        System.out.println(Thread.currentThread().getName() + " 主线程正在执行");
    }
}

代码运行结果
在这里插入图片描述
小练习:用继承的方式创建多线程,实现多个窗口买票
在这里插入图片描述

2. 实现Runable接口

public class Runable212 implements Runnable {
    @Override
    public void run() {
        int i = 0;
        while (i++ < 3) {
            System.out.println(Thread.currentThread().getName() + " 子线程正在执行");
        }
    }
}
class RunableTest{
    public static void main(String[] args) {
        Runable212 runable212 = new Runable212();
        Thread thread = new Thread(runable212);
        thread.start();
        int i=0;
        while(i++ <3){
            System.out.println(Thread.currentThread().getName()+"主线程正在执行");
        }
    }
}

代码运行结果:
在这里插入图片描述
需要注意的是根据CPU的不同,程序每次输出的顺序不一样。这是为什么呢?
在这里插入图片描述
当当当:当然因为我们是多线程呀!,main 函数和子线程各占一个CPU,但只有一个print具体的顺序取决于时间啦!
在这里插入图片描述
如果是我们之前的继承方式:就不是多线程了,每次输出的顺序也是固定的
在这里插入图片描述
比较创建线程的两种方式。
开发中:优先选择;实现Runnable接口的方式
原因:
1.实现的方式没有类的单继承性的局限性
2.实现的方式更适合来处理多个线程有共享数据的情况。
联系:
public class Thread impl ements Runnable
相同点:
两种方式都需要重写run(),将线程要执行的逻辑声明在run()中.
!

1.例子:创建三个窗口卖票,总票数为100张,用继承Thread的方法
存在线程安全问题

class Window extends  Thread{
    private  int ticket =1;
    @Override
    public void run() {
        while(true){
            if(ticket>0){
                //System.out.println(Thread.currentThread().getName()+"");
                System.out.println(getName()+": 买票,票号为:"+ticket);
                ticket--;
            }else{
                break;
            }
        }
    }
}
public class TicketDEMO {
    public static void main(String[] args) {
        Window t1 = new Window();
        Window t2 = new Window();
        Window t3 = new Window();
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}

在这里插入图片描述

  • 解决方案,换成staic 变量,共享对象

在这里插入图片描述

2.例子:创建三个窗口卖票,总票数为100张,使用Runnable接口的方式

class MyYThread implements Runnable{
    private  int ticket =3;
    @Override
    public void run() {
        while(true){
            if(ticket>0){
                System.out.println(Thread.currentThread().getName()+": 买票,票号为:"+ticket);
                ticket--;
            }else{
                break;
            }
        }
    }
}
public class TestDemo1 {
    public static void main(String[] args) {
        //创建实现类的对象
        Thread t1 = new Thread(new MyYThread());
        Thread t2 = new Thread(new MyYThread());
        Thread t3 = new Thread(new MyYThread());
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t1.start();
        t2.start();
       t3.start();
   }
}

在这里插入图片描述
对比上面讲的继承买票的形式
在这里插入图片描述

3.实现Callable接口

Callable声明形式

public interface Callable<V> {
    V call() throws Exception;
}

Callable接口提供了call方法,方法具有返回值,通过泛型类定义的,且可以以抛出异常

该接口的实现类无法直接使用,需要借助FutureTask类
FutureTask类声明如下:

public class FutureTask<V> implements RunnableFuture<V> {

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

FutureTask的构造函数

  public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

FutureTask类是实现自RunnableFuture接口,FutureTask是可以接口Callable的接口
而RunnableFuture接口是继承自Runable接口,意味着该FutureTask类的实例可以作为参数传递给Thread
FuturetTask构造函数

 public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

代码实现如下

public class Callable212 implements Callable<String>{
    @Override
    public String call() throws Exception {
        String name = Thread.currentThread().getName();
       int i=0;
       while (i++<3){
           System.out.println(name+"子线程正在执行");
       }
       return  name;
    }

}
class Test1{
    public static void main(String[] args) {
        Callable212 callable212 = new Callable212();
        FutureTask<String> stringFutureTask = new FutureTask<>(callable212);
        Thread thread = new Thread(stringFutureTask);
        thread.start();
        System.out.println(Thread.currentThread().getName()+"主线程正在执行");
    }
}

ExecutorService 和Executors

在这里插入图片描述

public class ThreadPool {

    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(10);
        ThreadPoolExecutor service1 =(ThreadPoolExecutor) service;
        //System.out.println(service.getClass());//得到接口地实现类
        service1.execute(new Runnable2());
    }
}

class Runnable2 implements Runnable{

    @Override
    public void run() {
        for(int i=0;i<3;i++){
            System.out.println(i);
        }
    }
}

线程的状态及状态转换

线程状态

jdk中提供了线程的状态,在Thread类中提供了一个内部的枚举类:State

public enum State {
        NEW,
        RUNNABLE,//runnable
        BLOCKED,//blovked
        WAITING,//waiting
        TIMED_WAITING, //timed waiting
        TERMINATED; //terminated
    }

新建状态(NEW)

用new语句创建的线程处于新建状态,此时和其他对象一样,在堆中仅仅给他分配了内存

就绪状态(RUNNABLE)

当一个线程创建后,调用了start方法,线程状态就进入到就绪状态,处于就绪状态的线程意味着所有需要的资源已经装备就绪,等待CPU的调用,CPU的调度是由操作系统控制的,用户无法操控

运行状态(RUNNING)

处于运行状态的线程占用CPU资源,执行程序代码,只有处于就绪状态的线程才能有机会进入到运行状态
1、如果一个时间片用完或者调用yield方法,线程会从运行状态进入到就绪状态
2、如果线程已经执行结果了,线程会从运行状态进入终止状态
3、如果线程执行过程中因为等待一些资源而进入阻塞状态(WAITING、TIME_WAITTING、BLOCKED)

阻塞状态(BLOCKED)

进入这种阻塞状态的原因可以是:线程期望进入同步方法或者同步代码块(Synchronized),尚未获取到锁资源的情况下,可以从运行状态进入到blocked状态

等待状态(WAITING)

如果调用wait()方法,就进入到waiting状态,即调用wait()会触发线程从运行状态进入到阻塞状态而无法执行,直到其他线程发出notify或notifyAll这个方法,此时线程才会从waiting状态进入到blocked状态,进而进入就绪状态

睡眠等待(TIMED_WAITING)

如果线程调用sleep(long)、join(long)、wait(long)等方法是,会触发线程进入到TIMED_WAITING状态,即指定了阻塞的时间,到达了给定的时间后就可以继续进入就绪状态等待CPU的调度

终止状态(TERMINATED)

当线程退出run()方法时,线程就进入了终止状态,该状态是结束线程的声明周期

线程状态转换(重要)

在这里插入图片描述

在这里插入图片描述

一个线程的生命周期中需要的状态:NEW、RUNNABLE、RUNNING、TERMINATED四个状态,当线程需要响应特定资源时,进入到阻塞状态:BLOCKED、WATING、TIMED_WAITING状态

线程方法

currentThread():静态方法

返回执行当前代码的线程
在这里插入图片描述

start():启动线程

start方法作用是用来启动一个新线程,start方法需要首先调用,start方法是不能被重复调用的

public synchronized void start() {
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        group.add(this);
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
      
            }
        }
    }

    private native void start0();

start方法的实现是调用了一个native的方法,本质上java创建的多线程其实是有操作系统所提供的多线程方式来创建的
start方法启动子线程是通过调用系统提供的方式来启动线程

run():子线程执行体

run方法中是子线程的执行体
run方法和普通的成员方法一样,可以被重复调用,如果在当前线程调用执行run(),是不会启动新线程

yield():线程让步

是暂停当前线程的执行,并让步于其他同优先级或更高优先级的线程先执行

public static native void yield();

yield方法是定义在Thread类下的静态方法
当线程中调用yield方法,会让当前正在执行的线程由”RUNNING“状态进入到”RUNABLE状态,线程锁占用的锁是不会释放的
yield让出CPU的执行权,当让给谁,是由系统决定的,系统会让具有相同优先级的或更高优先级的线程获取CPU执行权,如果没有相应优先级的线程,当前线程就会立即执行
在这里插入图片描述

sleep():线程睡眠

是让线程休眠,而且是哪个线程调用,就是那个线程休眠
sleep方法是Thread类下的静态方法,该方法执行过程中会抛出InterruptedException异常,即可以终止异常
sleep所在的线程休眠期间,线程会释放掉CPU资源给其他线程,但如果当前线程本身持有锁,锁是不会释放的,
线程会由”RUNNING“状态进入到TIMED_WAITING状态,当到达休眠时间或者是被中断掉休眠,就会从睡眠状态进入到就绪状态,从而等待CPU的调用

方法:
sleep(long millis)
sleep(long millis, int nanos)
这两个方法都是Thread类的静态方法,不属于某个对象

   //设置休眠的时间,单位是毫秒
   public static native void sleep(long millis) throws InterruptedException;

   //设置休眠时间 毫秒和纳秒
    public static void sleep(long millis, int nanos) throws InterruptedException {
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
            millis++;
        }

        sleep(millis);
    }

join():线程同步

参考文献https://www.cnblogs.com/lcplcpjava/p/6896904.html

在这里插入图片描述

上面程序结果是先打印完小明线程,在打印小东线程;  
上面注释也大概说明了join方法的作用:在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行。注意,这里调用的join方法是没有传参的,join方法其实也可以传递一个参数给它的,具体看下面的简单例子:

join方法特点:
1.普通方法,可以抛出Interrupted异常
2.t.join()是表示当前线程让t插队,等到t执行完,当前线程继续执行,
3.join方法调用会让当前线程进入阻塞的状态或者time_waiting状态

 public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        

if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
    //Object类下的native方法,是一个线程间通信的方法,notify或者notifyAll方法,
public final native void wait(long timeout) throws InterruptedException;
join方法执行的本质是使用线程间通信机制来完成线程同步功能

线程的顺序打印:给定4个线程,线程名分别为A,B,C,D,让每个线程打印各自名称,按照DCBA来打印
c 调 d.join
b 调 c.join
a b

public class JoinDemo extends Thread {
    private Thread thread;
    private String name;

    public JoinDemo(Thread thread, String name) {
        this.thread = thread;
        this.name = name;
    }
    @Override
    public void run() {
        if (thread != null) {
            try {
                //让子线程先执行
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
  //打印当前线程信息
     System.out.println(name);
    }
        JoinDemo threadd = new JoinDemo(null, "D");
        JoinDemo threadc = new JoinDemo(threadd, "C");
        JoinDemo threadb = new JoinDemo(threadc, "B");
        JoinDemo threada = new JoinDemo(threadb,"A");
        threadd.start();
        threadc.start();
        threadb.start();
        threada.start();  

2.interrupt()中断线程

用来中断当前的线程,终止处于“阻塞状态”的线程
interrupt方法是在Thread类中的一个普通方法,由对象调用该方法

方法:
boolean isInterrupted():判断线程是否发生中断,true:表示中断 false:非中断
interrupt():中断方法

方法特点: interrupt方法执行是对中断标志位做了修改
1、如果线程当前是可中断的阻塞状态(调用sleep、join、wait等方法会导致线程进入到阻塞状态WAITING/TIMED_WAITING/BLOCKED),
在任意的其他线程调用t.interrupt方法,那么线程会立即抛出一个InterruptedException异常,退出阻塞状态
2、如果线程t当前存储于运行状态,则调用t.interrupt()方法,线程会继续执行,直到发生了阻塞(调用sleep、join、wait)后立即抛出异常,跳出阻塞状态,Interrupt并不会终止处于“运行状态”的线程,其仅仅是对标志位做了修改

public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }
    
 private native void interrupt0();

demon守护线程

java中有两种线程,用户线程和守护线程,
可以通过isDaemon()来区分线程是否是守护线程 守护线程也称为”后台线程“
用户线程一般是用来执行用户级任务
守护线程也称之为“后台线程”,服务于用户线程,一般用来执行后台任务 例如“:垃圾回收线程是单独用来处理无用对象的回收的, 负责垃圾回收的线程就是守护线程
守护线程生命周期:
完全依赖与用户线程,用户线程存在即存在,消亡即消亡

   void setDaemon(boolean on);//true守护线程,  false 非守护线程,默认false
   boolean isDaemon() //判断当前线程是否是守护线程

Priority 线程优先级

获取线程优先级

public final int getPriority() {
        return priority;
    }

设置线程优先级 newPriority 必须在1~10 ,默认是5

public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
public final void setPriority(int newPriority)

高优先级的线程要抢占优先级线程cpu的执行权。但是只是从檄率上讲。高优先级的线程高概率的情况下被执行。并不童味着只有当高优先级的线程执行完以后,低优先级的线程才执行.

public class demo {
    public static  void main(String[] args) {
        MyThread h1 = new MyThread();
        Thread thread = new Thread(new thr());
        //设置分线程的优先级
        thread.setPriority(Thread.MAX_PRIORITY);
        h1.start();
        //给子线程命名
        h1.setName("子线程");
        //给主线程命名
        Thread.currentThread().setName("主线程");
        Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
        for(int i=0;i<100;i++){
            if(i%2==0) {
                System.out.println(Thread.currentThread().getName()+":"+Thread.currentThread().getPriority()+" "+i);
            }
        }
    }
}
class MyThread extends Thread{

    @Override
    public void run() {
        for(int i=0;i<100;i++){

            if(i%2==0) {
                System.out.println(Thread.currentThread().getName()+":"+Thread.currentThread().getPriority()+" "+i);
            }
        }
    }
}
class thr implements Runnable{
    @Override
    public void run() {
        for(int i=0;i<100;i++){
            if(i%2 !=0) {
                System.out.println(Thread.currentThread().getName()+":"+Thread.currentThread().getPriority()+" "+i);
            }
        }
    }
}

在这里插入图片描述

优先级特点

线程优先级:指导线程的执行的先后顺序的

方法:
//int getPriority() 获取线程的优先级
//setPriority(int newPriority) 设置线程优先级 newPriority必须在1~10之间的整的,默认的是5

优先级特点:
1、java线程的优先级并不绝对,他所控制的是执行的优先机会,优先级的决定权在操作系统,java设置的优先级只能是被优先执行的概率会高一些,并不绝对
在这里插入图片描述

2、java中优先级共有10级,分为1-10,数值越大,表明优先级越高,一般普通的线程优先级是5
3、优先级具有继承性,如果一个线程B在另一个线程A中创建出来,那么线程B的优先级和线程A保持一致

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值