【多线程】并发多线程基础


Java多线程超详解

1.创建线程有哪几种方式?

继承Thread类、实现Runnable接口、实现Callable接口

class MyThread2 extends Thread{
    @Override
    public void run() {//run()方法被称为线程执行体
        for(int i = 0; i < 100; i++){
            if(i % 2 == 1){
                System.out.println(Thread.currentThread().getName() + i);
            }
        }
    }
}//1.定义Thread类的子类,并重写该类的run()方法,该run()方法将作为线程执行体。

//2.创建Thread子类的实例,即创建了线程对象。
MyThread1 t1 = new MyThread1("线程一");
//3.调用线程对象的start()方法来启动该线程。
t1.start();//①启动线程 ②调用当前线程的run(),注意不能直接执行run方法,直接执行还没启动线程
//只能对处于新建状态的线程调用start()方法,否则将引发IllegalThreadStateException异常。

优先采用实现Runnable、Callable接口的方式创建多线程

  • 继承了Thread类,就不能继承其他类了,不满足实际的需求。

  • 线程类只是实现了Runnable接口或Callable接口,还可以继承其他类,并且多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况

2.Thread类的常用方法

1.start(开启线程,start是通过线程来调用run方法)
2.run   此run非彼run (不是在run方法实现线程的逻辑,而是thread.run(),这个run方法是直接调用了线程中的run)
3.yield(暂停当前线程,并执行其他线程)
4.sleep(使当前线程由运行状态变成阻塞状态,若睡眠时其他线程调用了interrupt方法,会导致sleep抛出异常InterruptException)
5.join(保证当前线程在其他线程开始时会结束)(如下,A线程想运行的话,必须等B线程结束才能运行(将处于阻塞状态))
 Thread A{undefined
run{undefined
new ThreadB.join();
}
}.start;
6.interrupt(中断线程)
7.wait/notify(从Object类继承下来的方法)
8.setPriority(设置线程优先级(只能在线程开始前设置))
9.stop(强制结束线程)

3.介绍一下线程的生命周期

在线程的生命周期中,它要经过新建(New)、就绪(Ready)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态。尤其是当线程启动以后,它不可能一直“霸占”着CPU独自运行,所以CPU需要在多条线程之间切换,于是线程状态也会多次在运行、就绪之间切换。

new关键字创建线程,该线程就处于新建状态,此时它和其他的Java对象一样,仅仅由Java虚拟机为其分配内存。此时的线程对象没有表现出任何线程的动态特征,程序也不会执行线程的线程执行体。

当线程对象调用了start()方法之后,该线程处于就绪状态,Java虚拟机会为其创建方法调用栈和程序计数器,处于这个状态中的线程并没有开始运行,只是表示该线程可以运行了。至于该线程何时开始运行,取决于JVM里线程调度器的调度。

如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态,如果计算机只有一个CPU,那么在任何时刻只有一个线程处于运行状态。当然,在一个多处理器的机器上,将会有多个线程并行执行;当线程数大于处理器数时,依然会存在多个线程在同一个CPU上轮换的现象。

当一个线程开始运行后,它不可能一直处于运行状态,线程在运行过程中需要被中断,目的是使其他线程获得执行的机会,线程调度的细节取决于底层平台所采用的策略。对于采用抢占式策略的系统而言,系统会给每个可执行的线程一个小时间段来处理任务。当该时间段用完后,系统就会剥夺该线程所占用的资源,让其他线程获得执行的机会。

  • 当发生如下情况时,线程将会进入阻塞状态:
    线程调用sleep()方法主动放弃所占用的处理器资源。
    线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞。
    线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有。
    线程在等待某个通知(notify)。
    程序调用了线程的suspend()方法将该线程挂起。但这个方法容易导致死锁,所以应该尽量避免使用该方法。
  • 针对上面几种情况,可以解除上面的阻塞,让该线程重新进入就绪状态:
    调用sleep()方法的线程经过了指定时间。
    线程调用的阻塞式IO方法已经返回。
    线程成功地获得了试图取得的同步监视器。
    线程正在等待某个通知时,其他线程发出了一个通知。
    处于挂起状态的线程被调用了resume()恢复方法。
  • 线程会以如下三种方式结束,结束后就处于死亡状态:
    run()或call()方法执行完成,线程正常结束。
    线程抛出一个未捕获的Exception或Error
    直接调用该线程的stop()方法来结束该线程,该方法容易导致死锁,通常不推荐使用。
    在这里插入图片描述

4.如何实现线程同步?

  • 同步方法
    即有synchronized关键字修饰的方法,由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法。**在调用该方法前,需要获得内置锁,否则就处于阻塞状态。**需要注意, synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。
  • 同步代码块
    即有synchronized关键字修饰的语句块,被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。需值得注意的是,同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。
  • ReentrantLock
    ReentrantLock 锁详解
  • volatile
    volatile关键字是由JVM提供的最轻量级同步机制。
    彻底理解volatile关键字
  • 原子变量
    在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类,使用该类可以简化线程同步。
    原子操作类AtomicInteger详解

5.Java多线程之间的通信方式

Java中的多线程(线程间通信)

sleep()和wait()的区别
notify()、notifyAll()的区别

例题:线程A负责实时往Map里put 1-100的数字,线程B负责实时从这个map中get数字
并进行累加(A放入MAP一个值后,B取出来,然后A继续放,B继续取,以此循环一直到A放完100,B取完100,结束),B实时打印当前时刻累加的结果。

public class WindowTest1 {
    public static void main(String[] args) {
        Window1 window1 = new Window1();
        Thread t1 = new Thread(window1);
        Thread t2 = new Thread(window1);
        t1.setName("窗口1");
        t2.setName("窗口2");
        t1.start();
        t2.start();

    }
}

class Window1 implements Runnable{
    HashMap<Integer, Integer> map = new HashMap<>();
    private static int ticket = 1;
    private static boolean flag = true;
    private int sum = 0;
    @Override
    public void run() {
	       if(flag){
	           flag = false;
	           show1();
	       }else{
	           flag = true;
	           show2();
	       }
    }
    public synchronized void show1(){
        while(ticket <= 100) {//使用 while而不是if
            map.put(ticket, ticket);
            System.out.println(Thread.currentThread().getName() + ": " + ticket);
            this.notify();
            try {
                this.wait();
            } catch (InterruptedException e) {
            }  //同步函数的锁是this
        }
    }
    public synchronized void show2(){
        while(ticket <= 100){
            sum += map.get(ticket);
            ticket++;
            System.out.println(Thread.currentThread().getName() + ": " + (ticket-1));
            System.out.println("累加结果: " + sum);
            this.notify();
            try {
                this.wait();
            } catch (InterruptedException e) {

            }  //同步函数的锁是this
        }
    }
}

6.进程和线程

进程和线程的区别(超详细)

根本区别:进程是操作系统进行资源分配的最小单元,线程是操作系统进行运算调度的最小单元。
从属关系不同:进程中包含了线程,线程属于进程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

db_1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值