JUC简介
JUC是java.util.concurrent工具包的简称,这是一个处理线程的工具包,JDK1.5开始出现的。
线程与进程
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配的基本单位,是操作系统结构的基础。在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。
线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
总得来说。进程,指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程是资源分配的最小单位。线程,系统分配处理器时间资源的基本单元,或者说进程之内独立执行的个单元执行流。线程程序执行的最小单位。
线程的状态
线程的状态包括NEW(新建)、RUNNABLE(准备就绪)、BLOCKED(阻塞)、WAITING(不见不散)、TIMED_WATING(过时不候)、TERMINATED(终结)。
wait/sleep
sleep是Thread 的静态方法,wait是Object的方法,任意对象实例都能调用。sleep不会释放锁,它也不需要占用锁。wait会释放锁,但调用它的前提是当前线程占有锁(即代码要在 synchronized中)。
它们都可以被 interrupted方法中断。
并发和并行
并发:同一时刻多个线程在访问同一个资源,多个线程对一个点。例子:春运抢票 电商秒杀…..
并行:多项工作一起执行,之后再汇总。例子:泡方便面,电水壶烧水,一边撕调料倒入桶中。
管程
是一种同步机制,保证同一个时间,只有一个线程访问被保护数据或者代码。
用户线程和守护线程
用户线程:自定义线程。主线程结束了,用户线程还在运行,JVM存活。
守护线程:比如垃圾回收。没有用户线程了,都是守护线程,JVM结束。
Lock接口
synchronized关键字
synchronized 是 Java 中的关键字,是一种同步锁。它修饰的对象有以下几种:
- 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号
{}括起来的代码,作用的对象是调用这个代码块的对象; - 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
- 虽然可以使用
synchronized来定义方法,但synchronized并不属于方法定义的一部分,因此synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同方法,因此,子类的方法也就相当于同步了。
- 虽然可以使用
- 修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
- 修饰一个类,其作用的范围是
synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
下面用三个线程模拟卖票的例子
public class Ticket {private int number = 30;public synchronized void sale(){if (number > 0){System.out.println(Thread.currentThread().getName()+"卖出票,"+"剩下:"+(--number));}}}public class SaleTicket {public static void main(String[] args) {Ticket ticket = new Ticket();new Thread(new Runnable() {@Overridepublic void run() {for (int i=0;i<30;i++){ticket.sale();}}}, "A").start();new Thread(new Runnable() {@Overridepublic void run() {for (int i=0;i<30;i++){ticket.sale();}}}, "B").start();new Thread(new Runnable() {@Overridepublic void run() {for (int i=0;i<30;i++){ticket.sale();}}}, "C").start();}}
创建线程的多种方式
- 继承
Thread类 - 实现
Runnable接口 - 使用
Callable接口 - 使用线程池
Lock接口
Lock 实现提供了比使用synchronized 方法和语句可获得的更广泛的锁定操作。它允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象。Lock 接口有实现类ReentrantLock、ReentrantReadWriteLock.ReadLock、ReentrantReadWriteLock.WriteLock。
Lock 与Synchronized 区别:
Lock不是Java 语言内置的,synchronized是 Java 语言的关键字,因此是内置特性。Lock 是一个类,通过这个类可以实现同步访问;synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在 finally 块中释放锁;Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;- 通过
Lock可以知道有没有成功获取锁,而synchronized却无法办到。 Lock可以提高多个线程进行读操作的效率。
在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时 Lock 的性能要远远优于synchronized。
下面用Lock接口实现刚才卖票的例子(可重入锁)
public class TicketLock {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));}}finally {//解锁lock.unlock();}}}public class SaleTicket {public static void main(String[] args) {TicketLock ticket = new TicketLock();new Thread(() -> {for (int i=0;i<30;i++){ticket.sale();}}, "A").start();new Thread(() -> {for (int i=0;i<30;i++){ticket.sale();}}, "B").start();new Thread(() -> {for (int i=0;i<30;i++){ticket.sale();}}, "C").start();}}
线程间通信
以一个加减1为例,先用synchronized 来实现
public class Share {private int number = 0;public synchronized void incr() throws InterruptedException {while (number != 0){wait();}System.out.println("值增加:"+(number++)+" ==> "+number);notifyAll();}public synchronized void decr() throws InterruptedException {while (number <= 0){wait();}System.out.println("值减少:"+(number--)+" ==> "+number);notifyAll();}}public class ShareMain {public static void main(String[] args) {Share share = new Share();new Thread(()->{for (int i=0;i<10;i++){try {share.incr();} catch (InterruptedException e) {e.printStackTrace();}}}, "A").start();new Thread(()->{for (int i=0;i<10;i++){try {share.decr();} catch (InterruptedException e) {e.printStackTrace();}}}, "B").start();}}
结果是这种交替出现的样子
多线程编程步骤
- 创建资源类,在资源类创建属性和操作方法
- 在资源类操作方法
- 判断
- 干活
- 通知
- 创建多个线程,调用资源类的操作方法
- 防止虚假唤醒问题
线程间定制化通信
实现下面的场景,A打印5次,B打印10次,C打印15次,循环往复进行10轮。
public class OutLock {/*** 定义标志位*/private int flag = 1;private Lock lock = new ReentrantLock();private Condition c1 = lock.newCondition();private Condition c2 = lock.newCondition();private Condition c3 = lock.newCondition();/*** 打印A*/public void outA() throws InterruptedException {lock.lock();try {while (flag != 1){//等待c1.await();}for (int i=0; i<5;i++){System.out.println(Thread.currentThread().getName());}flag = 2;//通知c2.signal();}finally {lock.unlock();}}/*** 打印B*/public void outB() throws InterruptedException {lock.lock();try {while (flag != 2){//等待c2.await();}for (int i=0; i<10;i++){System.out.println(Thread.currentThread().getName());}flag = 3;//通知c3.signal();}finally {lock.unlock();}}/*** 打印C*/public void outC() throws InterruptedException {lock.lock();try {while (flag != 3){//等待c3.await();}for (int i=0; i<15;i++){System.out.println(Thread.currentThread().getName());}flag = 1;//通知c1.signal();}finally {lock.unlock();}}}public class OutLockMain {public static void main(String[] args) {OutLock outLock = new OutLock();new Thread(() -> {for (int i=0; i<10;i++){try {outLock.outA();} catch (InterruptedException e) {e.printStackTrace();}}}, "A").start();new Thread(() -> {for (int i=0; i<10;i++){try {outLock.outB();} catch (InterruptedException e) {e.printStackTrace();}}}, "B").start();new Thread(() -> {for (int i=0; i<10;i++){try {outLock.outC();} catch (InterruptedException e) {e.printStackTrace();}}}, "C").start();}}
集合的线程安全
先演示一个集合的线程不安全例子
ArrayList<String> list = new ArrayList<>();for (int i=0;i<30;i++){new Thread(() -> {list.add(UUID.randomUUID().toString().substring(0,5));System.out.println(list);},String.valueOf(i)).start();}
会报出如下异常<br /><br />即`java.util.ConcurrentModificationException`,并发修改异常。<br />出现上述问题是因为`ArrayList`的`add()`方法没有同步,它是线程不安全的
Vector
Vector是线程安全的
List<String> list = new Vector<>();for (int i=0;i<30;i++){new Thread(() -> {list.add(UUID.randomUUID().toString().substring(0,5));System.out.println(list);},String.valueOf(i)).start();}
但是使用Vector效率较低
Collections
Collections工具类中有_synchronizedList()_方法,可以返回一个同步的List
List<String> list = Collections.synchronizedList(new ArrayList<>());for (int i=0;i<30;i++){new Thread(() -> {list.add(UUID.randomUUID().toString().substring(0,5));System.out.println(list);},String.valueOf(i)).start();}
CopyOnWriteArrayList
使用JUC提供的类CopyOnWriteArrayList,写时复制技术
List<String> list = new CopyOnWriteArrayList<>();for (int i=0;i<30;i++){new Thread(() -> {list.add(UUID.randomUUID().toString().substring(0,5));System.out.println(list);},String.valueOf(i)).start();}
`CopyOnWriteArrayList`采用并发读、独立写,最后再合并<br />
