Java多线程及锁的简单介绍

多线程

一、基本概念

1.1程序、进程、线程

程序(program):是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象

进程(process):是程序的一次执行过程,或是正在运行的一个程序。动态过程:有它自身的产生、存在和消亡的过程

  • 特点:
    • 在单核CPU下同一个时间点只能有一个程序运行
    • 宏观并行,微观串行
  • 运行中的QQ,运行中的MP3播放器
  • 程序是静态的,进程是动态的
  • 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域

线程(thread):进程可进一步细化为线程,是一个程序内部的一条执行路径。

  • 若一个进程同一时间并行执行多个线程,就是支持多线程的
  • 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
  • 一个进程中的多个线程共享相同的内存单元/内存地址空间,它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患
  • 每个Java程序都有一个隐含的主线程:main方法
  • 进程可以有多个线程,他们之间独立运行,交通执行称之为多线程

线程的组成:

  • 任何一个线程都具有基本的组成部分:
    • CPU时间片
      • 操作系统(OS)会为每个线程分配执行时间
    • 运行数据
      • 堆空间:存储线程需使用对象,多个线程可以共享堆中的对象
      • 栈空间 :存储线程需使用的局部变量,每个线程都拥有独立的栈
    • 线程的逻辑代码

进程和线程的区别:

  • 进程是操作系统资源分配的基本单位,而线程是CPU的基本单位
  • 一个程序运行后至少有一个进程
  • 一个进程可以包含多个线程,但是至少需要有一个线程,线程不能独立运行必须依附于线程当中
  • 进程间不能共享数据段地址,但同进程下的线程之间可以

1.2多核CPU

  • 单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务

  • 如果是多核的话,才能更好的发挥多线程的效率,现在的服务器都是多核的

1.3多线程应用场合

  • 程序需要同时执行两个或多个任务;
  • 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等;
  • 需要一些后台运行的程序时

二、线程的创建和使用

2.1创建方式一:继承Thread

步骤:

  1. 定义子类继承Thread
  2. 子类重写Thread类中的run方法
  3. 创建Thread子类对象,即创建线程对象
  4. 调用线程对象start方法(启动线程,调用run方法)
public class MyThread extends Thread{
    //覆盖run方法
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            //currentThread()获取到当前进程 getName()获取对象名
            System.out.println(Thread.currentThread().getName()+"======"+i);
        }
    }
}


public class Demo {
    public static void main(String[] args) {
        //创建线程对象
        MyThread t1 = new MyThread();

        //启动线程
        t1.start();

        //创建多个线程对象
        MyThread t2 = new MyThread();
        t2.start();

        MyThread t3 = new MyThread();
        t3.start();

    }
}

2.2创建方式二:实现Runnable接口

步骤:

  1. 定义子类实现Runnable接口
  2. 子类中重写Runnable接口中的run方法
  3. 通过Thread类构造方法创建线程对象
  4. Runnable接口的子类对象作为实际参数传递给Thread类的构造方法中
  5. 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法
public class MyThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            //currentThread()获取到当前进程 getName()获取对象名
            System.out.println(Thread.currentThread().getName()+"======"+i);
        }
    }
}

public class Demo {
    public static void main(String[] args) {

        Thread t1 = new Thread(new MyThread2());//MyThread2()作为一个线程任务启动线程
        t1.start();
    }
}

2.3两种方式的使用细节

  1. 启动线程不是调用run,而是调用start方法
  2. 同一个线程不能启动多次
  3. 继承Thread类和实现Runnable接口的区别
    1. 继承Thread类则无法继承别的类(java是单继承),使用不够灵活,而实现接口Runnable可以继承别的类,更灵活
    2. 继承Thread类只能表示这是一个线程类,可以之间调用start方法启动线程,接口只能表示当前类为一个线程任务,需要创建Thread对象执行这个线程任务

2.4启动线程需要注意的问题

  1. 不要调用run方法进行启动线程
  2. 一个线程只能调用一次start方法

2.5线程的状态

  • New 初始状态
    • 线程对象被创建即为初始状态 只在堆中开辟内存,与常规对象无异
  • Ready 就绪状态
    • 调用start()之后,进入就行状态 等待OS选中,并分配时间片
  • Running 运行状态
    • 获得时间片之后,进入运行状态如果时间片到期,则返回到就绪状态
  • Terminated 终止状态
    • 主线程main()或独立线程run()结束,进入终止状态,并释放持有的时间片

2.6常见方法

2.6.1设置线程名称

setName()getName()

获取到当前进程对象currentThread()

**注意:**如果没有设置线程名称,那么默认的名字就为Tread-0、Tread-1、Tread-2…

public class MyThread implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"======"+"启动了");
    }
}

public class Demo{
    public static void main(String[] args) {
        Thread t1 = new Thread(new MyThread3());
        //setName给线程取名字
        t1.setName("线程");
        t1.start();
    }

}
2.6.2设置线程的优先级

设置优先级方法setPriority()

  • 线程优先级1-10 线程的优先级越高 抢占CPU概率越大

获取线程的优先级方法getPriority()

  • 线程的优先级默认为5
2.6.3休眠
  • sleep(long m)
  • 当前线程主动休眠 m 毫秒
  • 让线程进入休眠,让出CPU的使用权,直到休眠结束,才会继续抢占CPU
public class MyThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            //线程休眠  参数:休眠的时间 (毫秒)
            try {
                //让当前线程处于睡眠时间,知道时间结束才会继续向下执行
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+" "+i);

        }
    }
}

public class Demo {
    public static void main(String[] args) {

        Thread t1 = new Thread(new MyThread4());
        //setName给线程取名字
        t1.setName("线程t1");
        t1.start();
    }
}

2.6.4放弃(礼让)
  • yield()
  • 当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片
  • 让出CPU的使用权,但是立马又会重新抢占CPU
public class MyThread1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if (i==5){
                //线程礼让 放弃当前的CPU使用权,进入到就绪状态
                Thread.yield();
            }
            System.out.println(Thread.currentThread().getName()+"启动了");
        }
    }
}

public class MyThread2 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"启动了");
        }
    }
}

public class Demo {
    public static void main(String[] args) {
        MyThread5 t1 = new MyThread5();
        t1.setName("线程1");
        t1.start();

        Thread t2 = new Thread(new MyThread3());
        t2.setName("线程2");
        t2.start();
    }
}

2.6.5结合(加入线程)
  • join()
  • 允许其他线程加入到当前线程中
  • 在当前线程加入另一个线程,必须将另一个加入的线程完全执行后才能执行当前线程
public class MyThread1 extends Thread{
    //覆盖run方法
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            //currentThread()获取到当前进程 getName()获取对象名
            System.out.println(Thread.currentThread().getName());
        }
    }
}

public class MyThread2 extends Thread{
    @Override
    public void run() {

        MyThread t = new MyThread();
        t.setName("加入");
        for (int i = 0; i <50; i++) {
             if (i==5){
                t.start();
                 try {
                     //将t加入到线程中执行 特点:必须要t完全指向结束之后 才能执行当前的线程任务
                     t.join();
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
            System.out.println(Thread.currentThread().getName()+"执行"+i+"次");
        }
    }
}

public class Demo {
    public static void main(String[] args) {
        MyThread6 t1 = new MyThread6();
        t1.start();
    }
}

2.7线程的状态

期限等待(Timed Waiting)和无期限等待(Waiting)

  • 初始状态—就绪状态—运行状态—(sleep)—期限等待(睡眠时间到期)—就绪状态—运行状态—终止状态
  • 初始状态—就绪状态—运行状态—(join)—无期限等待(满足条件、加入的线程完全执行后)—就绪状态—运行状态—终止状态

在这里插入图片描述

三、线程安全问题

存在的问题

public class ThreadDemo extends Thread{
    //临界资源
    //模拟一百张票
    static int ticket = 100;

    @Override
    public void run() {
        while (true){
            if(ticket==0){//当临界资源 小于0或者等于0时候表示没有票了
                break;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"卖出第"+(ticket--)+"票");
        }
    }
}

public class Demo {
    public static void main(String[] args) {
        ThreadDemo01 t1 = new ThreadDemo01();
        t1.setName("售票员1号");
        ThreadDemo01 t2 = new ThreadDemo01();
        t2.setName("售票员2号");
        ThreadDemo01 t3 = new ThreadDemo01();
        t3.setName("售票员3号");
        ThreadDemo01 t4 = new ThreadDemo01();
        t4.setName("售票员4号");

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}
  • 当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致
    • 临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其准确性
    • 原子操作:不可拆分的步骤,被视作一个整体,其顺序和步骤不可打乱或省缺

Java对于多线程的安全问题提供了专业的解决方式:同步机制

3.1线程同步

synchronized是互斥锁

3.1.1synchronized代码块(同步代码块)
public class ThreadDemo extends Thread {

        //临界资源
        //模拟一百张票
        static int ticket = 100;

        @Override
        public void run() {
            while (true) {
   			/* synchronized代码块运行完毕,才会切换到另一个线程执行
			 * 涉及到多个线程修改的地方要放到synchronized代码块中
			 * 锁:一定要是所有线程共享的	
			*/
                synchronized (this.getClass()) {//互斥锁对象,互斥锁对象可以是Java中任意对象但是要保证唯一
                    if (ticket <= 0) {//当临界资源 小于0或者等于0时候表示没有票了
                        break;
                    }
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "卖出第" + (ticket--) + "票");
                }//释放锁标记
            }
    }

}

public class Demo{
    public static void main(String[] args) {

        ThreadDemo02 t1 = new ThreadDemo02();
        t1.setName("售票员1号");
        ThreadDemo02 t2 = new ThreadDemo02();
        t2.setName("售票员2号");
        ThreadDemo02 t3 = new ThreadDemo02();
        t3.setName("售票员3号");
        ThreadDemo02 t4 = new ThreadDemo02();
        t4.setName("售票员4号");

        t1.start();
        t2.start();
        t3.start();
        t4.start();

    }
}
2.1.2synchronized方法(同步方法)

语法:

synchronized 返回值类型 方法名称(形参列表){//当前对象(this)加锁
	//代码(原子操作)
}

注:

  • 只有拥有对象互斥锁标记的线程,才能进入该对象的同步方法中
  • 线程退出同步方法时,会释放相应的互斥锁标记

方式一:

public class ThreadDemo extends Thread{
    static int ticket = 100;
    @Override
    public void run() {
        while (true) {
            sale();
        }

    }

    //成员方法,那么同步方法的互斥锁对象就是this
    //类(static)方法,那那么同步方法的互斥锁对象就是类对象
    public synchronized static void sale(){
        if (ticket<=0){
            System.exit(0);
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "卖出第" + (ticket--) + "票");
    }
}


public class Demo {
    public static void main(String[] args) {
        ThreadDemo03 t1 = new ThreadDemo03();
        ThreadDemo03 t2 = new ThreadDemo03();
        ThreadDemo03 t3 = new ThreadDemo03();
        ThreadDemo03 t4 = new ThreadDemo03();
        
        t1.setName("售票员1号");
        t2.setName("售票员2号");
        t3.setName("售票员3号");
        t4.setName("售票员4号");

        t1.start();
        t2.start();
        t3.start();
        t4.start();

    }
}

方式二:

public class ThreadDemo implements Runnable {
    //都是线程任务,因为都是通过同一个Runnable 接口实现类
    static int ticket = 100;
    @Override
    public void run() {
        while (true) {
            sale();
        }
    }

    public synchronized static void sale(){
        if (ticket<=0){
            System.exit(0);
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "卖出第" + (ticket--) + "票");
    }
}

public class Demo {
    public static void main(String[] args) {
        ThreadDemo04 task = new ThreadDemo04();
        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);
        Thread t3 = new Thread(task);
        Thread t4 = new Thread(task);

        t1.setName("售票员1号");
        t2.setName("售票员2号");
        t3.setName("售票员3号");
        t4.setName("售票员4号");

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

注意:

  • 只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记
  • 如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用

已知JDK中线程安全的类:

  • StringBuffer
  • Vector
  • Hashtable
  • 以上类中的的公开方法,均为synchronized修饰的同步方法

四、线程的生命周期

创建状态 就绪状态 运行状态 阻塞\等待状态 终止状态

在这里插入图片描述

五、死锁

  • 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
  • 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
public class MyLock {
    static Object obj1 = new Object();//一根筷子
    static Object obj2 = new Object();//另一根筷子
}

public class Boy extends Thread {
    @Override
    public void run() {
        synchronized (MyLock.obj1){//男孩有一根筷子
            System.out.println("男孩有一根筷子在等待另一根筷子");
            synchronized (MyLock.obj2){//obj2 另一根筷子
                System.out.println("男孩吃饭了");
            }
        }
    }
}

public class Girl extends Thread{
    @Override
    public void run() {
        synchronized (MyLock.obj2){//女孩孩有另一根筷子
            System.out.println("女孩有一根筷子在等待另一根筷子");
            synchronized (MyLock.obj1){//obj1 另一根筷子
                System.out.println("女孩吃饭了");
            }
        }
    }
}

public class Demo {
    public static void main(String[] args) {
        Boy boy = new Boy();
        Girl girl = new Girl();
        boy.start();
        girl.start();
    }
}

解决方法:

  • 专门的算法、原则
  • 打破互斥条件
  • 尽量避免嵌套同步

六、线程通信

相关方法:

  • wait():令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行
  • notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
  • notifyAll():唤醒正在排队等待资源的所有线程结束等待

以上三个方法只有在synchronized方法或synchronized代码块中才能使用

public class Phone {
    String name;
}

public class Shop {
    // 表示一个买卖的商品
    Phone phone;
    //进货(生产)
    public synchronized void putPhone(Phone phone){//参数表示进货的商品
        if(this.phone!=null){
            System.out.println("表示有商品,需要等待");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //表示没有商品需要生产商品
        System.out.println("正在生产商品"+phone.name);
        this.phone=phone;
        //通过消费者来消费
        this.notify();
    }

    //消费
    public synchronized void getPhone(){
        if (this.phone==null){//表示没有商品需要等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("表示正在消费商品"+phone.name);
        //模拟消费商品
        this.phone=null;
        //通知生产者生产9
        this.notify();
    }
}

public class Test {
    static Shop shop = new Shop();
    public static void main(String[] args) {

        ProducerThread pro = new ProducerThread();//生产者
        CustomerThread cus = new CustomerThread();//消费者
        pro.start();
        cus.start();
    }
}

class ProducerThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            Test.shop.getPhone();
        }
    }
}

class CustomerThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            Phone phone = new Phone();
            phone.name = "Iphone"+i;
            Test.shop.putPhone(phone);
        }
    }
}

七、线程池概念

  • 现有问题:
    • 线程是宝贵的内存资源,单个线程约占1MB空间,过多分配易造成内存溢出
    • 频繁的创建及销毁线程会增加虚拟机回收频率、资源开销、造成程序性能下降

7.1线程池

  • 线程容器,可设定线程分配的数量上限
  • 将预先创建的线程对象存入池中,并重用线程池中的线程对象
  • 避免频繁的创建和销毁
  • 线程池维护着一个对了,队列中保存着处于等待(空闲状态的)线程,不用每次创建新的线程

7.2线程池原理

  • 将任务交给线程池,由线程池分配线程、运行任务,并在当前任务结束后复用线程

7.4获取线程池

  • 常用线程池接口和类(所在包java.util.concurrent)
    • Executor:线程池顶级接口
    • ExecutorService:线程池接口,可以通过submit(Runnable task)提交任务代码
    • Executors工厂类:通过此类可以获得一个线程池
  • newFixedThreadPool(int nThreads)获取固定数量的线程池。参数指定线程池中的线程的数量
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MyTaskDemo01 {
    public static void main(String[] args) {
        //创建线程池对象(创建一个线程数为3的线程对象)
        ExecutorService es = Executors.newFixedThreadPool(3);

        //提交线程任务
        es.submit(new MyTask());
        es.submit(new MyTask());
        es.submit(new MyTask());
        es.submit(new MyTask());

        //关闭线程池
        es.shutdown();
    }
}

class MyTask implements Runnable{//线程任务类
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"---"+i);
        }
    }
}
  • newCacheThreadPool()获得动态线程数量的线程池,如不够则创建新的,无上限
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MyTaskDemo02 {
    public static void main(String[] args) {
        //创建无上限的线程池对象
        ExecutorService es = Executors.newCachedThreadPool();
        //往线程池提交线程任务
        //匿名内部类
        es.submit(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName()+"---"+i);
                }
            }
        });
        es.submit(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName()+"---"+i);
                }
            }
        });
        es.submit(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName()+"---"+i);
                }
            }
        });
        es.submit(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName()+"---"+i);
                }
            }
        });
		//关闭线程池
        es.shutdown();
    }
}

八、实现Callable接口

JDK5.0新增线程创建方式CallableRunnable类似,实现之后代表一个线程任务

与使用Runnable相比, 功能更强大些

  • 有返回值
  • 方法可以抛出异常
  • 支持泛型的返回值
  • 需要借助FutureTask类,比如获取返回结果
import java.util.concurrent.*;

public class MyTaskDemo03 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建有限的线程池对象 容量为3
        ExecutorService es = Executors.newFixedThreadPool(3);

        //通过线程池提交线程任务
/*        Future f1 = es.submit(new MyTask01());
        es.submit(new MyTask01());
        es.submit(new MyTask01());
        es.submit(new MyTask01());
        es.submit(new MyTask01());

        System.out.println("这是主线程的代码1");
        System.out.println(f1.get());
*/
        Future f2 = es.submit(new MyTask02());
        es.submit(new MyTask02());
        es.submit(new MyTask02());
        es.submit(new MyTask02());
        es.submit(new MyTask02());

        System.out.println("这是主线程的代码2");
        System.out.println(f2.get());

        //关闭线程池
        es.shutdown();

    }
}
class MyTask01 implements Callable{
    @Override
    public Object call() throws Exception {
        //线程执行的代码
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"---"+i);
        }
        return null;
    }
}
class MyTask02 implements Callable<String>{//泛型表示:coll方法的返回值
    @Override
    public String call() throws Exception {
       Thread.sleep(1000);
        return "Hello";
    }
}

关于Future接口

  • 可以对具体RunnableCallable任务的执行结果进行取消、查询是否完成、获取结果等
  • FutrueTaskFutrue接口的唯一的实现类
  • FutureTask同时实现了RunnableFuture接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
import java.util.concurrent.*;

public class MyTaskDemo04 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService es = Executors.newFixedThreadPool(2);
        //Future接口就是用于接收Callable线程任务的返回值
        Future<String> future = es.submit(new MyTask1());
        System.out.println(future.get());
        es.shutdown();

    }
}
class MyTask1 implements Callable<String> {
    @Override
    public String call() throws Exception {
        //线程执行代码
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"---"+i);
        }
        return "返回值";
    }
}

九、Lock接口

JDK 5.0加入与synchronized比较,显示定义,更加灵活

提供更多实用性方法,功能更强大、性能更优越

9.1Lock锁

  • ReentrantLock()重入锁
  • Lock``synchronized都具有互斥锁功能

常用方法:

  • lock()获取锁
  • tryLock()尝试获取锁
  • unlock()释放锁
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;

public class Demo {
    public static void main(String[] args) {
        //创建线程池
        ExecutorService es = Executors.newFixedThreadPool(4);
        //通过线程池提交线程任务
        TicketTask task = new TicketTask();
        es.submit(task);
        es.submit(task);
        es.submit(task);
        es.submit(task);

        //关闭线程池
        es.shutdown();

    }
}
class TicketTask implements Runnable{
    ReentrantLock lock = new ReentrantLock();//重入锁

    static int ticket = 300;
    @Override
    public void run() {
        while (true) {
           try {
               lock.lock();//获取锁
               if (ticket<1){
                   break;
               }
               try {
                   Thread.sleep(100);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               System.out.println(Thread.currentThread().getName()+"卖出第"+(ticket--)+"号票");
           }finally {
               lock.unlock();//释放锁
           }
        }
    }
}

9.2读写锁

  • ReentrantReadWriteLock
  • 一种支持一写多读的同步锁,实现读写分离,可分别分配读、写锁
  • 支持多分配读锁,使多个读操作并发执行

互斥规则

  • 写写:互斥 阻塞
  • 读写:互斥 写阻塞读 读阻塞写
  • 读读:不互斥 不阻塞
  • 在读操作高于写操作的环境中,可在保障线程安全的情况下提高运行效率
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class User {
    String name;
      ReadWriteLock lock = new ReentrantReadWriteLock();
      Lock readLock = lock.readLock();
      Lock writeLock = lock.writeLock();

    //写
    public void setName(String name){
        readLock.lock();
        try {
            this.name = name;
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            readLock.unlock();
        }
    }
    //读
    public String getName() {
        writeLock.lock();
        try {
            return name;
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            writeLock.unlock();
        }
        return null;
    }
}

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo02 {
    public static void main(String[] args) {
        User user = new User();
        ExecutorService es = Executors.newFixedThreadPool(20);

        //提交线程任务
        long start = System.currentTimeMillis();//记录时间

        //写的任务
        es.submit(new WriteTask(user));
        es.submit(new WriteTask(user));

        //读的任务
        for (int i = 0; i <18; i++) {
            es.submit(new ReadTask(user));
        }

        //关闭线程池
        es.shutdown();
        //判断线程是否还在运行
        System.out.println(es.isTerminated());

        while (!es.isTerminated()) {//让代码空转
            long end = System.currentTimeMillis();
            System.out.println(end-start);
        }
    }
}
class ReadTask implements Runnable{
    User user =new User();
    public ReadTask(User user){
        this.user = user;
    }
    @Override
    public void run() {
        user.getName();
    }
}
class WriteTask implements Runnable{
    static int i=1;//辨认操作 写了几次
    User user =new User();
    public WriteTask(User user){
        this.user = user;
    }
    @Override
    public void run() {
        user.setName("aaa");
    }
}

9.3重入锁

  • 重入锁
    • 也叫递归锁,指的是同一个线程外层函数获得了一把锁,内层函数同样具有这把锁的控制权限
    • synchronizedLock都可以实现锁的重入
public class Demo03 {
    public static void main(String[] args) {
       new Thread(new MyRunnable()).start();
    }
}
class MyRunnable implements Runnable{
    @Override
    public void run() {
        a();
    }
    public synchronized void a(){
        System.out.println("a");
        b();
    }
    //当前锁对象为this
    public synchronized void b(){
        System.out.println("b");
        c();
    }
    //当前锁对象为this
    public synchronized void c(){
        System.out.println("c");
    }
}

9.4公平锁

  • 公平锁与非公平锁

    • 非公平锁:优先使用上一个线程接着执行下面的线程任务

      • synchronized是非公平锁,无法实现公平锁
      • lock默认是非公平锁,那么就需要在构造方法中设置ture
    • 公平锁:每个线程公平的执行任务

      • lock可以实现公平锁

      • new ReentrantLock( true )//实现公平锁
        
      • synchronized无法实现公平锁

9.5synchronized锁升级

  • synchronized锁的状态有四种

    • 无锁

      • 当锁对象被创建的时候,还没有进入到线程时,此时锁对象处于无锁状态
    • 偏向锁

      • 当有线程A进入同步代码块并获得锁对象,此时保存线程ID,以后此线程进入到同步代码块,无需使用CAS加锁或者释放锁,只需要验证线程ID即可
    • 轻量级锁(自旋锁)

      • 当前锁处于偏向锁,此时有线程B进入到同步代码块,这个时候线程AB会使用CAS(比较和交换算法)竞争,并升级为轻量级锁
    • 重量级锁

      • 如果线程没有获得轻量级锁,线程会通过CAS自旋获得锁对象如果自旋次数大于阈值(10次),则升级为重量级锁
  • synchronized在JDK1.6中提供锁的四种状态,可以自动升级,但不允许降级

{
new Thread(new MyRunnable()).start();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
a();
}
public synchronized void a(){
System.out.println(“a”);
b();
}
//当前锁对象为this
public synchronized void b(){
System.out.println(“b”);
c();
}
//当前锁对象为this
public synchronized void c(){
System.out.println(“c”);
}
}


### 9.4公平锁

- 公平锁与非公平锁

  - 非公平锁:优先使用上一个线程接着执行下面的线程任务

    - `synchronized`是非公平锁,无法实现公平锁
    - `lock`默认是非公平锁,那么就需要在构造方法中设置ture

  - 公平锁:每个线程公平的执行任务

    - `lock`可以实现公平锁

    - ```java
      new ReentrantLock( true )//实现公平锁
      ```

    - `synchronized`无法实现公平锁

### 9.5`synchronized`锁升级

- `synchronized`锁的状态有四种

  - 无锁
    - 当锁对象被创建的时候,还没有进入到线程时,此时锁对象处于无锁状态

  - 偏向锁
    - 当有线程A进入同步代码块并获得锁对象,此时保存线程ID,以后此线程进入到同步代码块,无需使用CAS加锁或者释放锁,只需要验证线程ID即可
  - 轻量级锁(自旋锁)
    - 当前锁处于偏向锁,此时有线程B进入到同步代码块,这个时候线程AB会使用CAS(比较和交换算法)竞争,并升级为轻量级锁
  - 重量级锁
    - 如果线程没有获得轻量级锁,线程会通过CAS自旋获得锁对象如果自旋次数大于阈值(10次),则升级为重量级锁

- `synchronized`在JDK1.6中提供锁的四种状态,可以自动升级,但不允许降级

- `synchronized`在代码出现异常时会释放锁  `Lock`在代码出现异常时不会释放锁      

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值