java多线程学习

通过B站人人都是程序员视频讲解进行的笔记整理,用于个人学习和复习。

目录

一、认识线程:

Thread:

什么是多线程?

什么时候使用线程

创建线程的三种方式:

1、继承Thread类

2、实现Runnable接口

3、实现Callable接口

获取当前正在执行任务的线程

获取和设置线程的名称

run() 方法与 start方法的区别

获取线程优先级

使当前正在执行的线程进入休眠状态

优雅地停止线程

1、停止正在运行的线程

2、停止休眠中的线程

让线程放弃执行权

等待线程死亡join方法

守护线程(后台线程)

 判断线程是否存活

线程组

二、线程锁

synchronized关键字

同步代码块

同步方法

静态同步方法

同步锁有哪些?

什么是同步锁

同一把锁

死锁是如何产生的?

什么是死锁

 死锁产生的四个条件

等待唤醒机制

1、等待(wait)

2、唤醒单个线程(notify)

3、唤醒所有线程(notifyAll)

wait和sleep的区别

线程间通讯  (wait、notify应用)

Lock(显式锁)

非阻塞式获取锁

中断等待锁的线程

不可中断 

可中断

Lock锁的等待唤醒机制:

可重入锁和不可重入锁:

公平 & 非公平锁

1、非公平锁synchronized演示:

 2、非公平锁ReentrantLock演示

 3、公平锁ReentrantLock演示

4、判断是否是公平锁,但是只能用来ReentrantLock上,不能用在synchronized上

isFair() 方法

synchronized和Lock的区别

读&写 锁

读锁演示

写锁演示

读写锁使用时的互斥关系

LockSupport:等待唤醒工具类

线程状态以及生命周期

ThreadLocal(线程本地变量)

InheritableThreadLocal(可继承的线程本地变量)


一、认识线程:

单线程的情况下,代码是由上到下依次 顺序执行 的,代码如下情况下,不论执行多少次控制台都是打印:1、2、3。

 多线程情况下:执行以下代码:

        Thread thread1 = new Thread(()->{
           System.out.println(1);
       });
        Thread thread2 = new Thread(()->{
            System.out.println(2);
        });
        Thread thread3 = new Thread(()->{
            System.out.println(3);
        });
        thread1.start();
        thread2.start();
        thread3.start();

控制台结果: 就会出现多种情况,说明我们的多线程不是 顺序执行 的,而是  并行执行 的。最终的结果是不确定的,哪个线程优先获得cpu执行权,谁就先被执行。

 

Thread:

我们发现在多线程执行代码时,频繁出现了一个关键字 叫做 Thread(线程)。

什么是多线程?:

线程时进程的最进本执行单位。是CPU调度的最小单位。多线程的存在就是为了同步完成多项任务,从而提高资源的使用效率

图解:

我们在运行某个软件,那么这个运行中的软件就可以当作是一个进程,进程在运行中会产生多个线程,每个线程执行各自的任务,共同完成软件的各个功能的运行。

如下比喻:进程比作是一个工厂,而线程就是工厂里面的一台台的机器。CPU标识工厂内某一组工作人员,CPU核心数就是一个个的员工。    线程运行需要获取到CPU时间片(即一台机器启动运作,需要一个人去操作),所以多线程执行代码效率会比较快(当一个工厂内  有多台机器,多个工人,同步进行工作,产量自然就高),资源也会被充分使用(避免了一个工厂内,明明有这么多机器,这么多人员,你却只启动一台机器,使用一个员工去生产,自然而然就慢)。

 

什么时候使用线程:

在同一时间需要完成多项任务的时候,比如下载多个文件,多个图片等等,使用多线程可以多个文件同时下载,提高了资源使用率

创建线程的三种方式:

1、继承Thread类

/**
 * 自定义一个类 MyThread  继承 Thread 类
 * 重写 run() 方法,方法体内写线程需要执行的功能代码
 * 线程启动后,会调用这里面的代码。
 * */
public class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("这里执行了我的功能代码");
    }
}

如何使用?

public class Test {
    public static void main(String[] args) {
       //创建MyThread实例
        MyThread myThread = new MyThread();
        //启动线程,会运行run()内的代码
        myThread.start();
    }
}

运行结果:

2、实现Runnable接口

/**
 * 自定义一个任务类,这个类我们需要去实现Runnable接口
 * 重写 run()
 * */
public class Task implements Runnable{
    @Override
    public void run() {
        System.out.println("实现Runnable接口");
        System.out.println("这里执行了我的功能代码");
    }
}

如何使用?

这里需要注意 我们的Runnable它是一个接口,没有启动线程的能力,所以我们需要搭配着Thread来使用,这里 Thread 可以当作是执行Runnable实现类的启动器。

public class Test {
    public static void main(String[] args) {
        //创建Runnable的实现类对象
        Task task = new Task();
        //创建Thread,将实现类对象放入到Thread中.
        Thread thread = new Thread(task);
        //启动线程执行
        thread.start();
    }
}

运行结果:

3、实现Callable接口:

这种方式能获取到线程执行完返回的返回值。 

还有就是Callable接口是带泛型的,我们实现Callable接口的时候,指定的泛型是什么类型,返回值就是什么类型。不指定泛型返回值类型是Object。

与Runnable不同 这个接口的实现类需要实现 call()  方法。

/**
 * 自定义一个任务类,这个类我们需要去实现Callable接口
 * 指定泛型是Strung
 * 所以返回值类型就是String
 * 重写 call()
 */
public class Result implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println("执行了Callable的run方法的功能代码");
        return "我是返回值";
    }
}

如何使用?

使用方法和Runnable有些相似,不过需要在其基础上,加上一个FuntureTask<T> 对象

public class Test {
    public static void main(String[] args) {
        //创建Runnable的实现类对象
        Result result = new Result();
        //创建FutureTask实例,承载 Callable的实现实例对象
        FutureTask<String> futureTask = new FutureTask<>(result);
        //创建Thread线程,承载FutureTask
        Thread thread = new Thread(futureTask);
        //启动线程
        thread.start();
        try {
            //获取返回值
            String resultStr = futureTask.get();
            //打印返回值
            System.out.println(resultStr);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

总结:

获取当前正在执行任务的线程:

Thread.currentThread()

 如何使用?

public class Test {
    public static void main(String[] args) {
        //获取当前正在执行任务的线程
        Thread thread = Thread.currentThread();
        System.out.println(thread);
    }
}

运行结果:

 看Thread的toString()方法源码便知:

public String toString() {
        ThreadGroup group = getThreadGroup();
        if (group != null) {
            return "Thread[" + getName() + "," + getPriority() + "," +
                           group.getName() + "]";
        } else {
            return "Thread[" + getName() + "," + getPriority() + "," +
                            "" + "]";
        }
    }

获取和设置线程的名称:

因为是成员方法,所以首先要获取到线程的实例对象,创建一个也好,获取当前线程也好,都行。

这里  thread  代表线程对象

获取线程的名称:thread.getName();

设置线程的名称:thread.setName(String newName);

public class Test {
    public static void main(String[] args) {
        //获取当前正在执行任务的线程
        Thread thread = Thread.currentThread();
        //获取线程名称
        String threadName = thread.getName();
        //输出线程名称
        System.out.println(threadName);

        //设置线程名称
        thread.setName("newName");
        //获取线程名称
        String threadNewName = thread.getName();
        //输出线程名称
        System.out.println(threadNewName);
    }
}

运行结果:

run() 方法与 start方法的区别:

获取线程优先级:

线程优先级,默认值(NORM_PRIORITY)是5  、最小值(MIN_PRIORITY)是1  、最大值(MAX_PRIORITY)是10。

thread.getPriority(int priority)

public class Test {
    public static void main(String[] args) {
        //获取当前正在执行任务的线程
        Thread thread = Thread.currentThread();
        //获取线程优先级
        int priority = thread.getPriority();
        //输出线程优先级
        System.out.println(priority);

        //设置线程优先级
        thread.setPriority(10);
        //获取线程优先级
        int newPriority = thread.getPriority();
        //输出线程优先级
        System.out.println(newPriority);
    }
}

运行结果:

使当前正在执行的线程进入休眠状态:

Thread.sleep(long millis)  是一个静态方法,参数是以毫秒为单位的整数。

Thread.sleep(long millis,int nanos)  是一个静态方法,这个两参数的方法,第一个参数指休眠多少毫秒,第二个参数值多少微秒,总时长为millis毫秒 + nanos微秒

public class Test {
    public static void main(String[] args) {
        //睡眠前 打印当前时间
        System.out.println("睡眠前:"+ LocalDateTime.now());
        try {
            //睡眠3秒
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //睡眠后 打印当前时间
        System.out.println("睡眠后:"+ LocalDateTime.now());
    }
}

结果:

优雅地停止线程:

1、停止正在运行的线程

thread.interrupt()

public class Test {
    public static void main(String[] args) {
        //创建一个线程
        Thread thread = new Thread(()->{
            while(true){
                System.out.println("线程正在执行,时间:"+ LocalDateTime.now());
            }
        });
        //启动线程
        thread.start();

        try {
            //然后主线程睡眠一秒
            Thread.sleep(1000);
            //终止上面的thread线程
            thread.interrupt();

            //让主线程继续执行
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

执行以上代码,发现thread线程并没有停止,反正继续在执行。这是为啥呢?

原因:这个方法只是给thread线程一个标记,一个表示这个线程应该停止的标记。需要我根据这个标记去判断是否该停止这个线程。 如何去判断呢?  有两种方法:

 isInterrupted() 方法为非静态方法,执行某个线程的这个方法,会返回一个boolean值,true表示这个线程被标记了,false表示未被标记。

interrupted() 方法,是一个静态方法,静态方法,哪个线程使用的这个方法,就作用于哪个线程,interrupted()方法获取线程的标记状态,如果状态是false返回false结果不做处理,如果状态是true,返回true结果,然后再将标记由true改为false。

两种方法,根据需求去使用。

执行以下代码,以isInterrupted()方法为例子,成功终止线程thread

public class Test {
    public static void main(String[] args) {
        //创建一个线程
        Thread thread = new Thread(()->{
            while(true){
                System.out.println("线程正在执行,时间:"+ LocalDateTime.now());
                Thread thread1 = Thread.currentThread();
                if (thread1.isInterrupted()){
                    //结束循环
                    System.out.println("循环结束");
                    break;
                }
            }
        });
        //启动线程
        thread.start();

        try {
            //然后主线程睡眠一秒
            Thread.sleep(1000);
            //终止上面的thread线程
            thread.interrupt();
            //让主线程继续执行
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2、停止休眠中的线程

情况如下:

public class Test {
    public static void main(String[] args) {
        //创建一个线程
        Thread thread = new Thread(()->{
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        //启动线程
        thread.start();

        try {
            //然后主线程睡眠一秒,上面设置了thread线程睡眠5秒
            Thread.sleep(1000);
            //到这里,主线程睡眠完了,thread线程还有4秒需要睡眠,
            //这是我们终止上面的thread线程
            thread.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

 该异常是中断休眠中的线程引发的,报出这个异常,会让线程终止。上面代码 主线程去终止睡眠中的thread线程。

让线程放弃执行权:

Thread.yield() :该方法是一个静态方法,执行了该方法的线程,放弃本次执行机会,重新去抢CPU时间片。(就像:几个人抢一支笔写字,我抢到了笔,但是我不写字,我又扔回去,然后又继续去抢)

我们可以通过这个方法,完成当达成某种条件的时候,再让该线程往后执行,未达到条件时,就算抢到了CPU时间片我们也不去执行该线程,而是放弃执行的机会。

场景:

 

等待线程死亡join方法:

thread.join() 方法,这是一个成员方法,该线程执行到thread.join()方法,表示该线程需要等到thread线程执行完了或者终止结束了,才接着往下执行。

场景:

//定义线程1执行的任务
public class Task implements Runnable{
    @Override
    public void run() {
        System.out.println("我是线程1");
        System.out.println("这里执行了我的功能代码,睡3秒钟"+ LocalDateTime.now());
        try {
            Thread.sleep(3000);
            System.out.println("我执行完了,我结束了"+LocalDateTime.now());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

//定义线程2执行的任务
public class Task2 implements Runnable{
    private Thread thread;
    public Task2(Thread thread){
        this.thread = thread;
    }
    @Override
    public void run() {
        System.out.println("我是线程2,我开始执行我的任务");
        System.out.println("等待线程1结束");
        try {
            //等待线程1结束
            thread.join();
            System.out.println("线程1结束了,  我也结束了");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

//开启两个线程
public class Test {
    public static void main(String[] args) {
        //创建线程1
        Thread thread1 = new Thread(new Task());
        //创建线程2   将线程1作为参数给到线程2,用于执行join方法。
        Thread thread2 = new Thread(new Task2(thread1));
        //开启线程1
        thread1.start();
        //开启线程2
        thread2.start();
    }
}

运行结果:

守护线程(后台线程)

thread.setDaemon(boolean no)  将线程设置为守护线程。 

true:是守护线程    false:不是守护线程   

thread.isDaemon   判断该线程是不是守护线程。

true:是守护线程    false:不是守护线程   

守护线程的声明周期随着主线程的结束而结束。

使用:

public class DaemonTask implements Runnable{
    @Override
    public void run() {
        try {
            while(true){
                //重复运行中。
                Thread.sleep(100);
                System.out.println("我是守护线程,守护中.....");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class Test {
    public static void main(String[] args) {
        //创建线程
        Thread thread = new Thread(new DaemonTask());
        //设置为守护线程
        thread.setDaemon(true);
        //开启线程
        thread.start();

        boolean daemon = thread.isDaemon();
        System.out.println(daemon);
        try {
            //保持主线程不接受,持续执行中
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

 判断线程是否存活:

thread.isAlive()        判断线程是否存活  true:存活     false:死亡

场景使用:

public class Task implements Runnable{
    @Override
    public void run() {

        try {
            System.out.println("线程开始了");
            Thread.sleep(3000);
            System.out.println("线程结束了");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}
public class Test {
    public static void main(String[] args) {
        //创建线程
        Thread thread = new Thread(new Task());
        //开启线程
        thread.start();
        try {
            //查看线程存活状态
            System.out.println("线程存活状态:"+thread.isAlive());
            //保持主线程不接受,持续执行中
            Thread.sleep(5000);
            //查看线程存活状态
            System.out.println("线程存活状态:"+thread.isAlive());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

线程组

线程组在平时开发中比较少用

二、线程锁

synchronized关键字

synchronized关键字可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。(只有拿到相应的锁的线程,才能进入到方法体内,执行代码,因为锁只有一把,所以每次只能有一个线程拿到锁)

使用形式:

同步代码块

synchronized(object){

//方法体

}

object参数可以是任意的对象,把这个对象当作是一把锁。

同步方法

访问修饰符 synchronized 返回值类型 方法名(参数类型 参数名称){

//方法体

}

静态同步方法

访问修饰符 static synchronized 返回值类型 方法名(参数类型 参数名称){

//方法体

}

三种方法在不同的场景使用,功能都一样,就是方法体内的代码 同一时刻只能有又一个线程执行。

使用场景:

/**
 * 自定义一个任务类,模拟多个线程卖票场景
 * */
public class Task implements Runnable{
    //定义票数
    private int quantity = 10;
    @Override
    public void run() {
        while(quantity>0){
            System.out.println("卖"+quantity+"号票");
            quantity--;
        }
    }
}
public class Test {
    public static void main(String[] args) {
        //创建任务对象
        Task task = new Task();
        //创建线程,装载任务
        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);
        Thread thread3 = new Thread(task);
        //启动线程
        thread1.start();
        thread2.start();
        thread3.start();

    }
}

运行结果:(可能出现卖重复票的,也可能正常卖票,但是我们工作中,是不允许出现重复卖的情况,所以要避免掉。)

 修改:在我们的Task类中添加synchronized关键字。

/**
 * 自定义一个任务类,模拟多个线程卖票场景
 * */
public class Task implements Runnable{
    //定义票数
    private int quantity = 10;
    @Override
    public void run() {
        while(quantity>0){
            synchronized (this){
                System.out.println("卖"+quantity+"号票");
                quantity--;
            }
        }
    }
}

但是出现了新的问题:

 出现原因:

 解决方案,在synchronized内外双重判断。

/**
 * 自定义一个任务类,模拟多个线程卖票场景
 * */
public class Task implements Runnable{
    //定义票数
    private int quantity = 10;
    @Override
    public void run() {
        while(quantity>0){
            synchronized (this){
                if (quantity>0){
                    System.out.println("卖"+quantity+"号票");
                    quantity--;
                }
            }
        }
    }
}

同步锁有哪些?

什么是同步锁

同步锁是为了保证每个线程都能正常执行原子不可更改操作,同步监听对象/同步锁/同步监听器/互斥锁的一个标记锁。

两种类型:1、对象类型     2、类类型

对象类型:

        例如:Student student = new Student();

        Object object = new Object();

        this(指代当前对象)

        synchronized修饰的成员方法 同步锁是该方法的实例对象

        //创建同步锁对象
        Object object = new Object();
        //同步代码块
        synchronized(object){
            
        }

类类型:

        例如:Student.class

        Object.class

        synchronized修饰的静态方法 同步锁是该方法的类

        //同步代码块
        synchronized(Object.class){

        }

同一把锁

/**
 *  定义一个加锁任务,模拟多个线程争夺同一把锁。
 * */
public class Task implements Runnable{
    @Override
    public void run() {
        //这里使用同步代码块为例子,给代码块加上类类型的锁
        synchronized (Task.class){
            try {
                System.out.println("线程开始执行");
                System.out.println("现在的时间是:"+LocalDateTime.now());
                System.out.println("线程睡3秒");
                Thread.sleep(3000);
                System.out.println("线程醒了现在的时间是:"+LocalDateTime.now());
                System.out.println("请下一个线程执行");
                System.out.println("------------------------------------");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Test {
    public static void main(String[] args) {
        //定义两个Task任务实例
        Task task1 = new Task();

        //定义两个Thread实例
        Thread thread1 = new Thread(task1);
        Thread thread2 = new Thread(task1);

        //启动两个线程
        thread1.start();
        thread2.start();
    }
}

运行结果:

死锁是如何产生的?

什么是死锁:

死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去,此时称系统处于死锁状态或者系统产生了死锁,这些永远在互相等待的线程称为死锁。(两个或两个以上的线程争夺彼此的锁,造成阻塞,程序用永远处于阻塞状态)。

图: (开发中,我们就要注意,避免死锁的产生)

 死锁产生的四个条件:

1、两个或两个以上的线程

2、两个或两个以上的锁

3、两个或两个以上的线程持有不用锁

4、持有不同锁的线程争夺对方的锁

代码实现:

/**
 *  定义一个加锁任务,线程1一次拿object1   object2 锁。
 * */
public class Task implements Runnable{
    //定义两个成员变量,在这里也可以称为两把对象锁。
    private Object object1;
    private Object object2;

    //构造方法
    public Task(Object object1,Object object2){
        this.object1 = object1;
        this.object2 = object2;
    }
    @Override
    public void run() {
        //这里使用同步代码块为例子,加上object1 对象锁
        synchronized (object1){
            try {
                System.out.println("Task:我拿到了object1锁了");
                //这个线程去拿object2锁的之前,要确保object2名花有主了
                //所以这里先睡一会,给足时间另外一个任务去拿object2锁。
                Thread.sleep(1000);
                System.out.println("Task:我想拿object2锁");
                synchronized (object2){
                    System.out.println("Task:我拿到了object2锁了");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


/**
 *  定义一个加锁任务,线程2一次拿object2   object1 锁。
 * */
public class Task2 implements Runnable{
    //定义两个成员变量,在这里也可以称为两把对象锁。
    private Object object1;
    private Object object2;

    //构造方法
    public Task2(Object object1,Object object2){
        this.object1 = object1;
        this.object2 = object2;
    }
    @Override
    public void run() {
        //这里使用同步代码块为例子,去拿object2 对象锁
        synchronized (object2){
                System.out.println("Task2:我拿到了object2锁了");
            System.out.println("Task2:我想拿object1锁");
                synchronized (object1){
                    System.out.println("Task2:我拿到了object1锁了");
                }
        }
    }
}
public class Test {
    public static void main(String[] args) {
        //创建两把对象锁
        Object object1 = new Object();
        Object object2 = new Object();
        //定义Task任务实例,和Task2任务实例
        Task task = new Task(object1,object2);
        Task2 task2 = new Task2(object1,object2);
        //定义两个Thread实例
        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task2);
        //启动线程
        thread1.start();
        thread2.start();
    }
}

执行结果:

等待唤醒机制

1、等待(wait)

对象锁.wait() :使当前线程等待,只要被唤醒 ,使用这个方法前提,是当前线程必须拿到这个对象锁。

2、唤醒单个线程(notify)

对象锁.notify():唤醒对象锁上等待的单个线程,使用这个方法前提,是当前线程必须拿到这个对象锁。

3、唤醒所有线程(notifyAll)

对象锁.notifyAll():唤醒对象锁上等待的全部线程,使用这个方法前提,是当前线程必须拿到这个对象锁。

代码实现:

/**
 *  定义一个加锁任务
 * */
public class Task implements Runnable{
    @Override
    public void run() {
        //同步代码块,this锁,指的是当前实例对象就是这把对象锁
        synchronized (this){
            try {
                System.out.println("进入到代码块啦~~");
                System.out.println("进入等待,等待被唤醒");
                //进入到了等待状态
                this.wait();

                //需要被唤醒才能执行一下代码
                System.out.println("线程被唤醒了");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Test {
    public static void main(String[] args) {
        //定义Task任务实例
        Task task = new Task();
        //创建Thread
        Thread thread = new Thread(task);
        //启动
        thread.start();
        try {
            //主线程睡眠一段时间后再唤醒
            Thread.sleep(3000);
            System.out.println("主线程休眠结束,唤醒");
            synchronized (task) {
                task.notify();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

 

wait和sleep的区别

两个方法都会让线程进入到阻塞等待状态,那么它们有什么不同呢?

线程间通讯  (wait、notify应用)

模拟一个场景,生产工厂和消费工厂,生产工厂负责生产产品,消费工厂负责消费产品,但是为了防止产品生产太多了,或者产品生产不够卖了,编写一个程序,使产品的库存保持再在一定范围。

//产品类
public class Data {
    private int sum = 0;
    public int getSum() {
        return sum;
    }
    public void setSum(int sum) {
        this.sum = sum;
    }
}




//生产者任务
public class Producer implements Runnable {
    private Data data;
    public Producer(Data data){
        this.data = data;
    }
    @Override
    public void run() {
        while(true){
            synchronized (data){
            //如果sum>=10  则停止生产
            if (data.getSum()>=10){
                try {
                    //唤醒消费线程
                    data.notify();
                    //停止生产线程,即当前线程
                    data.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else {
                //否则进行生产
                int sum = data.getSum() + 1;
                data.setSum(sum);
                System.out.println("生产者生成了一个产品,当前产品数量为:"+sum);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        }

    }
}


//消费者任务
public class Consumer implements Runnable{
    private Data data;
    public Consumer(Data data){
        this.data = data;
    }
    @Override
    public void run() {
        while (true){
            synchronized (data){
                //如果sum<=0  则停止消费
                if (data.getSum()<=0){
                    try {
                        //唤醒生产线程
                        data.notify();
                        //停止消费线程,即当前线程
                        data.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                    //否则进行消费
                    int sum = data.getSum() - 1;
                    data.setSum(sum);
                    System.out.println("消费者消费了一个产品,当前产品数量为:"+sum);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

    }
}
public class Test {
    public static void main(String[] args) {
        Data data = new Data();
        Thread producerThread = new Thread(new Producer(data));
        Thread consumerThread = new Thread(new Consumer(data));
        producerThread.start();
        consumerThread.start();
    }
}

运行结果: 产品保持在0-10个

Lock(显式锁)

java5.0之后新增了一个同步锁对象,Lock。用于完善synchronized的不足。

Lock具备和synchronized一样的作用,可以实现线程同步,但是比synchronized更加强大,体现在更加灵活,可以自由地获取锁,释放锁。

 

 非阻塞式获取锁:

使用 lock对象.tryLock()  ,尝试获取锁,成功返回true,失败返回false。

中断等待锁的线程:

可中断 & 不可中断

不可中断 :

/**
 *  定义一个加锁任务,验证是否可中断锁的线程
 * */
public class Task implements Runnable{
    //创建一把lock锁,可重入锁
    private Lock lock = new ReentrantLock();
    @Override
    public void run() {
        lock.lock();
        try {
            //睡眠3秒
            Thread.sleep(3000);
            //输出线程名
            System.out.println(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}



public class Test {
    public static void main(String[] args) {
        //创建任务
        Task task = new Task();
        //创建线程
        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);
        //启动线程
        thread1.start();
        thread2.start();
        //睡眠1秒
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //中断线程
        thread2.interrupt();
    }
}

运行结果:

可中断:

Lock#lockInterruptibly

/**
 *  定义一个加锁任务,验证是否可中断锁的线程
 * */
public class Task implements Runnable{
    //创建一把lock锁,可重入锁
    private Lock lock = new ReentrantLock();
    @Override
    public void run() {
        try {
            //这个方法有异常抛出
            lock.lockInterruptibly();
        } catch (InterruptedException e) {
            //抛出异常后执行这里的代码
            e.printStackTrace();
            System.out.println(Thread.currentThread().getName()+"被中断");
            return;
        }
        try {
            //睡眠3秒
            Thread.sleep(3000);
            //输出线程名
            System.out.println(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}



public class Test {
    public static void main(String[] args) {
        //创建任务
        Task task = new Task();
        //创建线程
        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);
        //启动线程
        thread1.start();
        thread2.start();
        //睡眠1秒
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //中断线程
        thread2.interrupt();
    }
}

运行结果:

Lock锁的等待唤醒机制:

那么Lock锁的等待和唤醒机制的相关方法有是什么呢?与synchronized唤醒机制对应的有啥哪些方法?

 演示:

/**
 *  定义一个任务,演示lock的等待和唤醒
 * */
public class Task implements Runnable{
    //创建一把lock锁和一个Condition做对象。这两个对象需要外部提供
    private Lock lock;
    private Condition condition;

    public Task(Lock lock, Condition condition) {
        this.lock = lock;
        this.condition = condition;
    }

    @Override
    public void run() {
        try {
            lock.lock();
            System.out.println("线程进行等待");
            condition.await();
            //线程释放后执行到输出线程名
            System.out.println(Thread.currentThread().getName());
        }catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}



public class Test {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        //创建任务
        Task task = new Task(lock,condition);
        //创建线程
        Thread thread1 = new Thread(task);
        //启动线程
        thread1.start();
        //睡眠3秒
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //需要获取锁
        lock.lock();
        //随机唤醒单个线程
        condition.signal();
        //唤醒全部线程
        //condition.signalAll();
        //释放锁
        lock.unlock();
    }
}

运行结果:

生产者和消费者(Condition应用)

实现:

//生产者任务
public class Producer implements Runnable {
    private Data data;
    public Producer(Data data){
        this.data = data;
    }
    @Override
    public void run() {
        Lock lock = data.getLock();
        while(true){
            try {
                lock.lock();
                if(data.getSum()>=10) {
                    //消费者释放
                    data.getConsumerCondition().signalAll();
                    //表示不需要生产了
                    data.getProducerCondition().await();
                }else {
                    //否则进行生产
                    int sum = data.getSum() + 1;
                    data.setSum(sum);
                    System.out.println("生产者生产了一个产品,当前产品数量为:" + sum);
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }

    }
}



//消费者任务
public class Consumer implements Runnable{
    private Data data;
    public Consumer(Data data){
        this.data = data;
    }
    @Override
    public void run() {
        Lock lock = data.getLock();
        while(true){
            try {
                lock.lock();
                if(data.getSum()<=0) {
                    //生产者释放
                    data.getProducerCondition().signalAll();
                    //表示不需要消费了
                    data.getConsumerCondition().await();
                }else {
                    //否则进行消费
                    int sum = data.getSum() - 1;
                    data.setSum(sum);
                    System.out.println("消费者消费了一个产品,当前产品数量为:" + sum);
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
    }
}


public class Test {
    public static void main(String[] args) {
        Data data = new Data();
        Thread producerThread = new Thread(new Producer(data));
        Thread consumerThread = new Thread(new Consumer(data));
        producerThread.start();
        consumerThread.start();
    }
}

 运行结果:

可重入锁和不可重入锁:

重入:重复进入同步作用域(重复使用同步锁)

 

不可重入锁定义:

public class UnReentranLock implements Lock {
    /**
     * 定义绑定的线程
     */
    private Thread thread;
    @Override
    public void lock() {
        synchronized (this){
            //当已有线程拿到锁
            while(thread!=null){
                try {
                    //使当前线程等待
                    wait();
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
            //绑定当前线程
            this.thread = Thread.currentThread();
        }

    }

    @Override
    public void unlock() {
        synchronized (this){
            //当绑定的线程不是当前线程时
            if(thread!=Thread.currentThread()){
                return;
            }
            //解绑线程
            thread = null;
            //唤醒所有线程
            notifyAll();
        }
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public Condition newCondition() {
        return null;
    }
}

公平 & 非公平锁

公平 :每个线程获取锁的机会是平等的

非公平:每个线程获取锁的机会是不平等的。

1、非公平锁synchronized演示:

/**
 *  定义一个任务
 * */
public class Task implements Runnable{
    @Override
    public void run() {
        while (true){
            synchronized (this){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            }
        }
    }
}



public class Test {
    public static void main(String[] args) {
        //定义一个任务
        Task task = new Task();
        //创建线程
        Thread thread0 = new Thread(task);
        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);
        Thread thread3 = new Thread(task);

        thread0.start();
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

运行结果:当然其他线程也会拿到,只是拿到的几率比上一个拿到锁的线程几率小。

 2、非公平锁ReentrantLock演示

/**
 *  定义一个任务
 * */
public class Task implements Runnable{
    //参数不写  默认是false,即为非公平锁
    private Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true){
            lock.lock();
            try {
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
    }
}



public class Test {
    public static void main(String[] args) {
        //定义一个任务
        Task task = new Task();
        //创建线程
        Thread thread0 = new Thread(task);
        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);
        Thread thread3 = new Thread(task);

        thread0.start();
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

运行结果:

 3、公平锁ReentrantLock演示

/**
 *  定义一个任务
 * */
public class Task implements Runnable{
    //参数写 true  公平锁
    private Lock lock = new ReentrantLock(true);
    @Override
    public void run() {
        while (true){
            lock.lock();
            try {
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
    }
}


public class Test {
    public static void main(String[] args) {
        //定义一个任务
        Task task = new Task();
        //创建线程
        Thread thread0 = new Thread(task);
        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);
        Thread thread3 = new Thread(task);

        thread0.start();
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

运行结果:

4、判断是否是公平锁,但是只能用来ReentrantLock上,不能用在synchronized上

isFair() 方法

public class Test {
    public static void main(String[] args) {
        ReentrantLock lock1 = new ReentrantLock(false);
        ReentrantLock lock2 = new ReentrantLock(true);

        System.out.println(lock1.isFair());
        System.out.println(lock2.isFair());
    }
}

运行结果:

synchronized和Lock的区别:

 

读&写 锁

读锁演示:  

/**
 *  定义一个任务,读写锁
 * */
public class Task implements Runnable{
    //定义读写锁
    private ReadWriteLock lock = new ReentrantReadWriteLock();
    @Override
    public void run() {
        //夺取读锁
        Lock lock = this.lock.readLock();
        lock.lock();
        try {
            //睡眠3秒
            Thread.sleep(3000);
            //输出内容
            System.out.println("时间:"+LocalDateTime.now()+"===="+Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

    }
}



public class Test {
    public static void main(String[] args) {
        //创建任务
        Task task = new Task();
        //创建线程
        Thread thread0 = new Thread(task);
        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);
        //启动线程
        thread0.start();
        thread1.start();
        thread2.start();
    }
}

运行结果:

写锁演示:

/**
 *  定义一个任务,读写锁
 * */
public class Task implements Runnable{
    //定义读写锁
    private ReadWriteLock lock = new ReentrantReadWriteLock();
    @Override
    public void run() {
        //夺取读锁
        Lock lock = this.lock.writeLock();
        lock.lock();
        try {
            //睡眠3秒
            Thread.sleep(3000);
            //输出内容
            System.out.println("时间:"+LocalDateTime.now()+"===="+Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

    }
}



public class Test {
    public static void main(String[] args) {
        //创建任务
        Task task = new Task();
        //创建线程
        Thread thread0 = new Thread(task);
        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);
        //启动线程
        thread0.start();
        thread1.start();
        thread2.start();
    }
}

运行结果:

 

读写锁使用时的互斥关系:

一个读写锁对象,如果需要给一个线程加读锁,会先去看有没有线程在使用它的写锁,如果有线程在使用它写锁,读锁会阻塞,等写锁关闭之后,才能开读锁。(相当于你要获取数据,但是发现有人在改数据,因此就算你现在获取到了数据,说不定别人跟着就改了数据,这样会让你拿到的数据不一定是最新的,所有宁愿等一下吧,等改数据的人改完了数据,我再去读,确保数据是最新的)

如果一个需要给一个线程加写锁,却发现有其他线程用了读锁,那么这个线程也会阻塞,等待读数据操作完毕,读锁释放,然后才给线程加写锁。

总结:先读后读读(不阻塞)  先读后写(阻塞)  先写后读(阻塞)  先写后写(阻塞)。

 

LockSupport:等待唤醒工具类

 演示:

/**
 *  定义一个任务
 * */
public class Task implements Runnable{
    @Override
    public void run() {
        System.out.println("等待前");
        LockSupport.park();
        System.out.println("唤醒了");
    }
}


public class Test {
    public static void main(String[] args) {
        //定义任务
        Task task = new Task();
        //创建线程
        Thread thread = new Thread(task);
        //启动线程
        thread.start();
        try {
            //睡眠3秒
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //唤醒指定的线程
        LockSupport.unpark(thread);


    }
}

运行结果:

 

线程状态以及生命周期:

线程的状态一共有六种:

NEW(新建)、RUNNABLE(就绪)、BLOCKED(阻塞)、WAITING(等待)、TIMED_WAITING(记时等待)、TERMINATED(死亡)

 

 

 

 

 

 

 

ThreadLocal(线程本地变量):

存储在  当前线程  里的变量。 每个线程中的ThreadLocal是分开的。

 结构:

 

 

InheritableThreadLocal<T>(可继承的线程本地变量)

这里的继承指的是   在线程A种创建了线程B,则线程B可获取到线程A的InheritableThreadLocal

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值