线程状态
线程状态。 一个线程可以是以下状态之一:
- NEW 尚未启动的线程处于这种状态。
- RUNNABLE 在Java虚拟机上执行的线程处于这种状态。
- BLOCKED 被阻止等待监视器锁的线程处于这种状态。
- WAITING 即
无限期地
等待另一个线程来执行某一特定操作的线程处于这种状态。(不见不散) - TIMED_WAITING 正在等待另一个线程来达到一个
指定的等待时间
执行动作的线程处于这种状态。(过时不候) - TERMINATED 已退出的线程处于这种状态。
wait放开手去睡,
放开手里的锁
。
sleep握紧手去睡,醒了手里还有锁
。
创建线程方法:
- 继承Thread(不推荐使用。Java是单继承,资源宝贵,尽量使用接口)
- Runnable接口。
- 实现Runnable接口;
- 使用匿名内部类;(new Thread(Runnable runnable, String name))
- 使用lambda表达式。(new Thread(Runnable target, String name))
- 通过Callable和Future接口创建线程
多线程交互中,必须要防止多线程的虚假唤醒,多线程的判断中不用if,用while
wait和notifyAll不是Thread类中的方法,是Object类中的方法
if语句中醒来的线程 不会再一次进行判断了 而while会重新再判断
线程的虚假唤醒
package com.example.demo.thread;
class AirConditioner {
private int number = 0;
public synchronized void increament() throws InterruptedException {
if (number != 0) {
wait();
}
number++;
System.out.println(Thread.currentThread().getName() + "\t" + number);
this.notifyAll();
}
public synchronized void decreament() throws InterruptedException {
if (number == 0) {
wait();
}
number--;
System.out.println(Thread.currentThread().getName() + "\t" + number);
this.notifyAll();
}
}
public class ThreadWaitNotifyDemo {
public static void main(String[] args) {
AirConditioner airConditioner = new AirConditioner();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(200);
airConditioner.increament();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(300);
airConditioner.decreament();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(400);
airConditioner.increament();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(500);
airConditioner.decreament();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "D").start();
}
}
用while重新判断
package com.example.demo.thread.test;
public class AddThread {
private Boolean flag = false;
public synchronized void increament() throws InterruptedException {
while (flag) {
wait();
}
for (int i = 0; i < 2; i++) {
System.out.println("123");
}
flag = true;
notifyAll();
}
public synchronized void decreament() throws InterruptedException {
while (flag == false) {
wait();
}
for (int i = 0; i < 1; i++) {
System.out.println("abc");
}
flag = false;
notifyAll();
}
}
class ThreadWaitNotifyDemo {
public static void main(String[] args) {
AddThread addThread = new AddThread();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
addThread.increament();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
addThread.decreament();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
}
}
用lock替代原来的synchronized
package com.example.demo.thread;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadNewWaitNotifyDemo {
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void increament() {
lock.lock();
try {
while (number != 0) {
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName() + "\t" + number);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decreament() {
lock.lock();
try {
while (number == 0) {
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName() + "\t" + number);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
class ThreadWaitNotifyDemo1 {
public static void main(String[] args) {
ThreadNewWaitNotifyDemo threadNewWaitNotifyDemo = new ThreadNewWaitNotifyDemo();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
threadNewWaitNotifyDemo.increament();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
threadNewWaitNotifyDemo.increament();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
threadNewWaitNotifyDemo.decreament();
}
}, "C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
threadNewWaitNotifyDemo.decreament();
}
}, "D").start();
}
}
多线程锁
多线程8锁
标准访问(两个普通的同步方法,同一部手机),先打印邮件还是短信? Email
邮件方法暂停4秒,先打印邮件还是短信? 邮件
新增一个非同步方法hello,先打印邮件还是hello? hello
两部手机,先打印邮件还是短信 短信
两个静态同步方法,同一部手机,先打印邮件还是短信? 邮件
两个静态同步方法,两部手机,先打印邮件还是短信? 邮件
一个普通同步方法,1个静态同步方法,1部手机,先打印邮件还是短信? 短信
一个普通同步方法,1个静态同步方法,2部手机,先打印邮件还是短信? 短信
第一、二锁:
一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用一个synchronized方法了,其他线程只能等待。换句话说,某一个时刻内,只能由唯一一个线程去访问这些synchronized方法,锁的是当前对象this
,被锁定后,其他线程都不能访问当前对象的其他synchronized方法。
第三锁:
加一个普通方法和同步锁无关
第四锁:
两个对象代表着两个资源类,那么每个线程都持有自己对应的资源类的锁,所以互不相干。
第五、六锁:
都换成静态同步方法之后:new 出的对象 this,是具体的一部手机,而静态的 是 class,是一个唯一的模板。所以static修饰的同步方法,和几部手机无关。它锁的永远只是当前类。
第七八锁:
静态同步方法和非静态同步方法的两把锁是两个不同的对象,一个是当前实例对象,一个是类class 模板,所以不会有资源竞争。
结论:
- synchronized实现同步的基础:Java中的每一个对象都可以作为锁,具体表现为以下三种形式:
- 对于普通同步方法,锁是当前的实例对象(new 出的实例对象);
- 对于静态同步方法,锁的是当前的类class(就是对象.class,也就是说,不论new几个对象,锁的都是这个类模板);
- 对于同步方法块:锁的是synchronized括号里配置的对象。
- 当一个线程试图访问同步代码块时,首先必须获得锁,退出或抛出异常时必须释放锁。
- 静态同步方法和非静态同步方法的两把锁是两个不同的对象,所以不会有资源竞争。
不安全集合类
public class NoSafeCollections {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list + Thread.currentThread().getName());
}, String.valueOf(i)).start();
}
}
}
处理bug的解决步骤:
- 故障现象:java.util.ConcurrentModificationException(并发修改异常)
- 导致原因:ArrayList是线程不安全的,当多个线程同时进行add操作时,就会造成数据不一致,甚至崩溃。
- 解决方案:
- 使用Vector(Vector线程安全的,但是同一时间只能有一个线程读一个线程写,效率降低了)
- Collections.synchronizedList(new ArratyList<>()) 。使用集合工具类将一个线程不安全的集合转换成一个线程安全的集合。
- 使用JUC包下的
CopyOnWriteArrayList
。- 优化建议(同样的错误,不出现第二次)
同理,HashSet和HashMap也是线程不安全的,也都有对应的工具类Connections的方法转换成线程安全的,JUC中也有与之对应的线程安全的集合:
HashSet ==》 CopyOnWriteArraySet
HashMap ==》 ConcuuentHashMap
add方法源码:
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
写时复制:
CopyOnWrite容器,即写时复制容器。往一个容器添加元素的时候,不直接往当前容器Object[]添加,而是先将当前容器Object[]获取到然后复制一份(赋值后的容器大小要加1),得到新的容器Object[] newElements,然后在新容器中添加元素,添加完成后再将原容器的引用指向新的容器
setArray(newElements);
。好处:
可以对copyOnWrite容器进行并发读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,即读和写时操作的不是同一个容器。
-
HashSet,有序,不可重复:
-
底层数据结构:HashMap
-
HashSet的add方法调用的是HashMap的put方法,只不过HashSet添加一个数据时,添加的是HashMap的key值,而HashMap的value值是一个Object类型的常量。
-
/** * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has * default initial capacity (16) and load factor (0.75). */ public HashSet() { map = new HashMap<>(); }
-
/** * Adds the specified element to this set if it is not already present. * More formally, adds the specified element <tt>e</tt> to this set if * this set contains no element <tt>e2</tt> such that * <tt>(e==null ? e2==null : e.equals(e2))</tt>. * If this set already contains the element, the call leaves the set * unchanged and returns <tt>false</tt>. * * @param e element to be added to this set * @return <tt>true</tt> if this set did not already contain the specified * element */ public boolean add(E e) { return map.put(e, PRESENT)==null; }
-
-
HashMap(无序,无重复):
- 顶层数据结构:
Node类型的数据 + Node类型的链表 + 红黑树
。 - 默认
容量大小(数组大小)为1
6,默认负载因子为0.75
。 - 默认和容量和负载因子可以在创建HashMap时候使用
HashMap(int initialCapacity, float loadFactor)
构造方法来指定。 - 扩容:当前容量为 16 * 0.75 = 12 时,HashMap会进行扩容,
扩容为原来的一倍(16 ==> 32)
(ArrayList扩容时时原来的一半)。当需要HashMap的容量比较大时,为了避免扩容的损耗,可以将容量设置大一些就可以避免频繁的扩容。
- 顶层数据结构:
7. Callable接口
callable接口和Runnable接口的区别:
- 是否有返回值;Callable返回一个泛型的返回值,Runnable实现的方法没有返回值
- 是否抛出异常;Callable接口方法抛出一个Exception异常
- 实现方法名不同,Callable时call方法,Runnable时run方法
获取线程的几种方式:
传统的方式是继承thread类和实现runnable接口,
java5以后又有实现callable接口和java的线程池获得线程
class MyThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("come in");
return 1024;
}
}
public class CallableDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask futureTask = new FutureTask(new MyThread());
new Thread(futureTask, "A").start();
System.out.println(futureTask.get());
}
}
利用Java的多态来找到使用Callable的使用方法
使用FutureTask的构造方法public FutureTask(Callable callable)可以添加一个Callable接口或实现类,由于FutureTask类时FutureRunnable的实现类,FutureRunnable优势Runnable的子接口,所以可以使用Thread类的构造方法public Thread(Runnable target, String name)直接传入一个FutrueTask类来使用Callable。
FutureTask:未来的任务,用它就干一件事,异步调用
main方法就像一个冰糖葫芦,一个个方法由main线程串起来。但解决不了正常调用挂起的堵塞问题(若某个线程执行时间过长,就会阻塞,造成主线程以及其他线程等待该线程执行完成才能继续执行)。原理:
- 在主线程中执行一个耗时的操作,不想让主线程阻塞到这个操作中,这是也可以把任务交给FutureTask来完成,它可以在主线程需要的时候获取到该操作的结果。
- 仅在计算完成后才能获取结构,没完成还是会阻塞get方法。一旦完成后既不能重新开始或取消计算。get方法获取结果时或获取正确结果或者抛出一个异常。
- FutureTask的任务只会执行一次(多个线程调用一个FutureTask实例)。
- get方一般放到最后。
ArrayList 线程不安全
ArrayList底层是Object类型的数组
HashSet底层是HashMap,hashset的add方法实际是调用hashmap的put方法,add的值就是put的key,map的value就是固定写死的Object类型的PRESENT常量
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
hashMap底层是node类型的数组+node类型的链表+红黑树
ArrayList在原有的大小上再扩容0.5倍
hash在原有的大小上再扩容1倍,hashmap的容量为2的幂次方,查看源码即可发现在计算存储位置时,计算式为:
(n-1)&hash(key)
容量n为2的幂次方,n-1的二进制会全为1,位运算时可以充分散列,避免不必要的哈希冲突。
所以扩容必须2倍就是为了维持容量始终为2的幂次方
hashMap解决并发问题,用ConcurrentHashMap<>();
Map<String, String> map = new ConcurrentHashMap<>();
new Thread()中要放入一个实现了Runnable接口的实现类,现在是Callable接口,找桥梁工具类,将Callable类型和Runnable类型联系起来,就是FutureTask类,FutureTask构造方法可以单独传一个Callable接口,FutureTask实现了Runnable接口(java的多态思想)
class MyThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("线程进入");
return 1024;
}
}
public class CallableDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask futureTask = new FutureTask(new MyThread());
new Thread(futureTask, "A").start();
System.out.println(futureTask.get());
}
}
一个futureTask对象不管几个线程调用同一个对象方法,结果都只有一个不会出现两次
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t" + "离开教室");
countDownLatch.countDown();
}, String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + "\t" + "班长关门走人");
}
}
在JUC包中为我们提供了一个同步工具类能够很好的模拟这类场景,它就是CyclicBarrier类。利用CyclicBarrier类可以实现一组线程相互等待,当所有线程都到达某个屏障点后再进行后续的操作。
public class CyclicBarrierDemo {
public static void main(String[] args) {
final CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("召唤神龙");
});
for (int i = 0; i < 7; i++) {
final int temp = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t" + temp + "颗龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
}
}
信号灯主要用于多线程的并发控制
public class SemaphoreDemo {
public static void main(String[] args) {
final Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 6; i++) {
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "\t" + "抢占了车位");
try {
TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}
System.out.println(Thread.currentThread().getName() + "\t" + "离开了车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}, String.valueOf(i)).start();
}
}
}
ReadWriteLock:读写锁
class MyCash {
private volatile Map<String, Object> map = new HashMap<>();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void put(String key, Object value) {
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "\t -----写入数据" + key);
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "\t -----写入完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
public void get(String key) {
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "\t -----读取数据");
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
final Object result = map.get(key);
System.out.println(Thread.currentThread().getName() + "\t -----读取完成" + result);
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
public class ReadWriteLockDemo {
public static void main(String[] args) {
final MyCash myCash = new MyCash();
for (int i = 0; i < 5; i++) {
final int temp = i;
new Thread(() -> {
myCash.put(temp + "", temp + "");
}, String.valueOf(i)).start();
}
for (int i = 0; i < 5; i++) {
final int temp = i;
new Thread(() -> {
myCash.get(temp + "");
}, String.valueOf(i)).start();
}
}
}
线程池主要有三个,固定的,单一的,可扩展的,虽然表面上是三个,但实际上是一个ThreadPoolExecutor类,这个类有七大参数
ThreadPoolExecutor的七大参数
1.常驻核心线程数
2.最大线程数
3.4多余线程存活时间,一个是数字,一个是时间单位
5.等候区阻塞队列
6.线程池中生成线程的线程工厂,一般用默认
7.拒绝策略
以下重要:以下重要:以下重要:以下重要:以下重要:以下重要:
1、在创建了线程池后,开始等待请求。
2、当调用execute()方法添加一个请求任务时,线程池会做出如下判断:
2.1如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
2.2如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;
2.3如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
2.4如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
3、当一个线程完成任务时,它会从队列中取下一个任务来执行。
4、当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:
如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。
所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。
面试题:
在工作中单一的/固定数的/可变的三种创建线程池的方法哪个用的多?超级大坑
public static void main(String[] args) {
ExecutorService threadPool3 = new ThreadPoolExecutor(
2,
5,
2L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
线程池的拒绝策略
查看CPU核数
System.out.println(Runtime.getRuntime().availableProcessors());
ForkJoinDemo任务拆分合并
class MyTask extends RecursiveTask<Integer> {
private static final Integer ADJUST_VALUE = 10;
private int begin;
private int end;
private int result;
public MyTask(int begin, int end) {
this.begin = begin;
this.end = end;
}
@Override
protected Integer compute() {
if ((end - begin <= ADJUST_VALUE)) {
for (int i = begin; i <= end; i++) {
result = result + i;
}
} else {
int middle = (end + begin) / 2;
MyTask myTask1 = new MyTask(begin, middle);
MyTask myTask2 = new MyTask(middle + 1, end);
myTask1.fork();//调用fork递归执行computer方法
myTask2.fork();
try {
result = myTask1.get() + myTask2.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
return result;
}
}
public class ForkJoinDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyTask myTask = new MyTask(0, 100);
final ForkJoinPool forkJoinPool = new ForkJoinPool();
final ForkJoinTask<Integer> submit = forkJoinPool.submit(myTask);
System.out.println(submit.get());
forkJoinPool.shutdown();
}
}
public class CompletableFutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
final CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName() + "没有返回, update is ok");
});
completableFuture.get();
CompletableFuture<Integer> completableFuture1 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "\t" + "completableFuture1");
int age = 10 / 0;
return 1024;
});
completableFuture1.whenComplete((t, u) -> {
System.out.println("************ " + t);
System.out.println("************ " + u);
}).exceptionally(f -> {
System.out.println("exception " + f.getMessage());
return 4444;
}).get();
}
}