一.多线程的重点部分
1.两种创建方式
1.继承Threa类
2.实现Runnable接口
最后都通过重写run方法实现线程
2.wait和sleep区别
1.wait释放锁,sleep不释放
2.wait用于线程间交互,sleep用于暂停
3.wait等待CPU,sleep攒着CPU睡觉
3.synchronized和volatile关键字作用
volatile :易变,不稳定之意,一个成员变量被修饰后:
1.保证了不同线程对这个变量操作时的可见性:一个线程修改了某变量值,该值对其他线程立即可见.
2.禁止进行指令重排序
volatile告诉jvm,当前变量在寄存器(工作内存)中值是不确定的,需要从主存中读取
synchronized锁定当前变量,只有当前线程可访问,其他的阻塞.
1.volatile只能在变量级别
synchronized可在变量,方法,类级别
2.volatile紧能实现变量的修改可见性,不能保证原子性
synchronized能实现变量的修改可见性并能保证原子性
3.volatile不会造成线程的阻塞
synchronized可能会造成线程的阻塞。
4.volatile 标记的变量不会被编译器优化;
synchronized 标记的变量可以被编译器优化;
4.线程并发访问代码
public class Demo3 {
public static void main(String[] args) {
final Counter counter = new Counter();
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
counter.inc();
}
}).start();
}
System.out.println(counter);
}
}
class Counter {
private volatitle int count = 0;
public void inc() {
try {
Thread.sleep(3);
} catch (Exception e) {
e.printStackTrace();
}
//增加
count++;
}
@Override
public String toString() {
return "[count = " + count + "]";
}
}
问,是否打印1000?
不是,结果不可能等于1000,肯定是小于1000的,线程安全问题,所以小于,不管有无都小于.
线程池的重点部分
5.线程池是什么?怎么使用?
1.”事先”将”多个线程”放入一个容器中;
2.使用的时候就不用new线程而是从”池”中拿;
3.节省了开辟线程的时间,提高代码的执行效率;
使用方法
Executors的工厂方法提供的5种不同的线程池,都是.newXXX,自动回收的,固定大小的,调度的,单例的.
(java.util.concurrent.Executors-同时进行.执行):
1.ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
2.ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(4);
3.ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(4);
4.ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
然后再调用他们的execute()方法即可;
创建举例:
固定线程数量和单个:
public class Demo3 {
public static void main(String[] args) {
//预先创建6个线程
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
MyThread t4 = new MyThread();
MyThread t5 = new MyThread();
MyThread t6 = new MyThread();
//创建一个线程池,即容器
ExecutorService executorService = Executors.newFixedThreadPool(4);//设定同时运行的个数
//单例线程,任意时间池中只能有一个线程
//一个池最多只能同时执行一个
ExecutorService executor = Executors.newSingleThreadExecutor();
//把线程"们"放入池中,并执行
executorService.execute(t1);
executorService.execute(t2);
executorService.execute(t3);
executorService.execute(t4);
executorService.execute(t5);
executorService.execute(t6);
//关闭池
executorService.shutdown();
}
}
class MyThread extends Thread {
@Override
public void run() {
for (int i = 1; i < 101; i++) {
System.out.println(Thread.currentThread().getName() + " 正在执行...." + i);
}
}
}
输出结果(4个线程):
pool-1-thread-2 正在执行....4
pool-1-thread-4 正在执行....1
pool-1-thread-1 正在执行....11
pool-1-thread-4 正在执行....2
pool-1-thread-2 正在执行....5
pool-1-thread-3 正在执行....2
一次只能执行一次线程(1个线程):
pool-1-thread-1 正在执行....5
pool-1-thread-1 正在执行....6
pool-1-thread-1 正在执行....7
pool-1-thread-1 正在执行....8
pool-1-thread-1 正在执行....9
pool-1-thread-1 正在执行....10
>
6.对线程池的理解
三个好处:
1.降低能源消耗,通过反复利用已经创建线程,减少线程的创建和销毁造成的消耗
2.提高响应速度,任务一到达不需要等待线程创建就能立即执行
2.提高了线程的可管理性,线程是奇缺资源,如果不停的创建,还会消耗系统资源,对系统的稳定性造成影响.
线程池
1."线程池"就是一个容器,存放"Thread"or"Runable",
2.若每次有任务都"new Thread()"开启一个线程,那么就会"大量的消耗CPU"的资源,导致"Android"运行变慢,甚至"OOM(out of memory)" ,因而"java"就出现了一个"ThreadPoolExecutor"来管理这些线程。
1.控制最多的线程数"maximumPoolSize"
2.核心线程数"corePoolSize",来管理我们需要开启的线程数。
目的:减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务
所以:我们就可以根据"手机的CPU"核数来控制App可以开的最大线程数。保证程序的合理运行
创建线程池对象
几个参数的完全理解
1.比如去火车站买票, 有10个售票窗口, 但目前只有5个窗口开放(日常情况). 那么对外开放的5个窗口称为核心线程数"corePoolSize",
2.而最大线程数"maximumPoolSize"是10个窗口.
3.如果5个窗口都被占用, 那么后来的人就必须在后面排队, 但后来售票厅人越来越多, 已经人满为患, 就类似于线程队列new "LinkedBlockingQueue<Runnable>()"已满.
4.这时候火车站站长下令, 把剩下的5个窗口也打开, 也就是目前已经有10个窗口同时运行(春运). 后来又来了一批人,
5.10个窗口也处理不过来了, 而且售票厅人已经满了, 这时候站长就下令封锁入口,不允许其他人再进来, 这就是线程异常处理策略.
6.而线程存活时间"keepAliveTime"指的是, 允许售票员休息的最长时间, 以此限制售票员偷懒的行时间。休息一下在处理。
线程池实际:来5个人,正好,来第六个,如果等了500毫秒还没人出来,那么开第6个窗口,然后再78910个人就开10个窗口,多过10个人,就按照自定义策略处理,当少<10个人,那么窗口会对比空闲时间,超过就关闭.
7.线程池启动策略
图解
官方建议用Executor的工厂方法创建,以前最常用的方法是ThreadPoolExecutor,线程池执行
案例1:
public class ThreadPool {
public static void main(String[] args) {
//线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
3 //corePoolSize:线程池维护线程的最少数量
, 5 //maximumPoolSize:最大限度线程数
, 3 //keepAliveTime:线程池维护线程的空闲时间
, TimeUnit.SECONDS //unit:对应单位是什么
, new ArrayBlockingQueue<Runnable>(5)//workQueue,缓存队列数
, new ThreadPoolExecutor.DiscardOldestPolicy()//handler:拒绝任务的处理策略
);
for (int i = 1; i <= 10; i++)
{
try
{
// 产生一个任务,并将其加入到线程池
String task = "task@ " + i;
System.out.println("put " + task);
threadPool.execute(new ThreadPoolTask(task));
// 便于观察,等待一段时间,慢点加
Thread.sleep(10);
}
catch (Exception e)
{
e.printStackTrace();
}
}
//关闭
threadPool.shutdown();
}
}
/**
* 参数:
* 1.任务通过executor(Runnable)方法添加到线程池
* 2.任务就是一个runable对象
* 3.认为的执行方法就是run();
*
*优先级:
* 1.核心线程corePoolSize
* 2.任务队队列workQueue
* 3.最大限度线程
* 如果都满了,hanlder策略处理
*
* 线程数量大于最小数量,如果空闲时间超过了keepAliveTime,那么线程将会被终止.即线程池动态的调整池中线程数量.
*/
class ThreadPoolTask implements Runnable
{
// 保存任务所需要的数据
private Object threadPoolTaskData;
ThreadPoolTask(Object tasks)
{
this.threadPoolTaskData = tasks;
}
public void run()
{
// 处理一个任务,仅是一个打印
System.out.println(Thread.currentThread().getName()+"...start ..." + threadPoolTaskData);
try
{
//便于观察,等待一段时间
Thread.sleep(1000);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
打印结果:
put task@ 1
pool-1-thread-1...start ...task@ 1
put task@ 2
pool-1-thread-2...start ...task@ 2
put task@ 3
pool-1-thread-3...start ...task@ 3
put task@ 4
put task@ 5
put task@ 6
put task@ 7
put task@ 8
put task@ 9
pool-1-thread-4...start ...task@ 9
put task@ 10
pool-1-thread-5...start ...task@ 10
pool-1-thread-1...start ...task@ 4
pool-1-thread-2...start ...task@ 5
案例2:
/**
* 需求:
* 1.100个生产任务
* 2.核心线程数是3
* 3.最大线程数是5
* 4.2秒进行线程的回收
* 5.队列最多有6个在排队
*
* 1.当排队满的时候,给出提示,正在使用策略
* 2.打印过程数据
*/
public class ThreadPoolExecutorTest
{
private ThreadPoolExecutor mTpe;
public void createThreadPool() {
/*
* 创建线程池,最小线程数为3,最大线程数为5,线程池维护线程的空闲时间为3秒,
* 使用队列深度为6的有界队列,如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,
*/
/**
* AbortPolicy 队列满"抛出异常"
* CallerRunsPolicy "重试添加"当前任务,会自动调用execute()方法
* DiscardOldestPolicy 抛弃"旧"任务
* DidscardPolicy 抛弃"当前任务"
*
*/
ThreadPoolExecutor.CallerRunsPolicy HANDLER = new ThreadPoolExecutor.CallerRunsPolicy();
mTpe = new ThreadPoolExecutor(3, 5, 2, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(6),HANDLER);
// 向线程池中添加 100 个任务
for (int i = 0; i < 100; i++) {
mTpe.execute(new TaskThreadPool(i));
}
// 关闭线程池
mTpe.shutdown();
}
public static void main(String[] args)
{
ThreadPoolExecutorTest test = new ThreadPoolExecutorTest();
test.createThreadPool();
}
class TaskThreadPool implements Runnable
{
private int index;
public TaskThreadPool(int index)
{
this.index = index;
}
public void run() {
System.out.println("线程池中任务数是" + mTpe.getQueue().size());
System.out.println(Thread.currentThread().getName() + " 生产中... :" + index);
try {
//模拟生产耗时,一个工序要3秒
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 完成了 :" + index);
}
}
}
打印结果
线程池中任务数是6
线程池中任务数是6
线程池中任务数是6
线程池中任务数是6
main 生产中... :11
线程池中任务数是6
pool-1-thread-1 生产中... :0
pool-1-thread-4 生产中... :9
线程池中任务数是6
pool-1-thread-3 生产中... :2
pool-1-thread-2 生产中... :1
pool-1-thread-5 生产中... :10
pool-1-thread-1 完成了 :0
pool-1-thread-4 完成了 :9
main 完成了 :11
线程池中任务数是4
pool-1-thread-4 生产中... :4
线程池中任务数是5
pool-1-thread-1 生产中... :3
线程池中任务数是6
main 生产中... :14
pool-1-thread-2 完成了 :1
pool-1-thread-5 完成了 :10
pool-1-thread-3 完成了 :2
线程池中任务数是3
pool-1-thread-3 生产中... :7
8.怎么控制某个方法允许并发访问线程个数
采用Semaphore,信号的意思
1.创建对象,构造传入并发线程数量
static Semaphore semaphore = new Semaphore(5, true);
2.申请请求
semaphore.acquire();
3.释放
semaphore.release();
例子:
class Demo3 {
//1.创建一个对象
static Semaphore semaphore = new Semaphore(5, true);
public static void main(String[] args) {
//并发100个线程
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
test();
}
}).start();
}
}
public static void test(){
try {
//2.申请一个请求 ,相当于收尾包住
semaphore.acquire();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"进来了");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"走了");
//3.释放一个请求
semaphore.release();
}
}
打印结果
添加后,控制了,进来了同时最多5个:
Thread-1进来了
Thread-56进来了
Thread-2进来了
Thread-3进来了
Thread-4进来了
Thread-1走了
Thread-5进来了
不添加时:
Thread-0进来了
Thread-1进来了
Thread-2进来了
Thread-3进来了
Thread-4进来了
Thread-5进来了
Thread-6进来了
Thread-7进来了