Java 基础学习 day_11 12线程

线程(主线程可以创建子线程)

进程的一条执行路径
进程:资源分配的基本单位。
线程:cpu 调度的基本单位 。
一个程序至少有一个进程
一个进程可以包含多个线程,但是至少需要一个线程。
进程间不能共享资源,一个进程的多个线程可以共享资源。
堆空间:存储线程需使用的对象,多个线程可以共享堆中的对象。
栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈。

1.继承 Thread
重写run()方法,声明对象,调用start()方法。
改线程的名字:
1.可以在创建对象之后,对象.setName();

MyThread thread=new MyThread();
thread.setName("轩轩");
thread.start();
MyThread thread1=new MyThread();
thread1.setName("轩儿");

2.可以创造构造方法。

public MyThread(String name) {
    super(name);
}
public MyThread() {
}
MyThread thread=new MyThread("轩轩");
thread.start();
MyThread thread1=new MyThread("轩儿");
thread1.start();

一个线程对应一个自己的栈空间
内存结构:
在这里插入图片描述

2.实现Runnable接口(优先选择)
重写run()方法,声明对象,Thread thread=new Thread(对象);在调用start()方法。
实现Runnable接口,就相当于任务,把任务交给线程去处理,所以既能一起处理(共享资源),也可以单独处理(独享资源)。而继承Thread,只能单独处理(独享资源)。所以优先选择。
内存分配:

在这里插入图片描述

方法
1.(静态)Thread.sleep(毫秒),让当前线程休眠xx毫秒。需要处理异常。(有限等待)(需要抛出异常),不参与CPU的争抢,时间一到,回到就绪状态。
2.(静态)Thread.yield();让当前线程放弃时间片,回到就绪状态,继续抢夺时间片。(让给优先级高或则是优先级相同的)
3.对象.join(); 让线程加入到当前执行的线程,并优先执行。(当前线程发生阻塞,直到线程执行完成)(无限等待)需要处理异常
都会导致当前线程的暂停。都进入限期等待。
4.(静态)Thread.currentThread().获得当前线程。
5.getId():获得线程的Id
6.getName():获得线程的名字
7.对象.setPriority(int);设置优先级,数越高,优先级越高,获得cpu的机会越高。在任何地方都可以设置优先级。优先级1-10,默认为5。
8.对象.interrupt():打断线程,被打断线程抛出InterruptedException异常。(只要能抛出这个异常,就能被打断)
9.对象.setDaemon(true);设为保护线程。
10.对象.getState();返回线程的状态。

在这里插入图片描述

线程安全

同步方式(1)
1.同步代码块:
临界资源可以是引用类型。就相当于声明了一个锁对象。而且线程必须访问的是同一把锁。

为什么要用引用类型?
因为所有的引用类型都继承了Object类。

多个线程必须使用同一把锁。
synchronized(临界资源对象){
操作…
}
每个对象都有一个互斥锁标记,用来分配给线程。
只有拥有对象互斥锁标记的进程,才能进入对该对象加锁的同步代码块。
线程退出同步代码块时,会释放相应的互斥锁标记。
加锁就相当于封装了一个资源或者是操作。使用完才释放。
得不到锁标记,进入阻塞状态。
jdk1.5之后,就绪状态和运行状态合二为一。
在这里插入图片描述

2.同步方法:(非静态,锁是this;静态方法,锁是类名.class)(最好是锁住一个操作,最好没有循环)
非静态方法:
synchronized 返回值类型 方法名称(形参列表){
//对当前对象(this)加锁
}
静态方法:
static synchronized 返回值类型 方法名称(形参列表){
//对类名.class加锁
}

线程通信(有序的同步)

加锁就会产生死锁。
死锁:一个线程可以拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,所以可能造成死锁。

所以通过线程通信解决死锁。(Objcet 提供wait(),notify(),notifyAll())
等待(需要抛异常)(和锁的对象要一致)
wait()
wait(long timeout)
必须在对obj加锁的同步代码块中,在一个线程中,调用obj.wait(),此线程会释放其所有的锁标记,同时此线程处在无线期等待状态中。
通知:(和锁的对象要一致)
notify():
notifyAll():通知所有
必须在对obj加锁的同步代码块中,从obj的Waiting中释放一个或者全部线程。对自身没有任何影响。
但是notify()容易唤醒同类型的线程。所以可以用notifyAll()唤醒所有因阻塞的线程,但是这样又有一个问题,如果采用的if判断,它就会往下继续执行,可能会导致空的时候又在执行,所以可以不用if(),用把if变成while()让他继续在做判断。
生产出东西的同时要唤醒其他阻塞的线程。
消费完东西的同时要唤醒其他阻塞的线程。

sleep 和 wait 的区别

sleep:是线程进入休眠状态,wait线程进入等待队列。
sleep 放弃cpu,但是没有释放锁标记。
wait:释放锁标记,并且放弃cpu。

线程池

线程容器,可设定线程分配的数量上限。
将预先创建的线程对象存入池中,并重用线程中的线程对象。
避免频繁的创建和销毁。
单个线程约占1M
垃圾收集会停止所有正在工作的进程。

在这里插入图片描述

常用的接口。

Executor:只有execute()方法,一般不会用。
ExecutorService:继承了Executor,更为强大,提交任务时submit(任务),任务可以用runnable或者callable接口去实现。
在这里插入图片描述
newSingleThreadExecutor():创建单线程的线程池。

ExecutorService es=Executors.newFixedThreadPool(4);
ExecutorService es = Executors.newCachedThreadPool();
ExecutorService es=Executors.newSingleThreadExecutor();

shutdown():关闭等待所有的任务执行完毕后,才关闭。
shutdownNow():尝试关闭当前正在执行的线程,关闭所有没有执行的任务。
submit():提交任务到线程池。
isShutdown():执行器是否关闭。
isTerminated():所有任务在关闭后完成,返回true。

newScheduleThreadExecutor():创建时间调度的线程池。

Runnable runnable=new Runnable() {
    public int count=0;
    @Override
    public void run() {
        Date date=new Date();
        SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy--MM--dd hh:mm:ss");
        String format = simpleDateFormat.format(date);
        count++;
        if(count==5){
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName()+"   "+format);
    }
};
Date date=new Date();
ScheduledExecutorService es= Executors.newScheduledThreadPool(1);
SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy--MM--dd hh:mm:ss");
String format = simpleDateFormat.format(date);
System.out.println(format);
es.schedule(runnable, 10, TimeUnit.SECONDS);//延迟十秒执行
es.shutdown();//关闭
//答案
2020--08--05 04:41:45
pool-1-thread-1   2020--08--05 04:41:55
es.scheduleAtFixedRate(runnable,1,1,TimeUnit.SECONDS);
//答案
2020--08--05 04:45:21
pool-1-thread-1   2020--08--05 04:45:22
pool-1-thread-1   2020--08--05 04:45:23
pool-1-thread-1   2020--08--05 04:45:24
pool-1-thread-1   2020--08--05 04:45:25
pool-1-thread-1   2020--08--05 04:45:26
pool-1-thread-1   2020--08--05 04:45:31
pool-1-thread-1   2020--08--05 04:45:31
pool-1-thread-1   2020--08--05 04:45:31
pool-1-thread-1   2020--08--05 04:45:31
pool-1-thread-1   2020--08--05 04:45:31
es.scheduleWithFixedDelay(runnable, 1, 1, TimeUnit.SECONDS);
//答案
2020--08--05 04:46:57
pool-1-thread-1   2020--08--05 04:46:58
pool-1-thread-1   2020--08--05 04:46:59
pool-1-thread-1   2020--08--05 04:47:00
pool-1-thread-1   2020--08--05 04:47:01
pool-1-thread-1   2020--08--05 04:47:02
pool-1-thread-1   2020--08--05 04:47:08

ThreadPoolExecutor

构造方法:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

7个参数:
(1)核心线程数
(2)最大线程数(队列实在是装不了,创建线程而且是直接执行,不用进队列)(能不创建线程就不创建线程)
(3)线程存活时间(线程指的是最大线程数减核心线程数)
(4)时间单位
(5)请求队列(线程等待进入此队列)(等待的线程优先放队列中)
(6)线程创建工厂,(默认Executor.defaultThreadFactory())
(7)拒绝策略
1.AbortPolicy:抛弃任务,并抛出异常,默认的拒绝策略。
如果是比较关键的业务,推荐使用此拒绝策略,这样子在系统不能承载更大的开发量的时候,能够及时的通过异常发现。
2.DiscardPolicy:放弃任务,没有异常
使用此策略,可能会使我们无法发现系统的异常状态,建议是一些无关紧要的业务使用此策略。
3.DiscardOldestPolicy:新任务加入队列,抛弃队列中的头任务。
此策略是一种喜新厌旧的拒绝策略,是否要采用此种拒绝策略,还得根据实际的业务是否允许丢弃老任务来认真衡量。
4.CallerRunPolicy:线程池的多余线程由线程池的创建线程执行。(谁创建的线程池谁执行多余的线程)
如果任务被拒绝了,则由调用线程(提交任务的线程)直接执行此任务。

ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(4, 6, 4, TimeUnit.MINUTES, new LinkedBlockingQueue<>(2), Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardOldestPolicy());
for (int i = 0; i <11 ; i++) {
    int count=i;
    Runnable runnable=new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"开始执行 "+count);
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"结束执行  "+count);
        }
    };
    threadPoolExecutor.submit(runnable);
}
threadPoolExecutor.shutdown();
//答案

pool-1-thread-1开始执行 0
pool-1-thread-2开始执行 1
pool-1-thread-3开始执行 2
pool-1-thread-4开始执行 3
pool-1-thread-5开始执行 6
pool-1-thread-6开始执行 7
pool-1-thread-1结束执行  0
pool-1-thread-3结束执行  2
pool-1-thread-1开始执行 9
pool-1-thread-6结束执行  7
pool-1-thread-4结束执行  3
pool-1-thread-5结束执行  6
pool-1-thread-2结束执行  1
pool-1-thread-3开始执行 10
pool-1-thread-1结束执行  9
pool-1-thread-3结束执行  10

Callable接口

Callable:比runnable更为强大(它有泛型)
1.有返回值
2.有返回值的类型
3.能抛出异常。

在这里插入图片描述

使用callable,不能用Thread,而线程池都能使用。runnable能实现的,callable都能实现,callable不想返回值,就返回null。
实现callable接口,就要重写call()方法,并抛出异常。
但是callable可以间接使用Thread
Futuretask实现了Runnable的接口,用它去接收callable,然后就可以用Thread,启动线程之后,就可以用调用get()方法得到callable的返回值。

Callable<Integer> call=new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
        int sum=0;
        for(int i=0;i<=100;i++){
            sum+=i;
        }
        return sum;
    }
};
FutureTask<Integer> future=new FutureTask<Integer>(call);
Thread thread=new Thread(future);
thread.start();
try {
    System.out.println(future.get());
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (ExecutionException e) {
    e.printStackTrace();
}
//答案
5050

submit()有个返回值,为Future的对象,他的get()方法能得到call()方法的返回值。
future的泛型是由call的返回值决定的,call的返回值是由Callable<>泛型决定的。

ExecutorService es = Executors.newSingleThreadExecutor();
Callable<Integer> call=new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName()+"正在计算...");
        int sum=0;
        for(int i=0;i<=100;i++){
            sum+=i;
            Thread.sleep(100);
        }
        return sum;
    }
};
System.out.println(Thread.currentThread().getName()+"开始");
Future<Integer>future= es.submit(call);
System.out.println(future.get());
es.shutdown();

什么是同步

在这里插入图片描述

什么是异步

在这里插入图片描述

lock接口

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

重入锁(ReentantLock)

在这里插入图片描述

tryLock():尝试获取锁,成功返回true,失败返回false,不阻塞。

读写锁(ReadWriteLock)接口

ReentrantReadWriteLock 实现类
在这里插入图片描述
ReadWriteLock w1=new ReentrantReadWriteLock();获取读写锁
(lock的子类)ReadLock s1=w1.readLock();获得读锁。
(lock的子类)WriteLock s2=w1.writeLock();获取写锁。

private int num;
ReentrantReadWriteLock lock=new ReentrantReadWriteLock();//读入锁
ReentrantReadWriteLock.ReadLock readLock=lock.readLock();//读锁
ReentrantReadWriteLock.WriteLock write=lock.writeLock();//写锁
public void setNum(int num){
    write.lock();//得到写锁
    try{
        Thread.sleep(1000);
        this.num=num;
        System.out.println(Thread.currentThread().getName()+"修改成"+num);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        write.unlock();//释放写锁
    }
}
public void getNum(){
    readLock.lock();//得到读锁
    try {
        Thread.sleep(2000);
        System.out.println(Thread.currentThread().getName()+"读了"+this.num);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        readLock.unlock();//释放读锁
    }
}

猜你喜欢

转载自blog.csdn.net/m0_45196258/article/details/107736231