1.回顾
1.1 进程和线程
- 进程是指运行中的程序,是一种状态,是CPU资源分配的最小单元;
- 线程是CPU调度的最小单元,一个进程可以有多个线程。
- Java默认有两个线程:main、GC
- Java本身不会开启线程,而是通过调用底层的C++本地方法来开启线程的(start)
1.2 并发和并行
- 并发:
针对单核CPU,多个线程操作一个资源–>提升CPU的利用率 - 并行
针对多核CPU,多个线程可以同时执行;线程池;
1.3 线程的六种状态
- NEW(新生)
- RUNNABLE(可运行态)
- BLOCKED(阻塞态)
- WAITING(等待态)
- TIMED_WAITING(超时等待)
- DEAD(死亡态)
1.4 sleep/wait
- 所属的类不同;sleep是Thread的方法;wait是Object类的方法;
- 是否释放对象:sleep暂停线程,但不释放对象;wait释放线程;
- 存在的位置不同;sleep可以在任意地方,wait只能在同步代码块里;
- 是否引发异常:sleep需要捕获异常并处理,wait不会引发异常。
2.LOCK
2.1 回顾synchronized
- 使用方式
static class Ticket{
private int number= 50;
public void sale(){
synchronized(this){
if (number>0){
System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,当前剩余票数为"+number);
}
}
}
}
可以加在方法上、同步代码块上
2.2 LOCK的使用
static class Ticket{
private int number= 50;
Lock lock = new ReentrantLock();
public void sale(){
try {
lock.lock();
if (number>0){
System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,当前剩余票数为"+number);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
2.3 Lock和synchronized的区别
- synchronized是Java内置的关键字,lock是JUC下的一个类;
- synchronized无法判断锁状态,lock可以判断是否获取到了锁;
- synchronized会自动释放锁,lock需要手动释放锁,如果不释放将引发死锁;
- synchronized可重入锁,不可中断,非公平;lock可重入锁,非公平(可手动设置)
- synchronized同一时刻只允许一个线程使用,其他的线程会一直处于等待阻塞的状态;lock可以使用lock.tryLock()尝试获取锁,而不是让无锁的线程一直处于等待状态;
- synchronized适合少量的代码同步问题,lock适合大量的代码同步问题;
3.生产者消费者问题
3.1 传统的synchronized方法,等待(当前线程)-》业务-》唤醒(其他线程)
//资源类
static class Data{
private int number = 0;
//+操作
public synchronized void increment() throws InterruptedException {
if (number!=0){
//使当前线程等待
wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"==>"+number);
//业务执行完毕,唤醒其他线程
this.notifyAll();
}
//-操作
public synchronized void decrement() throws InterruptedException {
if (number==0){
//使当前线程等待
wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"==>"+number);
//业务执行完毕,唤醒其他线程
this.notifyAll();
}
}
- 但当线程的数量增加时,将无法满足线程间通信问题;因为存在虚假唤醒,解决思路是将if判断改为while
3.2 使用lock解决生产者消费者问题(线程间通信)
static class Data{
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//+操作
public void increment() throws InterruptedException {
lock.lock();
try {
while (number!=0){
//使当前线程等待
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"==>"+number);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//-操作
public void decrement() throws InterruptedException {
lock.lock();
try {
while (number==0){
//使当前线程等待
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"==>"+number);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
condition相较之前使用synchronized方式实现线程通信的改进:可以使得多个线程有序执行任务!
static class Data{
private int number=1;
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
//使执行的顺序为A-B-C
public void printA(){
lock.lock();
try {
while (number!=1){
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"=>AAAAAAA");
number=2;
condition2.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
while (number!=2){
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"=>BBBBBB");
number=3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
while (number!=3){
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"=>CCCCCC");
number=1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
4.八锁问题
5.Callable接口
- 要想执行有返回值的任务,需要实现callable接口
public class MyThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return 1234;
}
}
已知:使得线程开始执行的方式只有:new Thread(任务).start();
为了建立 线程执行有返回值的任务 这样的联系,使用FutureTask
方法:
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
将实现了Callable接口的有返回值的方法传入到该方法中:
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyThread myThread = new MyThread();
FutureTask futureTask = new FutureTask<>(myThread);
new Thread(futureTask,"A").start();
Integer n = (Integer) futureTask.get();
System.out.println(n);
}
总结与分析:
线程启动的方法只有一个:new Thread().start(); 但是之前线程执行的业务都是没有返回值的, 要想执行有返回值有异常的任务就必须要使得当前执行业务的类实现Callable接口;为了建立Thread和Callable任务之间的联系, 使用一个FutureTask类承接返回值,并使用get方法获取任务最终的返回值;
6.集合都是线程不安全的
6.1 List线程不安全
如:
public static void main(String[] args) {
ArrayList<Object> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
结果:
[403fa, 7ca09, 09dbb, 1483e, e9815, e8fda]
Exception in thread "2" Exception in thread "3" Exception in thread "1" Exception in thread "7" java.util.ConcurrentModificationException
[403fa, 7ca09, 09dbb, 1483e, e9815, e8fda, 22f0e, 8eb34, 9135d, d29a7]
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
[403fa, 7ca09, 09dbb, 1483e, e9815, e8fda]
at java.util.ArrayList$Itr.next(ArrayList.java:851)
[403fa, 7ca09, 09dbb, 1483e, e9815, e8fda, 22f0e, 8eb34]
[403fa, 7ca09, 09dbb, 1483e, e9815, e8fda, 22f0e]
...
解决方法:
- List list = new Vector();
- ArrayList Oldlist = new ArrayList<>();
List list = Collections.synchronizedList(Oldlist); - List list = new CopyOnWriteArrayList<>();
6.2 Set线程不安全
public static void main(String[] args) {
Set<String> set = new HashSet<>();
for (int i = 0; i < 30; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
结果:
...
java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1429)
at java.util.HashMap$KeyIterator.next(HashMap.java:1453)
at java.util.AbstractCollection.toString(AbstractCollection.java:461)
at java.lang.String.valueOf(String.java:2994)
at java.io.PrintStream.println(PrintStream.java:821)
at com.sofia.unsafe.unsafeSet.lambda$main$0(unsafeSet.java:22)
at java.lang.Thread.run(Thread.java:745)
java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1429)
at java.util.HashMap$KeyIterator.next(HashMap.java:1453)
at java.util.AbstractCollection.toString(AbstractCollection.java:461)
at java.lang.String.valueOf(String.java:2994)
at java.io.PrintStream.println(PrintStream.java:821)
at com.sofia.unsafe.unsafeSet.lambda$main$0(unsafeSet.java:22)
at java.lang.Thread.run(Thread.java:745)
...
解决思路:
- Set set = new HashSet<>();
Set set1 = Collections.synchronizedSet(set); - Set set = new CopyOnWriteArraySet<>();
HashSet的底层原理:
public HashSet() {
map = new HashMap<>();
}
没错,HashSet的底层就是HashMap
6.3 Map线程不安全
public static void main(String[] args) {
HashMap<String, String> map = new HashMap<>();
for (int i = 0; i < 30; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
执行结果:
...
java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1429)
at java.util.HashMap$EntryIterator.next(HashMap.java:1463)
at java.util.HashMap$EntryIterator.next(HashMap.java:1461)
at java.util.AbstractMap.toString(AbstractMap.java:531)
at java.lang.String.valueOf(String.java:2994)
at java.io.PrintStream.println(PrintStream.java:821)
at com.sofia.unsafe.unsafeMap.lambda$main$0(unsafeMap.java:20)
at java.lang.Thread.run(Thread.java:745)
java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1429)
at java.util.HashMap$EntryIterator.next(HashMap.java:1463)
at java.util.HashMap$EntryIterator.next(HashMap.java:1461)
at java.util.AbstractMap.toString(AbstractMap.java:531)
at java.lang.String.valueOf(String.java:2994)
at java.io.PrintStream.println(PrintStream.java:821)
at com.sofia.unsafe.unsafeMap.lambda$main$0(unsafeMap.java:20)
at java.lang.Thread.run(Thread.java:745)
...
改进方法:
- Map<String, String> map = new ConcurrentHashMap<String, String>();
7.辅助类
7.1 CountDownLatch
简介:相当于一个倒计时器,等待一定数量的任务执行完毕后再执行目标任务;
public static void main(String[] args) throws InterruptedException {
CountDownLatch downLatch = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"执行完毕~");
downLatch.countDown();
},String.valueOf(i)).start();
}
downLatch.await();
System.out.println(Thread.currentThread().getName()+"目标任务执行~");
}
结果:
0执行完毕~
4执行完毕~
3执行完毕~
2执行完毕~
1执行完毕~
5执行完毕~
main目标任务执行~
7.2 CyclicBarrier
相当于一个加法计数器
public static void main(String[] args) {
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()+"执行第"+temp+"个任务");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
执行结果:
Thread-0执行第0个任务
Thread-3执行第3个任务
Thread-4执行第4个任务
Thread-6执行第6个任务
Thread-1执行第1个任务
Thread-5执行第5个任务
Thread-2执行第2个任务
满足条件了,可以执行~
如果没有到达指定值,目标任务不会被执行。