线程和进程的基本概念
持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第14天,点击查看活动详情
什么是JUC
在java中,线程部分是一个重点,本片说的JUC也是关于现成的,juc就是java.util.concurrent工具包的简称,这是个处理线程的工具包。
什么是进程,线程
进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程是资源分配的最小的单位
线程:系统分配处理器时间资源的基本单位。或者说进程之内独立执行的一个单元执行流。线程——程序执行的最小单位
简单理解,一个360软件就是一个进程,你在里面做的很多操作就是一个个线程。
wait/sleep的区别
- sleep是Thread的静态方法,wait是Object方法,任何实例对象都可以调用
- sleep不会释放锁,他也不需要占用锁,wait会是释放锁,但调用前提是当前线程占有锁
- 他们都可以被interrupt方法中断
并发和并行
并发: 同一时刻多个线程访问同一个资源。
例如:抢票,秒杀
并行: 多项工作一起执行,之后汇总
例如:泡泡面的时候,烧水,撕调料
管程
管程是一种同步机制,保证了同一时间内,只有一个线程访问被保护数据或者代码
在操作系统中叫Monitor监视器,在java中叫锁
说白了,加锁和解锁本质就是持有管程对象和非持有管程对象
用户线程和守护线程
用户线程:自定义的线程,比如自己new的线程
守护线程:比如垃圾回收,它运行在后台
用户线程和守护线程特点:
- 主线程结束,用户线程如果还在运行,那么jvm依然存活
- 如果没有用户线程,都是守护线程,那么jvm结束
我们来写代码测试一下
我们可以看到,主线程虽然结束了,但是用户线程还是在运行,并且用户线程的isDaemon方法是false说明这是个用户线程。
接下来,我们把用户线程变成守护线程试试
程序立刻就结束了
Lock接口
复习Sychronized
我们通过一个案例去进行复习吧
public class SaleTicket {
public static void main(String[] args) {
for (int i = 0; i < 13; i++) {
new Thread(new sale(),"asd").start();
}
}
}
class sale implements Runnable{
public static Shop shop = new Shop();
@Override
public void run() {
shop.saleTicket();
}
}
class Shop{
public static int ticket = 10;
public Shop() {
}
public static int getTicket() {
return ticket;
}
public synchronized void saleTicket() {
if(ticket<=0){
System.out.println("票卖完了");
return;
}
ticket = ticket -1;
System.out.println("票的数量是" + ticket);
}
}
总体分为两个步骤:
- 创建资源类,创建属性和操作方法
- 创建多线程调用资源类的方法
注意:我们这里通过sychronized关键字锁住了操作资源的方法,让线程安全
但是我们都知道sychronizied关键字是一种自动加锁解锁的逻辑。
下面我们来复习一下Lock接口
Lock锁实现提供了比sychronzed的同步方法和语句更广泛的锁操作。他们允许更灵活的结构,可能具有非常不同的属性。Lock比sychronized更多的功能
Lock和Sychronized的区别
- Lock不是java语言内置的,Sychronized则是java语言的关键字,是内置特性。Lock是一个类,通过这个类可以实现同步访问
- Lock必须要用户去手动释放锁,如果没有手动释放,就有可能出现死锁
下面进行实战
我们new了一个ReentrantLock,可重入锁来进行资源的加锁和手动释放锁
package org.example.lock;
import java.util.concurrent.locks.ReentrantLock;
class LTicket{
public static int num = 30;
private final ReentrantLock lock = new ReentrantLock();
public void sell(){
lock.lock();
try{
if(num>0){
System.out.println(Thread.currentThread().getName() +"卖到"+ num--);
}
}finally {
lock.unlock();
}
}
}
public class LSaleTicket {
public static void main(String[] args) {
LTicket lTicket = new LTicket();
new Thread(()->{
for (int i = 0; i < 40; i++) {
lTicket.sell();
}
},"AA").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
lTicket.sell();
}
},"BB").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
lTicket.sell();
}
},"CC").start();
}
}
这个时候有同学会好奇了,BB去哪了?
我们在执行完这个方法以后
线程就立刻被创建了吗?
其实不是的!我们来看看start方法的源码
它先调用了start0方法
start0上被加了native关键字!
这个方法是其它语言去写的!
其实它是否被创建由操作系统决定!所以B就会慢一些创建,自然抢不到资源
线程间的通信
Sychronized实现
案例:如果一个类里的一个数值为0,那么一个线程就让他变成1,如果这个数值是1,那就让线程把它变成0。
思路:让一个线程负责变1,一个线程负责变0.如果第一个线程发现是1,则休眠。等待第二个线程变0后将它唤醒
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();
}
}
public class TreadDemo1 {
public static void main(String[] args) {
Share share = new Share();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share.decr();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"AA").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share.incr();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"BB").start();
}
}
注意,我们这里用while做判断是为了防止虚假唤醒问题。
因为如果线程一多,由于线程在哪里等待就在哪里会被唤醒。所以如果用if的话,它在等待中被唤醒了,不一定是值为0或者1,所以需要它在循环中一直等才可
可重入锁实现
class Share2{
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) {
Share2 share2 = new Share2();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share2.incr();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"AA").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share2.incr();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"BB").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share2.decr();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"CC").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share2.decr();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"DD").start();
}
}