多线程
1. Lock锁
- 重入锁 ReentrantLock
- 作用synchronized一样
- 如果出现异常,不会自动释放锁,所以开锁的代码一定放在finally中
- 读写锁
- 读锁 ReadLock
- 写锁 WriteLock
- 规则
- 写 写 互斥
- 写 读 互斥
- 读 读 不互斥
- 写 写 互斥
package com.qfedu;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo02 {
public static void main(String[] args) {
TestThread2 thread = new TestThread2();
for(int i=1; i<=10; i++) {
Thread t = new Thread(thread,"线程"+i);
t.start();
}
}
}
class TestThread2 implements Runnable {
/*
* ReentrantLock : 重入锁
* 作用和synchronized一默一样
*/
Lock lock = new ReentrantLock();
List<Integer> list = new ArrayList<Integer>();
@Override
public void run() {
int i = 1;
while(true) {
try {
lock.lock(); //加锁
if(i>100) {
break;
}
list.add(i);
i++;
} finally {
lock.unlock(); //解锁
}
}
System.out.println(Thread.currentThread().getName()+"添加完毕,当前数组中元素的个数为:"+list.size());
}
}
package com.qfedu;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
public class Demo04 {
/*
*
* 使用synchronized对方法进行加锁
* 如果多个线程同时操作同一个LockThread对象
* 1. 如果一个线程正在执行LockThread对象的setStr方法(写入)
* 1.1 其他线程都不能执行 getStr() setStr() 这两个方法
*
* 2. 如果一个线程正在执行LockThread对象的getStr方法(写入)
* 2.1 其他线程都不能执行 getStr() setStr() 这两个方法
*
* 写 写 互斥
* 写 读 互斥
* 读 读 互斥 即使出现线程不安全的情况,没什么影响
*
*
* 怎么才能达到读读不互斥呢?
* 换一种加锁的方式
* - 重入锁 ReentrantLock 作用synchronized一样
* - 读锁 ReadLock
* - 写锁 writeLock
* -- 写 写 互斥
* -- 写 读 互斥
* -- 读 读 不互斥
*
*/
}
class LockThread {
String str;
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
//读锁
ReadLock readLock = lock.readLock();
//写锁
WriteLock writeLock = lock.writeLock();
public void getStr() {
try {
readLock.lock();
System.out.println("得到str的值:"+str);
} finally {
readLock.unlock();
}
}
public synchronized void setStr(String str) {
try {
writeLock.lock();
System.out.println("为str的重写设置值:" + str);
} finally {
writeLock.unlock();
}
}
}
2. 线程池
使用线程池,获取多个线程,执行相应的任务,而且多个线程对象可以重复使用
package com.qfedu;
public class Demo01 {
public static void main(String[] args) {
A a = new A();
for(int i=1; i<=5; i++) {
Thread t = new Thread(a);
t.start();
}
B b = new B();
for(int i=1; i<=5; i++) {
Thread t = new Thread(b);
t.start();
}
}
}
class A implements Runnable {
int i=100;
@Override
public void run() {
while(true) {
if(i <= 0) {
break;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"打印了=="+i);
i--;
}
}
}
class B implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"==执行结束");
}
}
2.1 固定长度的线程池
语法:
ExecutorService es = Executors.newFixedThreadPool(线程个数);
- 会在线程池中创建固定个数的线程对象
- 线程执行实现了Runnable接口实现类对象定义的run方法
- 执行多少次submit()方法,就派多少个线程执行此业务
- 在执行结束后,线程对象不会被回收还可以继续使用执行其他任务
package com.qfedu;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo02 {
public static void main(String[] args) {
/*
* 1. 创建一个固定长度的线程池
* 池子里面有三个Thread对象
*/
ExecutorService es = Executors.newFixedThreadPool(6);
A a = new A();
/*
* 每执行一次submit方法,就是派一个线程执行该业务逻辑
*/
for(int i=1; i<=3; i++) {
es.submit(a);
}
/*
* 把a的业务代码执行后,还可以继续使用线程池的线程,执行新的任务
*/
B b = new B();
for(int i=1; i<=3; i++) {
es.submit(b);
}
//关闭线程池
es.shutdown();
}
}
2.2 可变长度的线程池
语法:
ExecutorService es = Executors.newCachedThreadPool();
- 多少个任务就创建多少个线程对象(核心线程数,最大线程数)
- 如果线程池的个数,小于核心线程数的,那么只要有新的任务就创建新的线程对象
- 如果线程池的个数,已经达到核心线程数,如果有新的任务
- 如果所有的线程都在执行,没有空闲线程,才会创建新的线程,但是不能超过最大线程数
- 我们很多设置把核心线程数,最大线程数相同
- 如果有空闲线程,那么不会创建新的线程,使用空闲的线程执行的任务
- 如果所有的线程都在执行,没有空闲线程,才会创建新的线程,但是不能超过最大线程数
package com.qfedu;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo03 {
public static void main(String[] args) {
/*
* 2. 创建一个可变长度的线程池
* 有多少个任务,就创建多少个线程对象去执行
*
*/
ExecutorService es = Executors.newCachedThreadPool();
A a = new A();
/*
* 每执行一次submit方法,就是派一个线程执行该业务逻辑
*/
for(int i=1; i<=4; i++) {
es.submit(a);
}
/*
* 把a的业务代码执行后,还可以继续使用线程池的线程,执行新的任务
*/
B b = new B();
for(int i=1; i<=4; i++) {
es.submit(b);
}
//关闭线程池
es.shutdown();
}
}
2.3 单线程的线程池
语法:
ExecutorService es = Executors.newFixedThreadPool(线程的个数);
- 线程池中只有一个线程
package com.qfedu;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo04 {
public static void main(String[] args) {
/*
* 3. 创建一个单线程的线程池
* 有多少个任务,就创建多少个线程对象去执行
*
*/
ExecutorService es = Executors.newSingleThreadExecutor();
A a = new A();
/*
* 每执行一次submit方法,就是派一个线程执行该业务逻辑
*/
for(int i=1; i<=3; i++) {
es.submit(a);
}
/*
* 把a的业务代码执行后,还可以继续使用线程池的线程,执行新的任务
*/
B b = new B();
for(int i=1; i<=3; i++) {
es.submit(b);
}
//关闭线程池
es.shutdown();
}
}
3. Runnable,Callable
- Runnable
- 定义一个线程的任务
- 如果想让一个线程调用执行什么业务逻辑,那就定义类实现Runnable,把业务定义在run方法中
- 可以做到多个线程操作同一个变量
- 没有返回值
- Callable
- 定义一个线程的任务
- 如果想让一个线程调用执行什么业务逻辑,那就定义类实现Callable,把业务定义在run方法中
- 可以做到多个线程操作同一个变量
- 可以有返回值
package com.qfedu;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Demo05 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
E e = new E();
ExecutorService es = Executors.newSingleThreadExecutor();
es.submit(e);
F f = new F();
Future<Integer> future = es.submit(f);
//获取执行完该任务后,返回的值
int result = future.get();
System.out.println(result);
es.shutdown();
}
}
class E implements Runnable {
@Override
public void run() {
int sum = 0;
for(int i=1; i<=100; i++) {
sum += i;
}
System.out.println(Thread.currentThread().getName()+"执行结束,结果为:"+sum);
}
}
class F implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i=1; i<=100; i++) {
sum += i;
}
return sum;
}
}
//编写一个任务,执行结束后,返回一个随机的小写字母
//编写一个双色球程序 6个1-33 1个1-16的整数
4. 线程安全的集合
1. 转化为线程安全的集合
package com.qfedu;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class Demo01 {
public static void main(String[] args) {
Collection<String> coll = new ArrayList<String>();
/*
* SynchronizedCollection 这是一个线程安全的集合
*/
Collection<String> sc = Collections.synchronizedCollection(coll);
/*
* SynchronizedList 线程安全的List集合
*/
List<String> list = new LinkedList<String>();
List<String> sl = Collections.synchronizedList(list);
/*
* SynchronizedSet 线程安全的Set集合
*/
Set<String> set = new HashSet<String>();
Set<String> ss = Collections.synchronizedSet(set);
/*
* SynchronizedMap 线程安全的Map集合
*/
Map<String,String> map = new HashMap<String,String>();
Map<String, String> sm = Collections.synchronizedMap(map);
}
}
- Vector
- Hashtable
他们都是线程安全集合,他们都是使用synchronized关键字修饰方法,同一时刻只能一个线程执行其中一个方法。
2. CopyOnWriteArrayList
- 线程安全的ArrayList,加强版读写分离
- 写有锁,读无锁,读写之间不阻塞
- 写入时,先copy一个容器副本、再添加新元素,最后替换引用
3. CopyOnWriteArraySet
- 线程安全的Set,底层使用CopyOnWriteArrayList实现
4. ConcurrentHashMap
- 初始容量默认为16段(Segment),使用分段锁设计
- 不对整个Map加锁,而是为每个Segment加锁
- 当多个对象存入同一个Segment时,才需要互斥
- 最理想状态为16个对象分别存入16个Segment,并行数量16。
- 使用方式与HashMap无异
5. 队列
队列也是一种集合,能够做到边遍历边删除的效果,这符合很多实际的使用场景
- 银行取号排队
- 医院挂号排队
- 请求的排队处理
package com.qfedu;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
public class Demo02 {
public static void main(String[] args) {
//定义队列
Queue<String> queue = new ArrayBlockingQueue<String>(5);
//添加数据
queue.offer("aa");
queue.offer("bb");
queue.offer("cc");
queue.offer("dd");
queue.offer("ee");
for(int i=0;i<queue.size(); i++) {
String v = queue.poll();
System.out.println(v+"队列中还剩下"+queue.size()+"个元素");
i--;
}
}
}
6. lambda表达式
当接口中只有一个抽象方法时,可以使用lambda表达式来实现接口的实现
接口 变量 = (参数列表) -> {
//方法体
};
- 如果方法没有返回值,且方法体只有一行代码,可以省略掉大括号{}
- 如果有参数,参数可以直接定义在小括号中
- 可以不定义参数的数据类型 可以自动匹配
- 如果参数只有一个,不仅数据类型可以省略,小括号也可以省略
- 如果有参数,有返回值,那就在大括号中使用return关键返回数据
- 如果方法体中只有一行代码,且为返回数据(return),在省略掉大括号时,也可以把return关键字也省略点
package com.qfedu;
public class Demo02 {
public static void main(String[] args) {
/*
* () -> {}
*
* () : 参数列表
* {} : 方法体
*/
B b1 = () -> {
System.out.println("实现了test方法1");
/* System.out.println("实现了test方法1"); */
};
/*
* 如果方法没有返回值,且方法体只有一行代码,可以省略掉大括号{}
*/
B b2 = () -> System.out.println("实现了test方法2");
/*
* 如果有参数,参数可以直接定义在小括号中
*/
C c = (int a, int b) -> System.out.println(a+b);
/*
* 如果有参数,参数可以直接定义在小括号中,可以不定义参数的数据类型 可以自动匹配
*/
C c2 = (a, b) -> System.out.println(a+b);
/*
* 如果参数只有一个,不仅数据类型可以省略,小括号也可以省略
*/
CD cd = a ->System.out.println(a);
/*
* 如果有参数,有返回值,那就在大括号中使用return关键返回数据
*/
D d = (a, b) -> {
int sum = a+b;
return sum;
};
/*
* 如果方法体中只有一行代码,且为返回数据(return)
* 在省略掉大括号时,也可以把return关键字也省略点
*/
D d2 = (a, b) -> a+b;
int r = d2.test(1, 2);
System.out.println("-------------------------------------------");
Runnable runnable = ()->{
};
Thread t = new Thread(runnable);
Thread t2 = new Thread(()->{
});
//使用lambda表达式实现接口的方式,让4个窗口共卖100张票
}
}
interface B {
void test();
}
interface C {
void test(int a, int b);
}
interface CD {
void test(int a);
}
interface D {
int test(int a, int b);
}
7. 函数式接口
1. 消费型接口
消费型接口:只有参数,没有返回值
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
Consumer<Integer> consumer = t -> {
System.out.println(t%10);
};
consumer.accept(19);
2. 供给型接口
public interface Supplier<T> {
T get();
}
Supplier<Character> supplier = () ->{
int n = (int) (Math.random()*26+97);
return (char) n;
};
Character c = supplier.get();
3. 函数型接口
public interface Function<T, R> {
R apply(T t);
}
Function<String, Integer> function = t -> t.length();
Integer len = function.apply("abc");
System.out.println(len);
4. 断言型接口
public interface Predicate<T> {
boolean test(T t);
}
//断言型接口
Predicate<String> predicate = t -> t.startsWith("j");
boolean r = predicate.test("jack");
System.out.println(r);