1、JUC

JUC,即java.util.concurrent这个处理线程的工具包,始于JDK1.5,其中下有三个包,为:

  • 基础包
  • 原子包
  • 锁包

【JUC】一、synchronized关键字与Lock接口-LMLPHP

2、进程与线程

  • 进程是一个应用程序(一个进程是一个软件)
  • 线程是一个进程中的执行场景/执行单元。一个进程可以启动多个线程

把进程看作是现实生活中的公司,如京东。线程则可看作是其下的某一个职能部门,负责完成某任务,如开发部门。

DOS窗口运行java HelloWorld,先启动JVM,JVM是一个进程,JVM启动一个主线程调用main方法,同时再启动一个垃圾回收线程来负责看护、回收垃圾。(也就是说Java程序至少两线程并发,main方法对应的主线程+GC)

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继续存活:

【JUC】一、synchronized关键字与Lock接口-LMLPHP

aa线程被set为守护线程后,则当用户线程都结束时,守护线程自动结束:

【JUC】一、synchronized关键字与Lock接口-LMLPHP

5、对象锁和类锁

  • 类锁属于一个类,类似静态变量,多个实例对象对应同一个锁,一个类一把锁
  • 对象锁属于对象实例,类似类属性变量,每一个实例对象,就对应一个锁,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();
    }


}


【JUC】一、synchronized关键字与Lock接口-LMLPHP

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();
    }
}

11-12 20:47