Java中的线程(线程同步、线程死锁、Lock锁的使用)

线程同步

概述

并发与并行

1、并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事情。

2、并发:一个CPU(采用时间片)同时执行多个任务,同一个对象被多个线程操作。比如:买票,取款等等。

多线程同步

1、多个线程同时读写同一份共享资源时,可能会引起冲突。所以引入线程“同步”机制,即各线程间要有个先来后到。

2、同步 = 排队 + 锁

3、几个线程之间要排队,一个个对共享资源进行操作,而不是同时进行操作。

4、为了保证数据在方法中被访问时的正确性,在访问时加入锁机制。

实现同步

1、为了确保一个时间点只有一个线程访问共享资源。可以给共享资源加一把锁,这把锁只有一把钥匙。哪个线程获取了这把钥匙,才有权力访问该共享资源。

2、一个线程持有锁会导致其它所有需要此锁的线程挂起;在多线程的竞争下,加锁、释放锁会导致比较多的上下文切换和调度延迟时,将引起性能问题。

同步代码块

有synchronized关键字修饰的语句块。被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。

synchronized(同步监视器){
	//需要被同步的代码;
}

代码示例——实现卖票

public class ThreadTicket {
    /*这是一个程序入口*/
    public static void main(String[] args) {
        Ticket t1 = new Ticket();
        t1.setName("窗口1");
        t1.start();

        Ticket t2 = new Ticket();
        t2.setName("窗口2");
        t2.start();
    }
}

class Ticket extends Thread {
    //共享资源
    static int num = 10;
    //同步锁对象
    static Object obj = new Object();
    @Override
    public void run() {
        while (true) {
            //同步代码块
            //同步对象,可以是任意对象,只能是唯一的对象
            //当一个线程持有同步锁后,进入同步代码块,当此线程将同步代码块的中的所有内容执行完后才会释放
            synchronized (obj){
                if (num > 0) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "剩余票数:" + num);
                    num--;
                } else {
                    System.out.println("票已售完");
                    break;
                }
            }

        }

    }
}
同步方法

synchronized关键字修饰的方法。由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

public synchronized void show(String name){
	//需要同步的代码;
}

代码示例——实现卖票

public class RunnableTicket {
    
    
    /*这是一个程序入口*/
    public static void main(String[] args) {
    
    
        TicketWindow ticket = new TicketWindow();
        Thread t1 = new Thread(ticket, "窗口1");
        t1.start();

        Thread t2 = new Thread(ticket, "窗口2");
        t2.start();
    }
}

class TicketWindow implements Runnable{
    
    
    int num = 10;

    //同步方法---只能在实现Runnable接口的类中实现
    public synchronized void printNum(){
    
    
        if (num > 0) {
    
    
            try {
    
    
                Thread.sleep(50);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "剩余票数:" + num);
            num--;
        }
    }
    @Override
    public void run() {
    
    
        while(true){
    
    
            if (num == 0) {
    
    
                System.out.println("票已售完");
                break;
            }
            printNum();
        }
    }
}

注意:synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。

同步监视器

同步监视器可以是任何对象,必须保证唯一,保证多个线程获得同一个对象(锁)

同步监视器的执行过程

1、第一个线程访问,锁定同步监视器,执行其中的代码。

2、第二个线程访问,发现同步监视器被锁定,无法访问。

3、第一个线程访问完毕,解锁同步监视器。

4、第二个线程访问,发现同步监视器没有锁,然后锁定并访问。

线程死锁

出现死锁的前提

1、多线程。

2、线程同步。

死锁描述

1、不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己所需要的同步资源,这就形成了线程的死锁。

2、出现死锁后,不会出现异常,也不会出现提示,只是所有的线程都处于阻塞状态,无法继续。

避免死锁

1、让程序每次至多获得一个锁。当然,在多线程环境下,这种情况通常是不现实的。

2、设计是要考虑清楚锁顺序,尽量减少嵌套的加锁交互数量。

代码示例——出现死锁

public class DealLockDemo {
    
    
    /*这是一个程序入口*/
    public static void main(String[] args) {
    
    
        DealLock t1 = new DealLock(true);
        DealLock t2 = new DealLock(false);
        t1.start();
        t2.start();
    }
}

class DealLock extends Thread {
    
    
    boolean flag;

    public DealLock(boolean flag) {
    
    
        this.flag = flag;
    }

    //锁objA
    static Object objA = new Object();
    //锁objb
    static Object objb = new Object();

    @Override
    public void run() {
    
    
        /*
        * 当线程t1运行拿到锁objA进入if后,
        * 线程t2运行拿到锁objb进入else,
        * 此时if和else中被嵌套的锁就不能够拿到相应的钥匙,这就陷入了死锁
        */
        if (flag){
    
    
            synchronized (objA) {
    
    
                System.out.println("if objA");
                synchronized (objb){
    
    
                    System.out.println("if objb");
                }
            }

        }else{
    
    
            synchronized (objb) {
    
    
                System.out.println("else objb");
                synchronized (objA) {
    
    
                    System.out.println("else objA");
                }
            }
        }
    }
}

在这里插入图片描述

Lock(锁)

概述

1、从JDK5.0开始,Java就提供了更强大的线程同步机制-通过显示定义同步锁对象来实现同步。同步锁使用Lock对象充当。

2、java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。

3、锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。

4、ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReenactmentLock,可以显示的加锁、释放锁。

5、Lock锁,使用时手动获取锁和释放锁,比synchronized更加灵活,可以中断的获取锁,也可以超时获取锁。

Lock的使用

1、lock():获得锁。

2、unlock():释放锁。

Lock锁最基本的用法就是lock()方法上锁,unlock()方法解锁。

3、lockInterruptibly():获得锁,可以中断。线程AB同时通过此方法获得某个锁时,如果线程A获得锁了,按理说线程B只能等待,但是这里的线程B可以调用interrupt()方法中断线程B的等待。

4、tryLock():获取空闲的锁,没有获取到锁不会等待。

代码示例:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author 蔡伟东
 * @create 2020-12-15 22:54
 * @function
 */
public class LockDemo {
    
    
    /*这是一个程序入口*/
    public static void main(String[] args) {
    
    
        TicketWindow ticket = new TicketWindow();
        Thread t1 = new Thread(ticket, "窗口1");
        t1.start();

        Thread t2 = new Thread(ticket, "窗口2");
        t2.start();
    }
}

class TicketWindow implements Runnable{
    
    
    int num = 10;

    //创建一个锁对象
    Lock l = new ReentrantLock();

    @Override
    public void run() {
    
    
        while(true){
    
    
            try{
    
    
                /*上锁*/
                l.lock();
                if (num > 0) {
    
    
                    try {
    
    
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "剩余票数:" + num);
                    num--;
                }else {
    
    
                    System.out.println("票已售完");
                    break;
                }
            }finally {
    
    
                /*解锁*/
                l.unlock();
            }
        }
    }
}

Lock和synchronized的区别

1、Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放。

2、Lock只有代码块锁,synchronized有代码块锁和方法锁,使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且Lock具有更好的扩展性(提供更多子类)。

猜你喜欢

转载自blog.csdn.net/Lotus_dong/article/details/111296638