JUC自学笔记01_real(概述、Lock接口、线程间通信、集合线程安全、多线程锁)
一、JUC概述
(一)JUC简介
在java中,JUC就是Java.util.concument工具包的简称,这是一个小狐狸线程的工具包,JSK1.5开始出现。
进程:指的是系统正在运行的一个应用程序,程序一旦运行就是进程,进程是资源分配的最小单元
线程:系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元执行流,线程就是程序执行的最小单元
(二)线程的状态
public enum State {
NEW, //(新建)
RUNNABLE, //(准备就绪)
BLOCKED, //(阻塞)
WAITING, //(不见不散)
TIMED_WAITING, //过期不候
TERMINATED; //线程终结
}
(三)wait和sleep方法的区别
1、sleep是Thread的静态方法,wait是Object的方法,任何对象实例都能调用
2、sleep不会释放锁,也不需要占用锁,wait会释放锁,但调用它的前提是当前线程占有锁(即代码要在synchronized中)
3、他们都可以被interruped方法中断
(四)并发与并行
并发:同一时刻多个线程在访问一个资源,多个线程对一个点(春运抢票,电商秒杀)
并行:多项工作一起执行,之后再汇总
(五)管程
就是一种Monitor监视器,相当于所说的锁
是一种同步机制,保证同意时间,只有一个线程能够访问被保护的数据或者代码
(六)用户线程和守护线程
用户 (User) 线程: 运行在前台,执行具体的任务,如程序的主线程、连接网络的子线程等都是用户线程。
守护 (Daemon) 线程: 运行在后台,为其他前台线程服务。也可以说守护线程是 JVM 中非守护线程的 “佣人”。一旦所有用户线程都结束运行,守护线程会随 JVM 一起结束工作.
举例:
main 函数所在的线程就是一个用户线程,main 函数启动的同时在 JVM 内部同时还启动了好多守护线程,比如垃圾回收线程。
二者区别:
用户线程:当存在任何一个用户线程未离开,JVM是不会离开的。
守护线程:如果只剩下守护线程未离开,JVM是可以离开的。
二、Lock接口
(一)Synchronized关键字
1、多线程编程步骤:
(1)创建资源类,创建属性和操作方法
(2)创建多线程调用资源类的方法
2、卖票例子
//1、创建资源类,定义属性和操作方法
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 = new Ticket();
//创建三个线程
//线程1
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 40; i++){
ticket.sale();
}
}
}, "AA").start();
//线程2
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 40; i++){
ticket.sale();
}
}
}, "BB").start();
//线程3
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 40; i++){
ticket.sale();
}
}
}, "CC").start();
}
}
输出结果:
AA:卖出:30剩下29
AA:卖出:29剩下28
AA:卖出:28剩下27
AA:卖出:27剩下26
AA:卖出:26剩下25
AA:卖出:25剩下24
AA:卖出:24剩下23
AA:卖出:23剩下22
AA:卖出:22剩下21
AA:卖出:21剩下20
AA:卖出:20剩下19
AA:卖出:19剩下18
AA:卖出:18剩下17
AA:卖出:17剩下16
AA:卖出:16剩下15
AA:卖出:15剩下14
AA:卖出:14剩下13
AA:卖出:13剩下12
AA:卖出:12剩下11
AA:卖出:11剩下10
AA:卖出:10剩下9
AA:卖出:9剩下8
AA:卖出:8剩下7
AA:卖出:7剩下6
AA:卖出:6剩下5
AA:卖出:5剩下4
AA:卖出:4剩下3
AA:卖出:3剩下2
AA:卖出:2剩下1
AA:卖出:1剩下0
Process finished with exit code 0
(二)Lock接口
1、Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性,Lock是一个类,通过这个类可以实现同步访问。
Lock和synchronized有一个非常大的不同,就是synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程对锁的占用。而Lock需要用户去手动释放锁,如果没有主动释放锁,就有可能会导致死锁现象。
用Lock完成,卖票例子
import java.util.concurrent.locks.ReentrantLock;
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);
}
}catch (Exception ex){
ex.printStackTrace();
}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();
}
}
三、线程间进行通信
小例子:让两个线程实现+1和-1的交替过程:
class Share{
//初始值
private int number = 0;
//+1的方法
public synchronized void incr() throws InterruptedException {
if(number != 0){
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"::"+number);
this.notify();
}
//-1的方法
public synchronized void decr() throws InterruptedException {
if(number != 1){
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"::"+number);
this.notify();
}
}
public class ThreadDemo1 {
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();
}
}
},"AA").start();
new Thread(()->{
for (int i = 0; i <= 10; i++) {
try {
share.decr();
}catch (InterruptedException e){
e.printStackTrace();
}
}
},"BB").start();
}
}
输出结果:
AA::1
BB::0
AA::1
BB::0
AA::1
BB::0
AA::1
BB::0
AA::1
BB::0
AA::1
BB::0
AA::1
BB::0
AA::1
BB::0
AA::1
BB::0
AA::1
BB::0
AA::1
BB::0
Process finished with exit code 0
利用Lock来实现+1和-1的交替实现
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Share{
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
//+1
public void incr(){
lock.lock();
try{
while (number != 0){
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"::"+number);
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
//-1
public void decr(){
lock.lock();
try{
while (number != 1){
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"::"+number);
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
Share share = new Share();
new Thread(()->{
for(int i = 0; i <= 10; i++){
try {
share.incr();
}catch (Exception e){
e.printStackTrace();
}
}
}, "AA").start();
new Thread(()->{
for(int i = 0; i <= 10; i++){
try {
share.decr();
}catch (Exception e){
e.printStackTrace();
}
}
}, "BB").start();
new Thread(()->{
for(int i = 0; i <= 10; i++){
try {
share.incr();
}catch (Exception e){
e.printStackTrace();
}
}
}, "CC").start();
new Thread(()->{
for(int i = 0; i <= 10; i++){
try {
share.decr();
}catch (Exception e){
e.printStackTrace();
}
}
}, "DD").start();
}
}
四、线程间的定制化通信
建立三个线程,三个线程必须按照顺序执行,AA打印5次,之后通知BB线程打印10次,之后通知CC线程打印15次。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class PrintClass{
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(){
lock.lock();
try{
while (flag != 1){
c1.await();
}
for(int i = 1; i <= 5; i++){
System.out.println(Thread.currentThread().getName()+"::"+i+"::5");
}
flag = 2;
c2.signal(); //注意此处线程的通知
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void print10(){
lock.lock();
try{
while (flag != 2){
c2.await();
}
for(int i = 1; i <= 10; i++){
System.out.println(Thread.currentThread().getName()+"::"+i+"::10");
}
flag = 3;
c3.signal(); //注意此处线程的通知
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void print15(){
lock.lock();
try{
while (flag != 3){
c3.await();
}
for(int i = 1; i <= 15; i++){
System.out.println(Thread.currentThread().getName()+"::"+i+"::15");
}
flag = 1;
c1.signal(); //注意此处线程的通知
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public class LockDemo3 {
public static void main(String[] args) {
PrintClass print = new PrintClass();
new Thread(()->{
try{
print.print5();
}catch (Exception e){
e.printStackTrace();
}
}, "AA").start();
new Thread(()->{
try{
print.print10();
}catch (Exception e){
e.printStackTrace();
}
}, "BB").start();
new Thread(()->{
try{
print.print15();
}catch (Exception e){
e.printStackTrace();
}
}, "CC").start();
}
}
五、集合的线程安全
(一)线程不安全的解决方案一:Vector
import java.util.List;
import java.util.UUID;
import java.util.Vector;
public class ThreadUnsafe {
public static void main(String[] args) {
List<String> list = new Vector<>(); //注意使用Vector
for(int i = 0; i <= 30; i++){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
使用Vector的原因:在vector的源码中,add()等方法前面的修饰词中有synchronized关键字
(二)线程不安全的解决方案二:Collections
import java.util.*;
public class ThreadUnsafe {
public static void main(String[] args) {
List<String> list = Collections.synchronizedList(new ArrayList<>()); //===================
for(int i = 0; i <= 30; i++){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
(三)(最常用)线程不安全的解决方案三:JUC方案——CopyOnWriteArrayList
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
public class ThreadUnsafe {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>(); //=========================
for(int i = 0; i <= 30; i++){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
CopyOnWriteArrayList类:写时复制技术
(四)HashSet线程不安全的解决方案
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;
public class ThreadUnsafe {
public static void main(String[] args) {
//Set<String> set = new HashSet<>();
Set<String> set = new CopyOnWriteArraySet<>();
for(int i = 0; i < 30; i++){
new Thread(()->{
set.add(UUID.randomUUID().toString());
System.out.println(set);
}, String.valueOf(i)).start();
}
}
}
(五)HashMap线程不安全的解决方案
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
public class ThreadUnsafe {
public static void main(String[] args) {
Map<String, String> map = new ConcurrentHashMap<>();
for(int i = 0; i < 30; i++){
String key = String.valueOf(i);
new Thread(()->{
map.put(key, UUID.randomUUID().toString().substring(0, 8));
System.out.println(map);
}, String.valueOf(i)).start();
}
}
}
六、多线程锁
(一)多线程锁的几种情况
synchronized关键字实现同步的基础:Java中的每一个对象都可以作为锁,具体表现形式:
对于普通的同步方法,在方法修饰词上加synchronized关键字就是对实例对象进行上锁
对于静态同步方法,在静态同步方法上加synchronized关键字就是对类的Class对象上锁
对于同步方法块,在同步方法块上加synchronized关键字就是对synchronized括号里面的配置的对象进行上锁
(二)公平锁与非公平锁
在实例化Lock对象的时候,使用的是new ReentrantLock()类实例化对象,其中当实例化对象的时候用的是无参构造器的时候默认的是生成非公平锁,如果想实例化一个公平锁,组要在有参构造中添加true参数。
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
(三)可重入锁
1、synchronized关键字的可重入锁
public class ThreadUnsafe {
public static void main(String[] args) {
Object obj = new Object();
new Thread(()->{
synchronized (obj){
System.out.println(Thread.currentThread().getName()+"外层");
synchronized (obj){
System.out.println(Thread.currentThread().getName()+"中层");
synchronized (obj){
System.out.println(Thread.currentThread().getName()+"内层");
}
}
}
}, "t1").start();
}
}
2、lock的可重入锁(注意:必须有上锁就要有解锁)
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadUnsafe {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
new Thread(()->{
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+"外层");
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+"外层");
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}, "t2").start();
}
}
七、死锁
(一)死锁的概念
1、什么是死锁: 两个或者两个以上的进程在执行的过程中,因为争夺资源而造成互相等待的现象,如果没有外力干涉,他们无法再执行下去,则这种现象称为死锁。
2、死锁的产生原因:
(1)系统资源不足
(2)进程运行推进顺序不合适
(3)资源分配不当
3、快速写一个死锁代码:
public class ThreadUnsafe {
public static void main(String[] args) {
Object a = new Object();
Object b = new Object();
new Thread(()->{
synchronized (a){
System.out.println(Thread.currentThread().getName().toString()+"持有锁a,想要获取锁b");
try {
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
synchronized (b){
System.out.println(Thread.currentThread().getName().toString()+"获取锁b");
}
}
}, "AA").start();
new Thread(()->{
synchronized (b){
System.out.println(Thread.currentThread().getName().toString()+"持有锁b,想要获取锁a");
try {
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
synchronized (a){
System.out.println(Thread.currentThread().getName().toString()+"获取锁a");
}
}
}, "BB").start();
}
}
4、验证是否当前线程阻塞为死锁:
(1)首先再java源安装文件夹找到jpx文件,进行该文件的环境配置;
(2)再IDEA中打开命令行界面,也就是Terminal界面;
(3)输入jps -l命令,找到当前程序的进程号;
(4)再输入jstack -进程号,回车键之后就会显示当前线程是否含有死锁。