线程(主线程可以创建子线程)
进程的一条执行路径
进程:资源分配的基本单位。
线程: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();//释放读锁
}
}