创建子线程
继承Thread类
继承Thread类,重写Thread类的run()方法,然后使用start()启动
public class TestThread {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
MyThread myThread = new MyThread();
myThread.start();
for (int i = 0; i < 10; i++) {
System.out.println("我是主线程,运行中" + i);
}
}
}
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("我是子线程,运行中" + i);
}
}
}

2.实现Runnable接口
实现Runnable接口,实现run()方法,通过new Thread(new 实现Runnable接口的类 )的start()方法启动线程
public class TestThread {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Thread myThread = new Thread(new MyThread());
myThread.start();
for (int i = 0; i < 10; i++) {
System.out.println("我是主线程,运行中=====" + i);
}
}
}
class MyThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("我是子线程,运行中---------" + i);
}
}
}

3.实现Callable<>类
通过Callable接口创建线程可以是线程返回一个值给主线程,需要实现Callable类的call()方法,指定返回值的泛型,通过FutureTask类代理自定义的方法类,然后通过Thread类的start()方法启动,
通过FutureTask类的get()方法可以可以得到返回值
public class TestThread {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
MyThread myThread1 = new MyThread();//创建自定义对象
FutureTask<Integer> futureTask = new FutureTask<>(myThread1);//利用FutureTask对象接收
Thread myThread = new Thread(futureTask);//通过代理模式创建线程对象
myThread.start();
try {
System.out.println("子线程返回值"+futureTask.get());//通过代理FutureTask可以得到线程返回值
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyThread implements Callable<Integer> {
@Override
public Integer call() {
int i = 0;
for (; i < 10; i++) {
System.out.println("我是子线程,运行中---------" + i);
}
return i;
}
}

函数式接口
定义
对于只定义了一个抽象方法的接口,可以称之为函数式接口
如Runnable接口:
@FunctionalInterface
public interface Runnable {
void run();
}
运用
可以使用lamda表达式实列化函数式接口;
可以在用runnable接口创建函数时直接使用lamda表达式实现接口
public class TestThread {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Thread myThread = new Thread(()-{
for (int i = 0; i < 10; i++) {
System.out.println("我是子线程,运行中---------" + i);
}
});//使用lamda表达式创建Runnable函数式接口
myThread.start();
for (int i = 0; i < 10; i++) {
System.out.println("我是主线程,运行中=====" + i);
}
}
}
//class MyThread implements Runnable{
// @Override
// public void run() {
// for (int i = 0; i < 10; i++) {
// System.out.println("我是子线程,运行中---------" + i);
// }
// }
//}
常用线程方法
线程休眠
Thread.sleep(毫秒数);
在代码中使用这个静态方法,会让当前线程立即放弃cpu和cpu竞争,指定时间后再重新进入cpu竞争状态
线程礼让
Thread.yield();
在代码中使用这个静态方法后,会让当前线程立即放弃cpu,让cpu重新开启线程调度,当前线程可能会被再次调度执行
线程强制执行
thread.join();
Thread类的普通方法,使用对象调用,会让调用对象所对应的线程强制执行并且不让出cpu资源,只有在强制执行的线程执行完毕后才会让出cpu资源
获取线程状态
thread.getState();
Thread类的普通方法,使用对象调用,会返回当前对象所对应线程的状态
设置线程优先级
thread.setPriority(a); //a的取值范围是1到10,不在这个范围会报错 线程优先级默认为5
Thread类的普通方法,使用对象调用,优先级越高,被cpu调用的机会越大
设置守护线程
myThread.setDaemon(true);
Thread类的普通方法,使用对象调用,会让对应线程被设置会守护线程
线程分为用户线程和守护线程,默认为用户线程
虚拟机必须保证用户线程执行完后才会结束程序
虚拟机不会保证守护线程执行完毕
synchronized与Lock
锁的概念
当一个线程设置一个资源不允许其他线程使用时,就是加锁
当该线程允许其他线程使用时,就是解锁
synchronized关键字
synchronized时java中的一个关键字,可以用在静态方法,普通方法和代码块前,使资源加锁
用在静态方法前,锁的是当前的类,不是类的对象
用在成员方法前,锁的是当前的对象
用在代码块前,需要指定需要锁的变量或者类,一个或者多个
synchronized是自动锁,不需要手动解锁
是一个重量级锁,很消耗cpu资源
Lock接口
Lock lock=new ReentrantLock();
通过这个方式可以获取当前线程的锁,锁的细粒度比synchronized更低
lock.lock(); //加锁
lock.unlock(); //解锁,推荐放在finally {}中
线程等待与唤醒
thread.wait(); //使当前对象对应的线程等待 TERMINATED状态
thread.notify(); //使当前对应线程的对象唤醒 RUNNABLE状态
thread.notifyAll(); //唤醒所有线程
Condition精确唤醒
通过Lock接口获取Condition对象,await()等待,signal()唤醒,更精确的控制

集合类的安全问题
new ArrayList<>();
new HashMap<>();
在多线程中,上面两个集合在插入和删除使会出现错误,因为没有加锁
new CopyOnWriteArrayList<>();
new ConcurrentHashMap<>();
然后就引入了安全集合类,CopyOnWriteArrayList底层使用Lock接口实现安全,ConcurrentHashMap底层使用synchronized锁代码块实现安全,在多线程中插入和删除不会出现锁安全问题
常用工具类
减法计数器工具类CountDownLatch

加法计数器工具类CyclicBarrier

信号量工具类Semaphore

读写锁ReadWriteLock
读 读 两锁可以共存
读 写 可以共存
写 写 不可以共存

阻塞队列BlockingDeque接口
方式 | 抛出异常 | 有返回值,不跑出异常 | 阻塞,等待 | 超时等待 |
添加元素 | add | offer | put | offer(value,时间,时间单位) |
移除元素 | remove | poll | take | poll(时间,时间单位) |
返回首元素 | element | peek |
抛出异常 在队列满时添加,在队列为0时删除或者返回首元素会抛出异常
有返回值,不跑出异常 会有一个返回值,不会抛出异常
阻塞,等待 会一直阻塞,直到满足条件后执行操作
超时等待 会阻塞指定时间,超时后取消操作
同步队列SynchronousQueue
SynchronousQueue作为阻塞队列的时候,对于每一个take的线程会阻塞直到有一个put的线程放入元素为止,反之亦然。
在SynchronousQueue内部没有任何存放元素的能力,可以理解为 容量为 0。所以类似peek操作或者迭代器操作也是无效的,元素只能通过put类操作或者take类操作才有效。
SynchronousQueue支持支持生产者和消费者等待的 公平性策略。默认情况下: 非公平。
如果是公平锁的话可以保证当前第一个队首的线程是等待时间最长的线程,这时可以视SynchronousQueue为一个FIFO队列。
SynchronousQueue 提供两种实现方式,分别是 栈和 队列的方式实现。这两种实现方式中, 栈是属于非公平的策略, 队列是属于公平策略。
线程池
操作系统每次创建线程和销毁线程都是是否消耗资源的,所以,Java就引入线程复用概念,即线程池,创建线程池后,会默认分配核心池大小的线程资源给线程池
当核心池满了后,在进入的线程会进入阻塞队列进行阻塞等待
阻塞队列满了后,会新增额外池进行处理新的线程,即池大小再增加(最大池大小-核心池大小)
当额外池再超过指定时间都是空后,会自动释放额外池,变回核心池大小
当最大池大小和阻塞队列都满后,会触发指定的拒绝策略进行处理
创建线程池
构造函数的七大参数

最大线程池数设置
Runtime.getRuntime().availableProcessors() 获取当前系统最大线程数
四大拒绝策略
ThreadPoolExecutor.AbortPolicy(); 在最大池和阻塞队列都满后,再让线程池新增线程,会抛出异常,, 默认
ThreadPoolExecutor.CallerRunsPolicy(); 会让新加入想新的被线程返回调用者处,让想使新线程进入线程池的线程处理
ThreadPoolExecutor.DiscardOldestPolicy(); 会立即抛出阻塞队列头的线程,然后让新线程进入阻塞队列
ThreadPoolExecutor.DiscardPolicy(); 会线程池忽略这个新进入的线程,不做任何处理
使用线程池运行线程
threadPoolExecutor.execute(Runnable接口或者lamda表达式);
关闭线程池
threadPoolExecutor.shutdown();
异步回调

.get()方法阻塞等待线程的返回结果,运行成功返回正常,失败返回指定返回值
.whenComplete() 参数是一个函数式接口,第一个值是正常返回结果,第二个值是错误信息
.exceptionally() 参数是一个函数式接口,必须有一个返回值,指定的错误消息