1、JUC概述
1.1 什么是JUC
在Java中,线程部分是一个重点,本篇文章说的JUC是关于线程的。JUC就是java.util.concurrent
工具包的简称。这是一个处理线程的工具包,JDK1.5开始出现的
1.2 线程和进程的概念
-
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。
-
线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
-
总结:
-
进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程——资源分配的最小单位
-
线程:系统分配处理器时间资源的基本单元,或者说进程内独立执行的一个单元执行流。线程——程序执行的最小单位
-
1.3 线程的状态
线程状态枚举类(Thread.State): NEW(新建)、RUNNABLE(准备就绪)、BLOCKED(阻塞)、WAITING(不见不散)、TIMED_WAITING(过时不候)、TERMINATED(终结)
wait和sleep的区别
-
sleep是Thread的静态方法,wait是Object的方法,任何对象实例都能调用
-
sleep不会释放锁,它也不需要占用锁。wait会释放锁,但调用它的前提是当前线程占有锁(即代码要在synchronized中)
-
它们都可以被interrupted方法中断
并发与并行
-
并发:多个线程在访问同一个资源(线程频繁切换),多个线程对一个点
-
例子:春运抢票、电商秒杀,一个医生给多个病人轮流看病
-
-
并行:多项工作一起执行(同时),之后再汇总
-
多个医生同时给病人看病
-
管程: Monitor监视器 就是我们所说的锁 是一种同步机制,保证同一个时间,只有一个线程访问被保护数据或者代码。JVM同步基于进入和退出,使用管程对象实现的。
用户线程和守护线程
-
用户线程:自定义线程 主线程结束了,用户线程还在运行,jvm存活
-
守护线程:比如垃圾回收 没有用户线程了,都是守护线程,jvm结束
public class Main {
public static void main(String[] args){
Thread aa = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "::" + Thread.currentThread().isDaemon());
while(true){
}
}, "aa");
//设置守护线程(注意要在start()方法之前设置)
aa.setDaemon(true);
aa.start();
System.out.println(Thread.currentThread().getName() + "over");
}
}
//第一次输出结果 main over aa::false 表示是用户线程
//设置守护线程之后 main over
2、Lock接口
2.1 复习Synchronized
synchronized 是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
-
修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象
-
修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法作用的对象是调用这个方法的对象
-
修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象
-
修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主要的对象是这个类的所有对象
2.2 什么是Lock接口
Lock实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的Condition对象
所有已知实现类:ReentrantLock、ReentrantReadWriteLock.ReadLock、ReentrantReadWriteLock.WriteLock
Lock与Synchronized区别:
-
Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现
-
synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock()时需要在finally块中释放锁
-
Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断
-
通过Lock可以知道有没有成功获取锁,而synchronized却无法办到
-
Lock可以提高多个线程进行读操作的效率
-
Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;
-
Lock则必须要用户去手动释放锁,如果没有手动释放锁,就有可能出现死锁现象
2.3 创建线程的多种方式
-
继承Thread类创建线程
-
实现Runnable接口创建线程
-
使用Callable和Future创建线程
-
使用线程池例如用Executor框架
多线程编程步骤:
-
第一步:创建资源类,在资源类创建属性和操作方法(例子:空调)
-
第二步:创建多个线程,调用资源类的操作方法
2.4 使用Lock实现卖票例子
//第一步 创建资源类,定义属性和操作方法
class Ticket{
//票数
private int number = 30;
//操作方法:卖票
public synchronized void sale(){
//判断:是否有票
if(number > 0){
System.out.println(Thread.currentThread().getName()+":卖出:" +(number--)+"剩下:"+number);
}
}
}
public class SaleTicket {
//第二步 创建多个线程,调用资源类的操作方法
public static void main(String[] args){
//创建Ticket对象
Ticket ticket = new Ticket();
//创建三个线程
new Thread(new Runnable(){
@Override
public void run(){
//调用卖票方法
for(int i = 0; i < 40; i++){
ticket.sale();
}
}
},"AA").start();
new Thread(new Runnable(){
@Override
public void run(){
//调用卖票方法
for(int i = 0; i < 40; i++){
ticket.sale();
}
}
},"BB").start();
new Thread(new Runnable(){
@Override
public void run(){
//调用卖票方法
for(int i = 0; i < 40; i++){
ticket.sale();
}
}
},"CC").start();
}
}
//第一步 创建资源类,定义属性和操作方法
class LTicket{
//票数量
private int number = 30;
//创建可重入锁
private final ReentrantLock lock = new ReentrantLock();
//卖票方法
public void sale(){
//上锁
lock.lock();
try{
//判断是否有票
if(num > 0){
System.out.println(Thread.currentThread().getName()+":卖出:" +(number--)+"剩余:"+number);
}
}finally{
//解锁
lock.unlock();
}
}
}
public class LSaleTicket(){
//第二步 创建多个线程,调用资源类的操作方法
//创建三个线程
public static void main(String[] args){
LTicket ticket = new LTicket();
new Thread(() -> {
for(int i = 0; i < 40; i++){
ticket.sale();
}
}, "AA").start();
new Thread(() -> {
for(int i = 0; i < 40; i++){
ticket.sale();
}
}, "BB").start();
new Thread(() -> {
for(int i = 0; i < 40; i++){
ticket.sale();
}
}, "CC").start();
}
}
3、线程间通信
//线程间通信 —— synchronized实现
//第一步 创建资源类,定义属性和操作方法
class Share{
//初始值
private int number = 0;
//+1的方法
public synchronized void incr() throws InterruptedException{
//第二步 判断 干活 通知
while(number != 0){//判断num是否是0,如果不是,等待
this.wait();//在哪里睡,就会在哪里醒
//前面判断如果用if就会有虚假唤醒问题
}
//如果number值是0,就+1操作
number++;
System.out.println(Thread.currentThread().getName()+"::"+number);
//通知其他线程
this.notifyAll();
}
//-1的方法
public synchronized void decr(){
while(number != 1){//判断
this.wait();
}
number--;//干活
System.out.println(Thread.currentThread().getName()+"::"+number);
//通知其他线程
this.notifyAll();
}
}
//第三步 创建多个线程,调用资源类的操作方法
public class ThreadDemo1{
public static void main(String[] args){
Share share = new Share();
//创建线程
new Thread(() -> {
for(int i = 1; i <= 10; i++){
try{
share.incr();//调用+1方法
}catch(InterruptedException e){
e.printStackTrace();
}
}
}, "AA").start();
new Thread(() -> {
for(int i = 1; i <= 10; i++){
try{
share.decr();//调用-1方法
}catch(InterruptedException e){
e.printStackTrace();
}
}
}, "BB").start();
new Thread(() -> {
for(int i = 1; i <= 10; i++){
try{
share.incr();//调用+1方法
}catch(InterruptedException e){
e.printStackTrace();
}
}
}, "CC").start();
new Thread(() -> {
for(int i = 1; i <= 10; i++){
try{
share.decr();//调用-1方法
}catch(InterruptedException e){
e.printStackTrace();
}
}
}, "DD").start();
}
}
//线程间通信 —— lock实现
//第一步 创建资源类,定义属性和操作方法
class Share {
private int number = 0;
//创建Lock
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
//+1
public void incr() throws InterruptedException{
//上锁
lock.lock();
try{
//判断
while(num != 0){
condition.await();
}
number++;//干活
System.out.println(Thread.currentThread().getName()+"::"+number);
//通知
condition.signalAll();
}finally{
//解锁
lock.unlock();
}
}
//-1
public void decr(){
lock.lock();
try{
while(number != 1){
aondition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"::"+number);
condition.signalAll();
}finally{
lock.unlock();
}
}
}
public class ThreadDenm2{
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();
new Thread(()->{
for(int i = 1; i < 10; i++){
try{
share.incr();
}catch(InterruptedException e){
e.printStackTrace();
}
}
}, "CC").start();
new Thread(()->{
for(int i = 1; i < 10; i++){
try{
share.decr();
}catch(InterruptedException e){
e.printStackTrace();
}
}
}, "DD").start();
}
}
4、线程间定制化通信
//线程间定制化通信 —— 通过标志位
//第一步 创建资源类
class ShareResource{
//定义标志位
private int flag = 1;//1 AA 2 BB 3 CC
//创建Lock锁
private lock lock = new ReentrantLock();
//创建三个condition
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
//打印5次,参数第几轮
public void print5(int loop){
//上锁
lock.lock();
try{
//判断
while(flag != 1){
//等待
c1.await();
}
for(int i = 1; i <= 5; i++){//干活
System.out.println(Thread.currentThread().getName()+"::"+" : 轮数 :"+loop)
}
flag = 2;//修改标志位2
c2.signal();//通知
}finally{
//释放锁
lock.unlock();
}
}
//打印10次,参数第几轮
public void print15(int loop){
//上锁
lock.lock();
try{
//判断
while(flag != 2){
//等待
c2.await();
}
for(int i = 1; i <= 5; i++){//干活
System.out.println(Thread.currentThread().getName()+"::"+" : 轮数 :"+loop)
}
flag = 3;//修改标志位2
c2.signal();//通知
}finally{
//释放锁
lock.unlock();
}
}
//打印15次,参数第几轮
public void print15(int loop){
//上锁
lock.lock();
try{
//判断
while(flag != 3){
//等待
c3.await();
}
for(int i = 1; i <= 5; i++){//干活
System.out.println(Thread.currentThread().getName()+"::"+" : 轮数 :"+loop)
}
flag = 1;//修改标志位2
c3.signal();//通知
}finally{
//释放锁
lock.unlock();
}
}
}
public class ThreadDemo3{
ShareResource shareResource = new ShareResource();
new Thread(() -> {
for(int i = 1; i <= 10; i++){
try{
shareRource.print5(i);
} catch (InterruptedException e){
e.printStackTrace();
}
}
},"AA").start();
new Thread(() -> {
for(int i = 1; i <= 10; i++){
try{
shareRource.print10(i);
} catch (InterruptedException e){
e.printStackTrace();
}
}
},"BB").start();
new Thread(() -> {
for(int i = 1; i <= 10; i++){
try{
shareRource.print15(i);
} catch (InterruptedException e){
e.printStackTrace();
}
}
},"CC").start();
}
5、集合的线程安全
5.1 集合线程不安全演示
//list集合线程不安全
public class ThreadDemo4{
public static void main(String[] args){
//创建ArrayList集合
List<String> list = new ArrayList<>();
for(int i = 1; i <= 30; i++){
new Thread(() -> {
//向集合添加内容
list.add(UUID.randomUUID().toString().substring(0, 8));
//从集合中获取内容
System.out.print(list);
}, String.valueOf(i)).start();
}
}
}
解决方案-Vector
将List<String> list = new ArrayList<>();
改成
List<String> list = new Vector();即可 (很古老的方案——不常用)
解决方案-Collections
List<String> list = Collections.synchronizedList(new ArrayList());
解决方案-CopyOnWriteArrayList ——常用
List<String> list = new CopyOnWriteArrayList<>();
写时复制技术,读的时候支持并发读,写是独立写,写之前先复制内容,在新的地址写入新的内容之后合并,后面再读取新的内容 —— 例如签到
首先将当前容器复制一份,然后在新副本上执行写操作,结束之后再将原容器的引用指向新容器
5.2 HashSet线程不安全
public class ThreadDemo4{
public static void main(String[] args){
//创建HashSet集合
//Set<String> set = new HashSet<>();
//解决方案
Set<String> set = new CopyOnWriteArraySet<>();
for(int i = 1; i <= 30; i++){
new Thread(() -> {
//向集合添加内容
set.add(UUID.randomUUID().toString().substring(0, 8));
//从集合中获取内容
System.out.print(set);
}, String.valueOf(i)).start();
}
}
}
5.3 HashMap线程不安全
public class ThreadDemo4{
public static void main(String[] args){
//Map<String, String> map = new HashMap<>();
Map<String, String> map = new ConcurrentHashMap<>();
for(int i = 1; i <= 30; i++){
new Thread(() -> {
//向集合添加内容
map.put(key,UUID.randomUUID().toString().substring(0, 8));
//从集合中获取内容
System.out.print(map);
}, String.valueOf(i)).start();
}
}
}