线程池
-
作用:可以承载管理多个线程任务,需要时将任务提交执行,任务执行结束并不会立即销毁,而是回到池中等待下次执行,直至线程池关闭。可以大大降低一个任务反复执行时多次创建线程对象带来的内存压力。
相关API
-
ExecutorService:线程池接口
-
对象.submit(任务对象):将线程任务对象提交执行
-
对象.shutdown():关闭线程池
-
-
Excutors:线程池工具类
-
ExecutorService newfixedThreadPool(int ):获取一个固定并发数量的线程池对象
-
执行:当设置的并发数量大于等于任务提交数量时,则任务会直接并发执行。当设置的并发数量小于提交数量时,先提交的任务先并发执行,当有任务执行结束让位之后,剩余任务才能进入池中参与执行
-
-
ExecutorService newCachedThreadPool():获取一个不固定并发数量的线程池
-
执行:所有提交的任务都会同时并发执行
-
-
线程任务
-
Runnable:run()
-
特点:无返回值,不能上抛异常
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TestES { public static void main(String[] args) { //获取一个固定并发数量的线程池 // ExecutorService es = Executors.newFixedThreadPool(2); //获取不固定并发数量的线程池 ExecutorService es = Executors.newCachedThreadPool(); Runnable r1=new Runnable() { @Override public void run() { for (int i = 1; i <=50 ; i++) { System.out.println("r1:"+i); } } }; Runnable r2=new Runnable() { @Override public void run() { for (int i = 51; i <=100 ; i++) { System.out.println("r2="+i); } } }; Runnable r3=new Runnable() { @Override public void run() { for (int i = 101; i <=150 ; i++) { System.out.println("r3>>>"+i); } } }; es.submit(r1); // es.submit(r1); es.submit(r2); es.submit(r3); //关闭线程池 es.shutdown(); } }
-
-
Callable<返回值泛型>:call()
-
特点:有返回值,可以上抛异常,默认上抛Exception
-
使用:会将返回值封装进一个Future对象,该对象中的get()可以获取到被封装的返回值
import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class TestES_Callable { public static void main(String[] args) throws Exception{ //创建一个线程池对象 ExecutorService es = Executors.newCachedThreadPool(); //计算1-100的和 Callable<Integer> c1=new Callable<Integer>() { @Override public Integer call() throws Exception { int sum = 0; for (int i = 1; i <= 100; i++) { sum += i; } return sum; } }; //提交执行并接收返回值 Future<Integer> future =es.submit(c1); System.out.println("查看返回值:"+future.get()); //关闭线程池 es.shutdown(); } }
-
线程安全问题
-
当多个线程同时访问同一个临界资源时,有可能破坏其原子操作,从而导致数据缺失,就会发生线程安全问题
-
临界资源:被多个线程同时访问的对象
-
原子操作:访问临界资源对象时不可缺失或更改的操作步骤
互斥锁
-
每个对象都默认拥有互斥锁,当开启互斥锁之后,线程会进入同步状态,只有同时拥有时间片和锁标记的线程才有资格执行自身内容,其他线程只有等正在执行的线程完成原子操作之后才能争抢资源
-
synchronized:开启互斥锁
同步方法
-
原理:在被线程同时访问的方法上加锁
访问修饰符 synchronized 返回值类型 方法名(参数列表){}
import java.util.ArrayList; import java.util.List; /** * 操作集合属性的工具类 * 1. 往集合中添加元素 * 2. 查看集合元素 */ public class MyList { private List<Integer> list = new ArrayList<>(); //添加元素 //同步方法 public synchronized void insert(int n){ list.add(n); } //查看集合元素 public void query(){ //长度 System.out.println("集合长度为:"+list.size()); //查看内容 for (Integer i : list) { System.out.print(i+" "); } } }
同步代码块
-
原理:访问临界资源的线程们自己加锁
synchronized(临界资源对象){ //访问语句 }
import com.bz.util.MyList; public class TestMyList { public static void main(String[] args) throws Exception{ //创建一个工具类对象 MyList m = new MyList(); //线程1:添加1-5 Thread t1 = new Thread(()->{ synchronized (m) { for (int i = 1; i <= 5; i++) { m.insert(i); } } }); //线程2:添加6-10 Thread t2 = new Thread(()->{ //同步代码块 synchronized (m){ for (int i = 6; i <= 10; i++) { m.insert(i); } } }); t1.start(); t2.start(); //使t1和t2在主函数查看之前执行 t1.join(); t2.join(); //查看集合内容 m.query(); } } /* * 线程1:张三上厕所 * * 线程2:李四上厕所 * *临界资源:厕所 *原子操作:步骤:脱裤子-->蹲下去-->上厕所-->擦屁股-->提裤子-->站起来走人 * *加锁的第一种:给厕所大门上锁(厕所管理者) * 上厕所的条件:时间片(资格)+钥匙(争抢) *加锁的第二种:给坑位上锁(自己) * 上厕所的条件:时间片+钥匙(不需要争抢,自己上锁) * * */
区别
-
同步方法线程需要争抢时间片和锁标记,效率较慢
-
同步代码块线程只需要争抢时间片,拥有时间片的线程默认拥有锁标记,效率较快(更推荐)
线程安全的集合类
悲观锁:悲观的认为线程一定会发生安全问题,所以统加锁
Vactor
Hashtable
Properties
乐观锁:乐观的认为线程不会发生安全问题,所以不加锁,等到安全问题真正发生时,再用算法结合极少量的synchronized处理问题
JDK5.0
都属于java.util.concurrent
-
ConcurrentHashMap:CAS算法
比较并交换:比较预期值和原有值,当其相等时才会将结果值放入内存
int i=1;
i++;
原有值:1 预期值:1 结果值2
-
CopyOnWriteArrayList
-
原理:在对集合进行写操作(增删改)时,先复制出一个副本,在副本上完成写操作,如果副本中出现安全问题,则直接舍弃该副本,再次复制新的副本重复操作,直至副本中正常执行写操作,然后将集合地址转换成副本地址。
-
特点:舍弃写的效率,提高读的效率。适用于读操作远多于写操作时
-
-
CopyOnWriteArraySet
-
原理:与CopyOnWriteArrayList一致,在写入时会执行去重操作
-