目录
[TOC]
JUC概述
什么是JUC
JUC实际上就是我们对于jdk中java.util.concurrent
工具包的简称。这个包下都是Java处理线程相关的类,自jdk1.5后出现。
线程和进程概念
进程
例如运行电脑管家,就开启了一个进程
线程
案例
在电脑管家里面的运行空间清理,就开启了一个线程。
如果同时开启空间清理和杀毒,则是多线程
如果同时开启多个功能,只能等上一个执行完成才能执行下一个,则为单线程
wati和sleep
- sleep是Thread的静态方法,wait是Object的方法,任何对象实例都能调用。
- sleep不会释放锁,它也不需要占用锁。wait会释放锁,但调用它的前提是当前线程占有锁A(即代码要在synchronized中)。
- 它们都可以被interrupted方法中断。
并发和并行
串行模式
串行是一次只能取得一个任务,并执行这个任务。
并行模式
同时执行多个任务
并发模式
并发(concurrent)指的是多个程序可以同时运行的现象,更细化的是多进程可以同时运行或者多指令可以同时运行
并行和并发区别
并发:同一时刻多个线程在访问同一个资源,多个线程对一个点
并行:多项工作一起执行,之后再汇总
管程
所说锁
是一种同步机制,保证同一个时间,只有一个线程访问被保护数据或者代码
JVM同步基于进入和退出,使用管程对象实现的
用户线程和守护线程
用户线程:自定义线程。主线程结束了,用户线程还在运行,JVM存活
守护线程:运行在后台,是一种特殊的线程,比如垃圾回收。
当主线程结束后,用户线程还在运行,JVM存活
如果没有用户线程,都是守护线程,JVM结束
Lock接口
创建线程的多种方式
继承Thread
继承Thread类,重写run方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
class ThreadTest { public static void main(String[] args) { new ExtendThread().start(); } }
class ExtendThread extends Thread { @Override public void run() { System.out.println(Thread.currentThread().getName() + ": 继承Thread线程啦"); } }
|
实现Runnable接口
注:Thread其实也是实现了Runnable接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class ThreadTest { public static void main(String[] args) { new Thread(new ImplRunnable()).start(); } }
class ImplRunnable implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + ": 继承Thread线程啦"); } }
|
Runnable接口匿名内部类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| class SaleTicket { public static void main(String[] args) { Ticket ticket = new Ticket(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 30; i++) { ticket.sale(); } } }, "线程a").start();
new Thread(() -> { for (int i = 0; i < 100; i++) { ticket.sale(); } }, "线程b").start();
new Thread(ticket::sale, "线程c").start(); } }
class Ticket { private Integer num = 30;
public synchronized void sale() { if (num > 0) { System.out.println(Thread.currentThread().getName() + "卖出:" + (num--) + "剩下:" + num); } } }
|
实现Callable接口
Java5开始提供Callable接口,提供call方法作为线程的执行体,可以看成是Runnable接口的增强版本,增强点在于call()方法可以有返回值,并且可以抛出异常,由于Callable是新增的接口,不能作为Thread的target使用,所以Java5里提供了Future接口,该接口实现了Runnable,Future的实现类FutureTask类用来包装Callable对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| class ThreadTest { public static void main(String[] args) { ImplCallable implCallable = new ImplCallable();
FutureTask<String> futureTask = new FutureTask<>(implCallable);
new Thread(futureTask).start();
try { String resp = futureTask.get(); System.out.println("返回值为:" + resp); } catch (Exception e) { e.printStackTrace(); } } }
class ImplCallable implements Callable<String> { @Override public String call() throws Exception { return "返回值类型为:String类型"; } }
|
使用线程池
线程池具体使用放在:CompletableFuture中
Synchronized
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
- 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号
{}
括起来的代码,作用的对象是调用这个代码块的对象;
- 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
- 虽然可以使用synchronized来定义方法,但synchronized并不属于方法定义的一部分,因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。
- 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
- 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
售票案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public class SaleTicket { public static void main(String[] args) { Ticket ticket = new Ticket(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 30; i++) { ticket.sale(); } } }, "线程a").start();
new Thread(() -> { for (int i = 0; i < 100; i++) { ticket.sale(); } }, "线程b").start();
new Thread(ticket::sale, "线程c").start(); } }
class Ticket { private Integer num = 30;
public synchronized void sale() { if (num > 0) { System.out.println(Thread.currentThread().getName() + "卖出:" + (num--) + "剩下:" + num); } } }
|
synchronized实现同步的基础
synchronized实现同步的基础:Java中的每一个对象都可以作为锁。具体表现为以下3种形式。
- 对于普通同步方法,锁是当前实例对象。
- 对于静态同步方法,锁是当前类的class对象。
- 对于同步方法块,锁是synchonized括号里配置的对象
什么是Lock
Lock锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象。Lock提供了比synchronized更多的功能。
Lock与的Synchronized区别
Synchronized自动放锁,Lock手动解锁
- Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;
- Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
Lock接口
ReentrantLock
ReentrantLock,意思是“可重入锁”,关于可重入锁的概念将在后面讲述。ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| public class LSaleTicket { public static void main(String[] args) { LTicket lTicket = new LTicket(); new Thread(() -> { for (int i = 0; i < 30; i++) { lTicket.sale(); } }, "线程一").start(); new Thread(() -> { for (int i = 0; i < 30; i++) { lTicket.sale(); } }, "线程二").start(); new Thread(() -> { for (int i = 0; i < 30; i++) { lTicket.sale(); } }, "线程三").start(); } }
class LTicket { private int number = 30;
private final ReentrantLock lock = new ReentrantLock();
public void sale() { lock.lock(); try { if (number > 0) { System.out.println(Thread.currentThread().getName() + "卖出:" + (number--) + " 剩余:" + number); } } finally { lock.unlock(); } } }
|
ReadWriteLock
具体后续补充
Synchronized和Lock锁有以下几点不同
- Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
- synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
- Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
- 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
- Lock可以提高多个线程进行读操作的效率。
在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。
线程间通信
线程间通信的模型有两种:共享内存和消息传递,以下方式都是基本这两种模型来实现的。
场景—-两个线程,一个线程对当前数值加1,另一个线程对当前数值减1,要求用线程间通信
Synchronized方案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| public class ThreadCommunication { public static void main(String[] args) { Share share = new Share(); new Thread(() -> { for (int i = 1; i <= 10; i++) { try { share.incr(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "AA").start();
new Thread(() -> { for (int i = 1; i <= 10; i++) { try { share.decr(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "BB").start(); } }
class Share { private int number = 0;
public synchronized void incr() throws InterruptedException { while (number != 0) { this.wait(); } number++; System.out.println(Thread.currentThread().getName() + " :: " + number); this.notifyAll(); }
public synchronized void decr() throws InterruptedException { while (number != 1) { this.wait(); } number--; System.out.println(Thread.currentThread().getName() + " :: " + number); this.notifyAll(); } }
|
Lock方案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| class Share { private int number = 0;
private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition();
public void incr() throws InterruptedException { lock.lock(); try { while (number != 0) { condition.await(); } number++; System.out.println(Thread.currentThread().getName()+" :: "+number); condition.signalAll(); }finally { lock.unlock(); } }
public void decr() throws InterruptedException { lock.lock(); try { while(number != 1) { condition.await(); } number--; System.out.println(Thread.currentThread().getName()+" :: "+number); condition.signalAll(); }finally { lock.unlock(); } } }
public class ThreadDemo2 { public static void main(String[] args) { Share share = new Share(); new Thread(()->{ for (int i = 1; i <=10; i++) { try { share.incr(); } catch (InterruptedException e) { e.printStackTrace(); } } },"AA").start(); new Thread(()->{ for (int i = 1; i <=10; i++) { try { share.decr(); } catch (InterruptedException e) { e.printStackTrace(); } } },"BB").start(); } }
|
线程间定制化通信
实现:A线程打印5次A,B线程打印10次B,C线程打印15次C,按照此顺序循环10轮
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
| class ShareResource { private int flag = 1;
private Lock lock = new ReentrantLock();
private Condition c1 = lock.newCondition(); private Condition c2 = lock.newCondition(); private Condition c3 = lock.newCondition();
public void print5(int loop) throws InterruptedException { lock.lock(); try { while(flag != 1) { c1.await(); } for (int i = 1; i <=5; i++) { System.out.println(Thread.currentThread().getName()+" :: "+i+" :轮数:"+loop); } flag = 2; c2.signal(); }finally { lock.unlock(); } }
public void print10(int loop) throws InterruptedException { lock.lock(); try { while(flag != 2) { c2.await(); } for (int i = 1; i <=10; i++) { System.out.println(Thread.currentThread().getName()+" :: "+i+" :轮数:"+loop); } flag = 3; c3.signal(); }finally { lock.unlock(); } }
public void print15(int loop) throws InterruptedException { lock.lock(); try { while(flag != 3) { c3.await(); } for (int i = 1; i <=15; i++) { System.out.println(Thread.currentThread().getName()+" :: "+i+" :轮数:"+loop); } flag = 1; c1.signal(); }finally { lock.unlock(); } } }
public class ThreadDemo3 { public static void main(String[] args) { ShareResource shareResource = new ShareResource(); new Thread(()->{ for (int i = 1; i <=10; i++) { try { shareResource.print5(i); } catch (InterruptedException e) { e.printStackTrace(); } } },"AA").start();
new Thread(()->{ for (int i = 1; i <=10; i++) { try { shareResource.print10(i); } catch (InterruptedException e) { e.printStackTrace(); } } },"BB").start();
new Thread(()->{ for (int i = 1; i <=10; i++) { try { shareResource.print15(i); } catch (InterruptedException e) { e.printStackTrace(); } } },"CC").start(); } }
|
集合的线程安全
ArrayList
ArrayList集合线程不安全演示
程序会报错:java.util.ConcurrentModificationException
1 2 3 4 5 6 7 8 9 10 11
| public static void main(String[] args) { List<String> list = new ArrayList<>(); for (int i = 0; i < 10; i++) { new Thread(()->{ list.add(UUID.randomUUID().toString().substring(0,8)); System.out.println(list); },String.valueOf(i)).start(); } }
|
CopyOnWriteArrayList解决(⭐)
使用JUC中提供的CopyOnWriteArrayList
来解决ArrayList线程不安全问题
1 2 3 4 5 6 7 8 9 10 11 12
| public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>(); for (int i = 0; i < 50; i++) { new Thread(()->{ list.add(UUID.randomUUID().toString().substring(0,8)); System.out.println(list); },String.valueOf(i)).start(); } }
|
Vector解决
1 2 3 4 5 6 7 8 9 10 11 12
| public static void main(String[] args) { List<String> list = new Vector<>(); for (int i = 0; i < 50; i++) { new Thread(()->{ list.add(UUID.randomUUID().toString().substring(0,8)); System.out.println(list); },String.valueOf(i)).start(); } }
|
Collections解决
1 2 3 4 5 6 7 8 9 10 11 12
| public static void main(String[] args) { List<String> list = Collections.synchronizedList(new ArrayList<>()); for (int i = 0; i < 50; i++) { new Thread(()->{ list.add(UUID.randomUUID().toString().substring(0,8)); System.out.println(list); },String.valueOf(i)).start(); } }
|
HashSet
HashSet集合线程不安全演示
程序会报错:java.util.ConcurrentModificationException
1 2 3 4 5 6 7 8 9 10 11
| public static void main(String[] args) { Set<String> set = new HashSet<>(); for (int i = 0; i < 50; i++) { new Thread(()->{ set.add(UUID.randomUUID().toString().substring(0,8)); System.out.println(set); },String.valueOf(i)).start(); } }
|
CopyOnWriteArraySet解决(⭐)
1 2 3 4 5 6 7 8 9 10 11 12
| public static void main(String[] args) {
Set<String> set = new CopyOnWriteArraySet<>(); for (int i = 0; i < 50; i++) { new Thread(()->{ set.add(UUID.randomUUID().toString().substring(0,8)); System.out.println(set); },String.valueOf(i)).start(); } }
|
HashMap
HashMap集合线程不安全演示
程序会报错:java.util.ConcurrentModificationException
1 2 3 4 5 6 7 8 9 10 11 12
| public static void main(String[] args) { Map<String, String> map = new HashMap<>(); for (int i = 0; i < 50; i++) { new Thread(() -> { String uuid = UUID.randomUUID().toString().substring(0, 8); map.put(uuid, uuid); System.out.println(map); }, String.valueOf(i)).start(); } }
|
ConcurrentHashMap解决(⭐)
1 2 3 4 5 6 7 8 9 10 11 12 13
| public static void main(String[] args) { Map<String, String> map = new ConcurrentHashMap<>(); for (int i = 0; i < 50; i++) { new Thread(() -> { String uuid = UUID.randomUUID().toString().substring(0, 8); map.put(uuid, uuid); System.out.println(map); }, String.valueOf(i)).start(); } }
|
多线程锁
锁的八个问题演示
synchronized实现同步的基础:Java中的每一个对象都可以作为锁。具体表现为以下3种形式。
- 对于普通同步方法,锁是当前实例对象。
- 对于静态同步方法,锁是当前类的class对象。
- 对于同步方法块,锁是synchonized括号里配置的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
|
public class Phone {
public static synchronized void sendSMS() throws Exception { TimeUnit.SECONDS.sleep(4); System.out.println("------sendSMS"); }
public synchronized void sendEmail() throws Exception { System.out.println("------sendEmail"); }
public void getHello() { System.out.println("------getHello"); } }
|
公平锁和非公平锁
公平锁
每个线程平均执行,但是效率较低。如买票案例中,多个线程同时进行买票,使用公平锁,每个窗口平均把剩余的票卖出
非公平锁
可能一个线程直接把全部任务执行完成,其他线程可能出现线程饿死,但是效率较高。如买票案例中,多个线程同时进行买票,使用非公平锁,可能一个窗口就能直接把票全部卖出去
可重入锁
可重入锁也称为递归锁
死锁
Callable$Future接口