java学习笔记(十)多线程基础

一、程序,进程,线程

1、程序

使用计算机语言编写的一段可执行的,能完成某种功能的静态代码

2、进程

程序执行的动态过程

开始运行到关闭或停止运行

一个程序可以同时运行多次,每次运行都是一个进程

3、线程

一个程序中可以包含多个任务,一个任务一个线程,

一个进程可以有多个任务,同时运行这些任务就是多线程

二、多线程

多个任务流程同时执行互不影响时使用多线程

1、并发和并行

所有程序都是运行在CPU上

并发:在CPU单核上交替运行多个线程

并行:利用CPU的多核特性同时运行多个线程

2、多线程优点

减少了阻塞,程序的执行效率高

提高了CPU的利用率

增强用户体验

改善程序结构

3、线程分类

(1)用户线程

用户线程即程序功能流程,不用自己编写的

主线程:程序执行的入口

一个进程有且只有一个主线程

(2)守护线程

即后台线程

如:jvm的垃圾回收线程,jvm异常处理线程

三、多线程实现

1、继承Thread

(1)继承java.lang.Thread类

(2)重写run方法,run方法是线程的的执行代码

(3)创建线程对象

(4)使用start()开启线程

2、实现Runnable

(1)实现java.lang.Runnable接口

(2)实现run方法

(3)创建线程任务对象

(4)使用Tread创建线程对象

(5)开启线程

3、比较

(1)thread方式开启新线程比较方便,直接使用start方法,同时继承了其他的很多方法

(2)继承了Thread类,就不能继承其他类,

但是实现了Runnable接口,还可以继承其他类

(3)实现Runnable方式,将线程和任务代码进行分离。降低了程序的耦合度,提高了代码的重用性。

(4)使用Runnable方式可以共享数据(一个任务对象开启了多个线程,共享了一个对象的数据),而继承Thread方式,是一个对象一个对象的开启,不能做到一个对象同时开启多个线程

而是这个类能开启多个线程

4、线程生命周期

新建状态->可运行状态(就绪)->运行状态->阻塞状态->死亡状态

CPU执行原理

创建线程后在CPU队列中就绪等待CPU调用

CPU每个核每次会选择队列中的一个线程来执行

CPU会给该执行的线程一个时间片,实现时间片轮转

若在时间片中,运行时出现阻塞,会将运行的线程放入阻塞队列,等待唤醒

因为CPU随机选择(有调度算法的)线程来执行

CPU每次分配的时间片大小会随机,所以多线程每次执行的结果都可能不一样

四、线程方法

1、设置线程的名字

(1)通过构造方法,Thread(Runnable r,String name)

(2)通过对象调用方法

setName(String name)

(3)类方法

Thread.currentThread().setName(String name),设置当前线程任务对象的名字

2、获取线程的名字

(1)通过对象调用方法

getName()

(3)类方法

Thread.currentThread().getName(String name),获取当前线程任务象的名字

3、线程阻塞

sleep(n)

使当前线程阻塞n毫秒

join

在一个线程中调用了另一个线程的join方法

只有当另一个线程的全部代码运行完毕,当前线程才能继续执行

五、多线程内存分配

1、程序计数器

线程私有,当前线程所执行的字节码的行好该程序执行到指示器,记录程序执行的行数。

2、Java虚拟机栈

线程用私有,于存储局部变量表、操作栈、动态链接、方法出口等信息

3、本地方法栈

同虚拟机栈用相似,区别:Java虚拟栈是字节码文件

4、Java堆

线程共享,在JVM启动时创建,此内存区域的唯一目的就是存放对象实例

5、方法区

线程共享,用于存储已被虚拟机加载的类,常量,静态变量,

即被编译的字节码文件中

6、运行时常量池

方法区的一部分,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放

7、多线程内存分配

多个线程共享堆内存和方法区,但是计数器和栈是每个线程私有的

六、线程同步

1、为什么需要线程同步

多个线程访问同一对象,导致数据不一致

如:同时有三个窗口卖票,使用同一个数据对象,可能两个窗口卖出了同一张票

public class TicketWindow implements   Runnable{
    private  int  ticket=100;

    @Override
    public void run() {
        //卖票
        while(true){
            
            if(ticket>0){
                System.out.println(Thread.currentThread().getName()+",卖出的票号为:"+ticket);
                ticket--;
            }else{
                break;
            }
        }
    }
}
public class TestTicketWindowMain {
    //主线程
    public static void main(String[] args) {

//      线程任务:卖票
        TicketWindow   window=new TicketWindow();

//        3个窗口同时卖票:3个线程。
        Thread   t1=new Thread(window,"1号窗口");
        Thread   t2=new Thread(window,"2号窗口");
        Thread   t3=new Thread(window,"3号窗口");

        t1.start();
        t2.start();
        t3.start();

    }
}

为了解决上述问题,开启线程同步,只有当一张票卖完,才能卖下一张票

2、线程同步方式

(1)同步块

将一块代码锁起来,每次只能一个线程执行。

一个线程执行完毕同步块,下一个线程才能执行

原理:

对象锁:一个对象对应一把锁。

通过该对象锁,锁住某块代码。

哪个线程获取到这个对象锁,哪个线程就可以执行同步块中的代码。其他线程阻塞。

该线程执行完同步块代码,主动释放锁。其他线程竞争这个对象锁。

//建立一个对象作为锁
private Object   o=new Object();

synchronized(o--对象名){
	//同步的代码块
}
public class TicketWindow implements   Runnable{
    private  int     ticket=100;
    private Object   o=new Object();

    @Override
    public void run() {
        //卖票
        while(true){
            //卖一张票的整个操作
            //通过对象o锁,锁住代码块。
            synchronized (o) {
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + ",卖出的票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}

(2)同步方法

使用同一个任务对象中的同步方法;

原理:

通过当前对象this的锁,锁住整个方法。

[修饰符]  synchronized   返回值类型   方法名([参数]){
	方法体:同步代码
}
public class TicketWindow2 implements Runnable{

    private  int     ticket=100;

    @Override
    public void run() {
        //卖票
        while (true){
            //卖出一张票
            saleTicket();

            if(ticket<=0){
                break;
            }
        }

    }

    //同步方法
//    通过当前对象this的锁,锁住整个方法
    public  synchronized   void  saleTicket(){
        if(ticket>0) {
            System.out.println(Thread.currentThread().getName() + ",卖出的票号为:" + ticket);
            ticket--;
        }
    }
}

(3)lock方式

Lock接口:java.util.concurrent.locks.Lock

void lock()-- 获得锁

void unlock() – 释放锁

实现类:ReentrantLock

多个线程竞争同一把锁,实现线程同步。

和同步块效果一样。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TicketWindow3 implements   Runnable{
    private  int     ticket=100;

//    创建一把锁
    private Lock lk=new ReentrantLock();

    @Override
    public void run() {
        //卖票
        while(true){
            //卖一张票的整个操作
//           获取锁
            lk.lock();
            try {
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + ",卖出的票号为:" + ticket);
                    ticket--;
                }
            }catch(Exception  e){
            }finally {
                //释放锁
                lk.unlock();
            }


                if(ticket<=0){
                    break;
                }

        }
    }
}

(4)lock与同步块比较

lock更直观,直接在代码中可以看见锁。

lock可以解决同步块中出现异常释放锁的问题。

七、线程通信

1、为什么需要线程通信

死锁

使用多线程,会出现死锁,同一类的的多线程不会发送死锁,但是不同类使用的同一把锁会发生死锁,即两个线程各有一把锁,同时继续执行又需要对方的锁,从而出现相互阻塞的现象。

public class MyThread11  implements   Runnable {
    private   Object   a;
    private   Object   b;

    public MyThread11(Object a, Object b) {
        this.a = a;
        this.b = b;
    }

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        synchronized (a){
            a.wait();
            System.out.println(name+"获得了a锁");
            synchronized (b){
                System.out.println(name+"获得了b锁");
              
                System.out.println(name+"释放了b锁");
            }
            System.out.println(name+"释放了a锁");
        }
    }
}
public class MyThread12 implements   Runnable{
    private   Object   a;
    private   Object   b;

    public MyThread12(Object a, Object b) {
        this.a = a;
        this.b = b;
    }

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        synchronized (b){
            System.out.println(name+"获得了b锁");
            synchronized (a){
                System.out.println(name+"获得了a锁");
                System.out.println(name+"释放了a锁");
            }
            a.notifyAll();
            System.out.println(name+"释放了b锁");
        }
    }
}
public class TestMain07 {
    public static void main(String[] args) {

        Object  a=new Object();
        Object  b=new Object();
        MyThread11  t11=new MyThread11(a,b);
        MyThread12  t12=new MyThread12(a,b);

//        创建线程
        Thread  t1=new Thread(t11,"线程1:");
        Thread  t2=new Thread(t12,"线程2:");

//        开启线程
        t1.start();
        t2.start();
    }
}

这时使用通过线程通信的方式,让其中一方先放弃手中的锁,让对方先用完,然后再使用。

2、线程通信方式

锁对象名.wait()

释放对象锁,该线程阻塞,等待被唤醒

锁对象名.notify();唤醒一个等待该对象锁的阻塞线程

锁对象名.notifyAll();唤醒所有等待该对象锁的阻

3、wait和sleep区别

都是线程阻塞的方法。

区别:

wait方法是Object方法;sleep方法是Thread方法。

wait方法会释放锁;sleep方法不会释放锁。

wait阻塞,需要其他线程调用notify、notifyAll方法来唤醒;sleep阻塞,到时间自动结束阻塞。

八、线程池

1、使用Callable实现多线程

(1)继承Thread,实现Runnable,实现Callable的区别

Thread 也实现了Runnable

不管是继承Thread,还是实现Runnable,都要重写run方法。

run方法的缺陷:没有返回值。

​ 不能抛出异常。

Callable接口:call方法有返回值,可以声明抛出异常。

(2)实现Callable接口创建线程

第一,实现Callable

第二,实现call方法(任务代码,和run一样):这个方法有返回值,还可以声明抛出异常。

第三,创建线程对象。

        FutureTask<String>   task=new FutureTask<>(call);
        Thread  t=new Thread(task);

第四,开启线程。

(3)Future类

用来跟踪和存储Callable执行过程和结果。

get() 等待计算完成,然后检索其结果

isDone()若任务已完成,返回true;

(4)FutureTask

实现了Runnable和Future。

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

public class TestCallable {

    public static void main(String[] args) throws InterruptedException, ExecutionException {

//        创建线程任务
        MyThread21   call=new MyThread21();

//        创建线程对象
//        FutureTask=Runnable+ Future
        FutureTask<String>   task=new FutureTask<>(call);
        Thread  t=new Thread(task);

//        开启线程
        t.start();

//        跟踪线程是否执行完毕
        Thread.sleep(1000);
        System.out.println(task.isDone());
        System.out.println(task.get());
		
    }
}

2、线程池是?优点是?

创建线程的第四种方式;

线程池是线程的集合,包含多个线程

启动程序时创建好一些线程保存在线程池中,当需要执行任务代码时,从线程池中取出一条线程,然后在这个线程中执行该任务代码,执行完毕后将线程放回线程池中;

线程池优点:

当任务到达时,可以不需要等待创建线程就能立即执行,

提高了效率,也提高了响应速度。

通过重复利用已创建的线程降低线程创建和销毁造成的消耗,降低了资源消耗

线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,监控和调优,提高了线程的可管理性

3、四种线程池

使用java.util.concurrent.Executors 创建线程池。

(1)单一线程池

使用场景:多个任务顺序执行。

//      单一线程池:有且仅有一个线程。
        ExecutorService single = Executors.newSingleThreadExecutor();
        Task   t=new Task();//Runnable
        Task2   t2=new Task2();//Callable

//        线程池执行任务:从线程池取出一个线程,执行该任务。
        single.execute(t);
//       线程池提交任务:从线程池取出一个线程,执行该任务。
        single.submit(t2);

//       关闭线程池
		single.shutdown();

(2)固定线程池

使用场景:同时要执行的任务数量比较多的情况,控制线程的数量。

//        固定线程池:有且仅有n个线程
        ExecutorService fixedPool = Executors.newFixedThreadPool(2);

        Task   t=new Task();//Runnable
        Task2   t2=new Task2();//Callable
        Task   t3=new Task();//Runnable
        Task2   t4=new Task2();//Callable
        fixedPool.submit(t);
        fixedPool.submit(t2);
        fixedPool.submit(t3);
        fixedPool.submit(t4);

//        关闭线程池
        fixedPool.shutdown();

(3)可变线程池

初始时,线程数量为0.

当有任务时,创建线程,执行任务。

执行完毕后,放回线程池。

空闲线程空闲时间大于指定时间,销毁该线程。

使用场景:同时执行的任务比较少的时候。

//        可变线程池
        ExecutorService cachedPool = Executors.newCachedThreadPool();

        Task   t=new Task();//Runnable
        Task2   t2=new Task2();//Callable
        Task   t3=new Task();//Runnable
        Task2   t4=new Task2();//Callable
        cachedPool.submit(t);
        cachedPool.submit(t2);
        cachedPool.submit(t3);
        cachedPool.submit(t4);

//        关闭线程池
        cachedPool.shutdown();

(4)定时线程池(任务调度线程池)

使用场景:延迟执行,或者定期执行。

//        定时线程池
        ScheduledThreadPoolExecutor scheduledPool=(ScheduledThreadPoolExecutor)Executors.newScheduledThreadPool(10);

//        执行任务
        Task   t=new Task();
//        立马执行
//        scheduledPool.submit(t);
//        定时执行
        scheduledPool.schedule(t,10, TimeUnit.SECONDS);

//        关闭线程池
//        不能关闭,应为关闭了线程也被销毁了。
        //scheduledPool.shutdown();

(5)ExecutorService的submit和execute方法区别

submit执行完毕后有返回值。

execute执行完毕后没有返回值。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值