文章目录
- 1、JUC
- 2、进程与线程
- 3、并发与并行
- 4、用户线程和守护线程
- 5、对象锁和类锁
- 6、Synchronized关键字
- 7、synchronized案例
- 8、Lock接口
1、JUC
JUC,即java.util.concurrent这个处理线程的工具包,始于JDK1.5,其中下有三个包,为:
- 基础包
- 原子包
- 锁包
2、进程与线程
进程与线程的关系:
- 进程是一个应用程序(一个进程是一个软件)
- 线程是一个进程中的执行场景/执行单元。一个进程可以启动多个线程
把进程看作是现实生活中的公司,如京东。线程则可看作是其下的某一个职能部门,负责完成某任务,如开发部门。
举个例子:
DOS窗口运行java HelloWorld,先启动JVM,JVM是一个进程,JVM启动一个主线程调用main方法,同时再启动一个垃圾回收线程来负责看护、回收垃圾。(也就是说Java程序至少两线程并发,main方法对应的主线程+GC)
进程A和进程B的内存关系:
Java中,线程A和线程B,堆内存和方法区内存共享,但栈内存独立,一个线程一个栈
。如启动了10个线程,就会有10个栈空间,每个栈和每个栈之间互不干扰,各自执行各自的,这就是多线程并发。
举个例子:
🍁Java中的多线程机制,目的就是为了提高程序的处理效率, 如火车站看成是一个进程,则每个售票小窗口就是一个个线程,甲在窗口1买票,乙在窗口2买票,谁也不用等谁 一个个售票窗口就像一个个栈,有自己独立的空间。售票大厅这个共用空间就像堆和方法区
3、并发与并行
先说下串行和并行:
- 串行模式就是一次只能取一个任务,并执行这个任务,如必须先装完一车柴,才能运送这车柴,只有送到了,才能卸下这车柴。一个步骤完成了,才能进行下一个步骤。
- 并行模式即可以同时取多个任务,并同时去执行所取得的这些任务
而并行和并发
,则是:
-
并发:
同一时刻,多个线程在访问同一个资源
,多个线程对一个点,对应的例子: 春运抢票 电商秒杀 -
并行:多项工作一起执行,之后再汇总例子,举例就是泡方便面,电水壶烧水,一边撕调料倒入桶中
4、用户线程和守护线程
Java中的线程分两类:
- 用户自定义的线程,如主线程main线程、或者自定义的其他线程
- 守护线程:如垃圾回收线程。一般守护线程是一个死循环,所有用户线程结束的时候,守护线程自动结束
写个测试代码:
public static void main(String[] args){
Thread aa = new Thread( () -> {
System.out.println(Thread.currentThread().getName() + "::" + Thread.currentThread().isDaemon());
while (true) {
}
}, "aa");
aa.setDaemon(true);
aa.start();
System.out.println(Thread.currentThread().getName() + " over");
}
可以看到,aa.setDaemon(true)被注释时,aa线程属于用户自定义线程,此时main线程结束以后,自定义的aa线程不受影响,JVM继续存活:
aa线程被set为守护线程后,则当用户线程都结束时,守护线程自动结束:
5、对象锁和类锁
锁是
控制多个线程对共享资源进行访问的工具
。通常,锁提供了对共享资源的独占访问。一次只能有一个线程获得锁,对共享资源的所有访问都需要首先获得锁。不过,某些锁可能允许对共享资源并发访问,如ReadwriteLock的读取锁。
- 类锁属于一个类,类似静态变量,多个实例对象对应同一个锁,一个类一把锁
- 对象锁属于对象实例,类似类属性变量,每一个实例对象,就对应一个锁,100个类A的对象,就有一百个对象锁
6、Synchronized关键字
synchronized是Java关键字,表示一种同步锁,这个关键字:
- 可修饰代码块,作用范围是大括号中的语句,作用对象是调用这个代码块的对象
synchronized(线程共享对象){
…
//线程同步代码块,即要排队执行的代码块
…
}
- 可修饰方法,作用范围是整个方法,作用对象是调用这个方法的对象
public synchronized void doSome(){
//...
}
- 在实例方法中使用synchronized,表示共享的对象一定是this,并且同步的代码块是整个方法体
- 在静态方法中使用synchronized,表示找类锁,类锁永远只有1把(对象锁是100个对象就有100个对象锁)
7、synchronized案例
多线程编程的步骤
- 步骤一:创建(将来被共享的)资源类,创建属性和操作方法
- 步骤二:在资源类的操作方法中进行:判断、干活儿、通知
- 步骤三:创建多线程调用资源类的方法
- 步骤四:防止虚假唤醒现象
模拟三个售票员同时对外出售30张票,三个售票员,即三个线程,资源类就是票类,资源类的属性是票的数量:
//资源类
class Ticket{
private Integer 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(() -> {
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();
Thread cc = new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "CC");
cc.setPriority(Thread.MAX_PRIORITY);
cc.start();
}
}
8、Lock接口
API文档:
https://tool.oschina.net/apidocs/apidoc?api=jdk-zh
Lock接口比synchronized更灵活,其实现类有:
- ReentrantLock
- ReentrantReadWriteLock.ReadLock
- ReentrantReadWriteLock.WriteLock
ReentrantLock即可重入锁,类比排队上厕所,进门后锁门,用完后解锁,下个人继续上锁,用完解锁。
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}
Lock接口比synchronized的对比:
- Lock是一个接口,有对应的实现类,而synchronized是Java关键字
- synchronized不需要用户去手动释放锁,synchronized代码块或者方法执行结束或者发生异常时,系统会自动让线程释放锁,而Lock需要开发者手动释放锁
- Lock可以让等待锁的线程响应终端,而synchronized不行,等待的线程会一直等待下去
- Lock可以直到有没有成功获取锁,而synchronized不行
- Lock可以提到多个线程进行读操作的效率
- 大量线程同时竞争激烈时,Lock性能远胜synchronized
用Lock实现上篇synchronized实现的卖票例子:
//资源类
class LTicket{
private Integer 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 {
//释放锁写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(new Runnable() {
@Override
public void run() {
for(int i = 0 ; i < 40; i++ ){
ticket.sale();
}
}
},"BB").start();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "CC").start();
}
}