11-多线程

本文详细介绍了进程与线程的概念,探讨了线程的生命周期、优先级、休眠及常见方法。深入分析了同步机制,包括synchronized关键字的使用、锁的概念及ReentrantLock类的应用。同时,讲解了死锁现象及线程通信方法,如wait()、notify()和Condition的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

先修

  • 进程:

        1、正在运行的程序,是程序的运行状态和资源占用(内存,CPU)的描述,通过进程ID区分

        2、进程是程序的一个动态过程,它指的是从代码加载到执行完毕的一个完成过程

        单个CPU在任何时间点上,只能运行一个进程,宏观并行,微观串行

        进程的特点:

        a.独立性:不同的进程之间是独立的,相互之间资源不共享(如:两个正在上课的教室有各自的财产,相互之间不共享)

        b.动态性:进程在系统中不是静止不动的,而是在系统中一直活动的

        c.并发性:多个进程可以在单个处理器上同时进行,且互不影响

  • 线程:

        线程就是进程中一条执行路径,是进程的组成部分,一个进程可以有多个线程,每个线程去处理一个特定的子任务

        线程的执行是抢占式的,多个线程在同一个进程中可以并发执行,其实就是CPU快速的在不同的线程之间切换,也就是说,当前运行的线程在任何时候都有可能被挂起,以便另外一个线程可以运行

  • 线程的组成:

        (1)CPU时间片:操作系统会为每个线程分配执行时间

        (2)运行数据:

                堆空间:存储线程需要使用的对象,多个线程可以共享堆中的对象

                栈空间:存储线程需使用的局部变量,每个线程都有独立的栈

        (3)线程的逻辑代码

  • 线程和进程的关系:

        a.一个程序运行后至少有一个进程

        b.一个进程可以包含多个线程,但是至少需要有一个线程,否则这个进程是没有意义的

        c.进程间不能共享资源,但线程之间可以

        d.系统创建进程需要为该进程重新分配系统资源,而创建线程则容易的多,因此使用线程实现多任务并发比多进程的效率高


多线程的实现

  • extends Thread:

        Thread类是所有线程类的父类,实现了对线程的抽取和封装

        继承Thread类创建并启动多线程的步骤:

        a.定义一个类,继承自Thread类,并重写该类的run方法,该run方法的方法体就代表了线程需要完成的任务,因此,run方法的方法体被称为线程执行体

        b.创建Thread子类的对象,即创建了子线程

        c.用线程对象的start方法来启动该线程

        ps:程序运行时会自动创建一个线程,这个线程叫主线程;可以通过主线程创建子线程

public class ThreadTest {

    public static void main(String[] args) {
        //实际的子线程
        MyThread t0 = new MyThread();
        t0.setName("线程000");
        t0.start();
        MyThread t1 = new MyThread();
        t1.setName("线程111");
        t1.start();

        //当前正在执行的线程对象的引用
        Thread thread = Thread.currentThread();
        //Thread[main,5,main]
        //Thread[线程的名字,线程的执行优先级,在哪个线程中创建的]
        System.out.println(thread);

        thread.setName("主线程");
        System.out.println(thread.getName());

        MyThread1 myThread1_instance = new MyThread1("MyThread1 instance");
        myThread1_instance.start();
        myThread1_instance.getState();//RUNNABLE
    }

}

class MyThread extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("hello+" + i);
        }

        Thread thread = Thread.currentThread();
        System.out.println(thread);
        System.out.println(thread.getName());
    }
}

class MyThread1 extends Thread {

    public MyThread1() {
    }

    public MyThread1(String name) {
        super(name);
    }

    @Override
    public void run() {
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName());
    }
}

        运行结果:

public class ThreadTest {

    public static void main(String[] args) {
        //需求:模拟4个售票员售100张票
        SellTickts s1 = new SellTickts();
        SellTickts s2 = new SellTickts();
        SellTickts s3 = new SellTickts();
        SellTickts s4 = new SellTickts();
        s1.start();
        s2.start();
        s3.start();
        s4.start();
    }

}

class SellTickts extends Thread {
    //共享数据
    static int count = 10;//100

    @Override
    public void run() {
        //循环售票
        while (count > 0) {//写+读 非原子性
            count--;//写
            System.out.println(Thread.currentThread().getName() + "售出了一张票,剩余" + count);//读
        }
    }
}

        运行结果:

  • 实现Runnable接口

        实现run方法,通过Thread t=new Thread(Runable target)方法传入子类,多态实现

        实现Runnable接口创建并启动多线程的步骤:

                a.定义一个Runnable接口的实现类,并重写该接口中的run方法,该run方法的方法体同样是该线程的线程执行体

                b.创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象

                c.调用线程对象的start方法来启动该线程

public class ThreadTest {

    public static void main(String[] args) {
        //并不是线程对象
        Check c = new Check();
        //Thread(Runnable target) 分配新的 Thread 对象。
        Thread t0 = new Thread(c);
        Thread t1 = new Thread(c);
        t0.start();//将new Thread()放上面,两线程同时start,i值设大一些,更能看出效果
        t1.start();
    }
}


class Check implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println(i);
        }
    }
}
public class ThreadTest {
    static int count = 100;
    static Runnable r = new Runnable() {//运行效果与继承Thread相同
        @Override
        public void run() {
            while (count > 0) {
                count--;
                System.out.println(Thread.currentThread().getName() + "售出了一张票,剩余" + count);
            }
        }
    };

    public static void main(String[] args) {
        Thread t0 = new Thread(r);
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        Thread t3 = new Thread(r);
        t0.start();
        t1.start();
        t2.start();
        t3.start();
    }
}
  • 两种方式的比较:

        实现Runnable接口的方式:

                a.线程类只是实现了Runnable接口,还可以继承其他类

                b.可以多个线程共享同一个target对象,所以非常适合多线程处理同一份资源的情况

                c.如果要访问当前线程,必须使用Thread.currentThread()

        继承Thread类的方式:

                a.编写简单,如果要访问当前线程,除了可以通过Thread.currentThread()方式之外,还可以使用super关键字

                b.弊端:因为线程类已经继承了Thread类,则不能再继承其他类【单继承】

        实际上大多数的多线程应用都可以采用实现Runnable接口的方式来实现【推荐使用匿名内部类】

  • start方法和run方法区别:

        start()将创建新的线程并执行run()方法里的代码,直接调用run()不会创建线程也不会调用线程的代码

        用start()来启动线程,实现了真正意义上的启动线程,此时会出现异步执行的效果,即线程的创建和启动并不紧密相连
        而如果使用run()来启动线程,就不是异步执行了,而是同步执行,不会达到使用线程的意义


线程的常用方法 

  • 线程休眠:

        使得当前正在执行的线程休眠一段时间,释放时间片,导致线程进入阻塞状态

        sleep(5000):5000的单位是毫秒,设置了sleep就相当于将当前线程挂起5秒,这个操作跟线程的优先级无关,当对应的时间到了之后,还会再继续执行

public class ThreadTest2 {
    static Runnable r = new Runnable() {
        @Override
        public void run() {
            while (true) {
                System.out.println(Thread.currentThread().getName() + "在执行");
                //设置线程休眠
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {

                }
            }
        }
    };

    public static void main(String[] args) {
        //使得当前正在执行的线程休眠一段时间,释放时间片,导致线程进入阻塞状态
        //sleep(5000),5000的单位是毫秒,设置了sleep就相当于将当前线程挂起5s,这个操作跟线程的优先级无关,当对应的时间到了之后,还会再继续执行
        Thread t0 = new Thread(r);
        t0.setName("线程000");
        t0.setPriority(10);
        Thread t1 = new Thread(r);
        t1.setName("线程111");
        t1.setPriority(1);

        t0.start();
        t1.start();
    }
}

        运行结果:(两线程每秒争抢一次CPU时间片,线程000优先级更高)

  • 线程优先级:

        可以通过设置优先级来改变线程抢到时间片的概率,优先级高的线程获得较多的执行机会。默认情况下,每个线程的优先级都与创建它的父线程具有相同的优先级。eg:main线程具有普通优先级,则由main线程创建的子线程也有相同的普通优先级。

        注意:所传的参数范围1~10,默认为5,对应的数值越大,说明优先级越高,这个方法的设定一定要在start之前,线程的优先级低并不意味着争抢不到时间片,只是抢到时间片的概率较低而已

  • 合并线程:

        在执行原来线程的过程中,如果遇到了合并线程,则优先执行合并进来的线程,执行完合并进来的线程后,再回到原来的任务中,继续执行原来的线程

        特点:

                a.线程合并,当前线程一定会释放cpu时间片,cpu会将时间片分给要join的线程

                b.哪个线程需要合并就在当前线程中,添加要合并的线程

                c.join之前,一定要将线程处于准备状态start

public class ThreadTest2 {

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + i);
            if (i == 5){
                //合并线程
                CustomerThread customerThread = new CustomerThread();
                customerThread.start();

                //将需要合并的线程join
                //优先执行合并进来的线程
                try {
                    customerThread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

class CustomerThread extends Thread {
    public CustomerThread() {
    }

    public CustomerThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}

        运行结果:

  • 后台线程:

        隐藏起来一直默默运行的线程,直到进程结束,又被称为守护线程或精灵线程,JVM的垃圾回收线程就是典型的后台线程

        特征:如果所有的前台线程都死亡,后台线程会自动死亡,必须要在start之前执行

public class ThreadTest2 {

    public static void main(String[] args) {
        DaemonThread daemonThread = new DaemonThread();
        //设置后台进程
        daemonThread.setDaemon(true);
        daemonThread.start();

        //主线程的任务
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}

class DaemonThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}

        运行结果:

  • 线程让步:

        可以让当前正在执行的线程暂停,但它不会阻塞该线程,它只是将该线程转入就绪状态,完全可能出现的情况是:当某个线程调用了yield方法暂停之后,线程调度器又将其调度出来重新执行

        实际上,当某个线程调用了yield方法暂停之后,只有优先级与当前线程相同,或者优先级比当前线程更高的就绪状态的线程才会获得执行的机会

public class ThreadTest2 {

    public static void main(String[] args) {
        YieldThread t0 = new YieldThread("线程000");
        YieldThread t1 = new YieldThread("线程111");

        t0.start();        
        t1.start();
    }
}

class YieldThread extends Thread{
    public YieldThread(){}

    public YieldThread(String name){
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"-"+i);
            if(i == 5){
                //线程让步,不会让线程进入阻塞状态
                Thread.yield();
            }
        }
    }
}

        运行结果:


线程的生命周期

        对于线程,当线程被创建并启动之后,它既不是已启动就进入了执行状态,也不是一直处于执行状态,在线程的生命周期中,他会经历各种不同的状态【在一个进程中,多个线程同时运行,是在争抢CPU时间片】

        New(新生):线程被实例化,但是还没有开始执行

        Runnable(就绪):没有抢到时间片

        Running(运行):抢到了时间片,cpu开始处理这个线程中的任务

        Blocked(阻塞):线程在执行过程中遇到特殊情况,使得其他线程就可以获得执行的机会,被阻塞的线程会等待合适的时机重新进入就绪状态

        Dead(死亡):线程终止

                a.run方法执行完成,线程正常结束【正常的死亡】

                b.直接调用该线程的stop方法强制终止这个线程


Synchronized

  • 多线程访问临界资源:

        多线程访问临界资源的数据安全问题:

                产生原因:有多个线程在同时访问同一个资源,如果一个线程在取值的过程中,时间片又被其他线程抢走了,临界资源问题就产生了

                解决:一个线程在访问临界资源的时候,如果给这个资源“上一把锁”,这个时候如果其他线程也要访问这个资源,就得在“锁”外面等待

        锁的划分方式: 1、对象锁和类锁;2、同步代码块和同步方法

  • 对象锁:

        1、锁住实体里的非静态变量;2、直接锁非静态方法;3、锁住 this 对象

        使用对象锁时只有使用同一实例的线程才会受锁的影响,多个实例调用同一方法不会受影响

        当synchronized锁住一个对象后,别的线程如果也想拿到这个对象的锁,就必须等待这个线程执行完成释放锁,才能再次给对象加锁,这样才达到线程同步的目的。即使两个不同的代码段,都要锁同一个对象,那么这两个代码段也不能在多线程环境下同时运行

        所以我们在用synchronized关键字的时候,能缩小代码段的范围就尽量缩小,能在代码段上加同步就不要再整个方法上加同步。这叫减小锁的粒度,使代码更大程度的并发。

class ObjLock {
    private Object lock = new Object();

    //锁住非静态变量(变量必须为对象)
    public void lockObjectField() throws InterruptedException {
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName());
            Thread.sleep(2 * 1000);
        }
    }

    //锁住this对象 this就是当前对象实例
    public void lockThis() throws InterruptedException {
        synchronized (this) {
            System.out.println(Thread.currentThread().getName());
            Thread.sleep(2 * 1000);
        }
    }

    //直接锁住非静态方法
    public synchronized void methodLock() throws InterruptedException {
        System.out.println(Thread.currentThread().getName());
        Thread.sleep(2 * 1000);
    }

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            //5个ObjectLockWorker中的 5个ObjLock中的 5个lock/this
            //本质上 不是同一把锁,而是5个锁
            Thread worker = new Thread(new ObjectLockWorker());
            worker.setName("kite-" + i);
            worker.start();
        }
    }

    public static class ObjectLockWorker implements Runnable {
        @Override
        public void run() {
            try {
                ObjLock objLock = new ObjLock();
                // 方式 1
                objLock.lockObjectField();
                // 方式 2
//                objLock.lockThis();
                // 方式 3
//                objLock.methodLock();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果:

        

        每个线程立刻输出线程名称,然后各自休眠 2 秒,之后程序执行结束

        分别调用方式1、2、3,效果都是一样的

  • 类锁:

        1、锁住类中的静态变量;2、直接在静态方法上加 synchronized;3、锁住 xxx.class

        类锁是所有线程共享的锁,所以同一时刻,只能有一个线程使用加了锁的方法或方法体,不管是不是同一个实例

class ClazLock {
    private static Object lock = new Object();

    //锁住静态变量(变量必须为对象)
    public void lockStaticObjectField() throws InterruptedException {
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName());
            Thread.sleep(2 * 1000);
        }
    }

    //锁住静态方法
    public static synchronized void methodLock() throws InterruptedException {
        System.out.println(Thread.currentThread().getName());
        Thread.sleep(2 * 1000);
    }

    //锁住 xxx.class
    public void lockClass() throws InterruptedException {
        synchronized (ClazLock.class) {
            System.out.println(Thread.currentThread().getName());
            Thread.sleep(2 * 1000);
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            Thread worker = new Thread(new ClassLockWorker());
            worker.setName("kite-" + i);
            worker.start();
        }
    }

    public static class ClassLockWorker implements Runnable {
        @Override
        public void run() {
            try {
                ClazLock classLock = new ClazLock();
                // 方式 1
                classLock.lockStaticObjectField();
                // 方式 2
//                ClazLock.methodLock();
                // 方式 3
//                classLock.lockClass();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

        最开始只有1个线程抢到锁,然后输出线程名,等待 2 秒后下一个抢到锁的线程输出线程名,继续等待 2 秒,直到最后一个抢到锁的线程

        分别调用方式1、2、3, 效果都是一样的

  •  同步代码块:

        a.程序走到代码段中,就用锁来锁住了临界资源,这个时候,其他线程不能执行代码段中的代码,只能在锁外边等待

        b.执行完代码段中的这段代码,会自动解锁,然后剩下的其他线程开始争抢cpu时间片

        c.一定要保证不同的线程看到的是同一把锁,否则同步代码块没有意义

class LockDemo{
    //临界资源  10张
    static int count = 10;

    //任何对象都可以充当一个对象锁
    //static Object obj = new Object();
    static String str = new String();

    static Runnable r = new Runnable(){
        @Override
        public void run() {
            while (count > 0){
                
                //使用对象做类锁
                synchronized (str){//加锁后用锁给线程做了排队,使 写和读 原子性
                //使用类做类锁
                //synchronized (Math.class) //任何类,包含自定义的类都可以充当一把类锁 LockClazDemo.class
                    
                    if(count <= 0){//未抢到锁的线程在while内 锁外,避免count为0后线程进入强制消费
                        return;
                    }
                    count--;//写
                    System.out.println("售票员"+Thread.currentThread().getName()+"售出一张票,余额为"+count);//读
                }
            }
        }
    };

    /**
     * 售票的过程中,出现负数的原因:
     *  当其中的一个线程抢到时间片进入到锁中,
     *  这时,其他剩余的三个线程都在while里面,同步代码块的外面
     *  当最后一张票被售完之后,其他剩余的三个线程再次进入同步代码块进行强制售票,出现了负数
     *  解决办法:在锁里面进行判断
     */
    public static void main(String[] args) {
        Thread t0 = new Thread(r, "歪比歪比");
        Thread t1 = new Thread(r, "歪比巴布");
        Thread t2 = new Thread(r, "玛卡巴卡");
        Thread t3 = new Thread(r, "依古比古");
        t0.start();
        t1.start();
        t2.start();
        t3.start();
    }
}

        运行结果:

                        

  • 同步方法:
public class ThreadSyncTest {
    //临界资源  100张
    static int count  = 100;

    static Runnable r = new Runnable() {
        @Override
        public void run() {
            while (count > 0){
                sellTickets();
            }
        }

        //同步方法,作用和同步代码块一样
        public synchronized void sellTickets(){//把握好“同一把锁”概念
            if(count <= 0){
                return;
            }
            count--;
            System.out.println("售票员"+Thread.currentThread().getName()+"售出一张票,余额为"+count);
        }
    };

    public static void main(String[] args) {
        Thread t0 = new Thread(r, "歪比歪比");
        Thread t1 = new Thread(r, "歪比巴布");
        Thread t2 = new Thread(r, "玛卡巴卡");
        Thread t3 = new Thread(r, "依古比古");

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

}

 synchronized深入理解

  • 脏读:

        同步代码与非同步代码并发执行

public class TestS {
    public synchronized void m1() {
        System.out.println(Thread.currentThread().getName() + " m1 start...");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " m1 end");
    }

    public void m2() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " m2 ");
    }

    public static void main(String[] args) {
        TestS t = new TestS();
        new Thread(t::m1, "t1").start();
        new Thread(t::m2, "t2").start();
    }
}
//        t1 m1 start...
//        t2 m2
//        t1 m1 end

         脏读示例:

public class Account {
	String name;
	double balance;
	
	public synchronized void set(String name, double balance) {
		this.name = name;
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		this.balance = balance;
	}
	
	public /*synchronized*/ double getBalance(String name) {
		return this.balance;
	}
	
	
	public static void main(String[] args) {
		Account a = new Account();
		new Thread(()->a.set("zhangsan", 100.0)).start();

		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(a.getBalance("zhangsan"));

		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(a.getBalance("zhangsan"));
	}
}
//		0.0
//		100.0
  • 可重入

        synchronized为可重入锁

public class T {
	synchronized void m1() {
		System.out.println("m1 start");
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		m2();
		System.out.println("m1 end");
	}
	
	synchronized void m2() {
		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("m2");
	}

	public static void main(String[] args) {
		new T().m1();
	}
}
//    m1 start
//    m2
//    m1 end

        synchronized必须可重入,否则在调用中会出现死锁问题

        synchronized如果不可重入,在父子类继承关系的调用中就会出现死锁问题

public class T {
	synchronized void m() {
		System.out.println("m start");
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("m end");
	}
	
	public static void main(String[] args) {
		new TT().m();
	}
	
}

class TT extends T {
	@Override
	synchronized void m() {
		System.out.println("child m start");
		super.m();
		System.out.println("child m end");
	}
}
//		child m start
//		m start
//		m end
//		child m end
  • 锁与异常:
/**
 * 程序在执行过程中,如果出现异常,默认情况锁会被释放
 * 所以,在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况。
 * 比如,在一个web app处理过程中,多个servlet线程共同访问同一个资源,这时如果异常处理不合适,
 * 在第一个线程中抛出异常,其他线程就会进入同步代码区,有可能会访问到异常产生时的数据。
 * 因此要非常小心的处理同步业务逻辑中的异常
 */
public class T {
	int count = 0;
	synchronized void m() {
		System.out.println(Thread.currentThread().getName() + " start");
		while(true) {
			count ++;
			System.out.println(Thread.currentThread().getName() + " count = " + count);
			try {
				TimeUnit.SECONDS.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			if(count == 5) {
				int i = 1/0; //此处抛出异常,锁将被释放,要想不被释放,可以在这里进行catch,然后让循环继续
				System.out.println(i);
			}
		}
	}
	
	public static void main(String[] args) {
		T t = new T();
		Runnable r = new Runnable() {
			@Override
			public void run() {
				t.m();
			}
		};

		new Thread(r, "t1").start();
		
		try {
			TimeUnit.SECONDS.sleep(3);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		new Thread(r, "t2").start();
	}
	
}

  • 锁粒度的细化:
public class FineCoarseLock {
	int count = 0;

	synchronized void m1() {
		//do sth need not sync
		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
		count ++;
		
		//do sth need not sync
		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	void m2() {
		//do sth need not sync
		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
		//采用细粒度的锁,可以使线程争用时间变短,从而提高效率
		synchronized(this) {
			count ++;
		}
		//do sth need not sync
		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
  • 锁对象:

        当使用对象作为锁时,对象的属性可以更改,但对象本身不能更改(锁在对象首部字节码中)

public class SyncSameObject {
	/*final*/ Object o = new Object();

	void m() {
		synchronized(o) {
			while(true) {
				try {
					TimeUnit.SECONDS.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName());
			}
		}
	}
	
	public static void main(String[] args) {
		SyncSameObject t = new SyncSameObject();
		//启动第一个线程
		new Thread(t::m, "t1").start();
		
		try {
			TimeUnit.SECONDS.sleep(3);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//创建第二个线程
		Thread t2 = new Thread(t::m, "t2");
		
		t.o = new Object(); //锁对象发生改变,所以t2线程得以执行,如果注释掉这句话,线程2将永远得不到执行机会
		
		t2.start();
	}
}
  • Synchronized不能锁:

        String常量,Integer、Long等基本数据类型 

public class DoNotLockString {
	String s1 = "Hello";
	String s2 = "Hello";

	void m1() {
		synchronized(s1) {
			
		}
	}
	
	void m2() {
		synchronized(s2) {
			
		}
	}
}

ReentrantLock类

        通过显式定义同步锁对象来实现同步,同步锁提供了比 synchronized 代码更广泛的锁定操作

        注意:最好将 unlock 的操作放到 finally 块中

        通过使用 ReentrantLock 这个类来进行锁的操作,它实现了 Lock 接口,使用 ReentrantLock可以显式地加锁、释放锁

public class ReentrantLockDemo {
    // 临界资源
    static int count = 100;
    // 定义一个ReentrantLock类的对象
    static ReentrantLock lock = new ReentrantLock();

    static Runnable r = new Runnable() {
        @Override
        public void run() {
            while (count > 0) {
                //加锁
                lock.lock();

                if (count <= 0) {
                    return;
                }
                count--;
                System.out.println("售票员" + Thread.currentThread().getName() + "售出一张票,余 额为" + count);

                //解锁
                lock.unlock();

                //注:lock() 和 unlock() 都是成对出现的
            }

        }
    };

    public static void main(String[] args) {//执行结束进程不结束
        Thread t0 = new Thread(r, "喜羊羊");
        Thread t1 = new Thread(r, "沸羊羊");
        Thread t2 = new Thread(r, "灰太狼");
        Thread t3 = new Thread(r, "小灰灰");
        t0.start();
        t1.start();
        t2.start();
        t3.start();
        System.out.println("hello");
    }
}
public class ReentrantLockTest {
    //需求:有一张银行卡,两个线程向其中存钱,两个线程取钱
    /**
     * 临界资源:银行卡
     * 注意:要保证临界资源的同步性
     */

    //存钱线程的target
    static Runnable r0 = new Runnable() {
        @Override
        public void run() {
            while (true) {
                //获取单例对象
                CardDemo cardDemo = CardDemo.currentInstance();
                //调用storeMoney方法
                cardDemo.storeMoney(500);
            }
        }
    };

    static Runnable r1 = new Runnable() {
        @Override
        public void run() {
            while (true) {
                //获取单例对象
                CardDemo cardDemo = CardDemo.currentInstance();
                //调用getMoney方法
                cardDemo.getMoney(1000);
            }
        }
    };

    public static void main(String[] args) {
        //存钱线程
        Thread t0 = new Thread(r0, "歪比歪比");
        Thread t1 = new Thread(r0, "歪比巴布");

        //取钱线程
        Thread t2 = new Thread(r1, "玛卡巴卡");
        Thread t3 = new Thread(r1, "依古比古");

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

//银行卡类 -- 使用单例
class CardDemo {
    //实例化一个私有的静态的当前类的实例
    private static CardDemo instance = new CardDemo();

    //余额
    private int rest = 1000;

    //实例化一个ReentrantLock对象
    ReentrantLock lock = new ReentrantLock();

    //私有化构造方法
    private CardDemo() {}

    //提供一个对外的开放方法,将实例返回
    public static CardDemo currentInstance() {
        return instance;
    }

    public int getRest() {
        return rest;
    }

    public void setRest(int rest) {
        this.rest = rest;
    }

    //存钱
    public void storeMoney(int num) {
        //加锁
        lock.lock();

        this.rest += num;
        System.out.println(Thread.currentThread().getName() + "存了" + num + ",余额为:" + rest);

        //解锁
        lock.unlock();
    }

    //取钱
    public void getMoney(int num) {
        //加锁
        lock.lock();

        if (num > rest) {
            num = rest;
        }
        this.rest -= num;
        System.out.println(Thread.currentThread().getName() + "取了" + num + ",余额为:" + rest);

        //解锁
        lock.unlock();
    }

}

        运行结果:

  • ReentrantLock对比synchronized:

通过一个故事理解可重入锁的机制 - 小勇DW3 - 博客园

        1、都是独占锁(互斥锁),synchronized 加锁和解锁的过程自动进行,易于操作,但不够灵活;ReentrantLock 加锁和解锁的过程需要手动进行,不易操作,但非常灵活

        2、都可重入,且 synchronized 因为加锁和解锁自动进行,不必担心最后是否释放锁;但加锁和解锁需要手动进行,且次数需一样,否则其他线程无法获得锁

        3、synchronized 不可响应中断,一个线程获取不到锁就一直等着;ReentrantLock可以响应中断

        4、synchronized 为非公平锁,ReentrantLock 无参构造默认为非公平锁,但可实现公平锁(谁等的时间最长,谁就先获取锁;new ReentrantLock(true))


死锁

        每个人都拥有其他人需要的资源,同时又等待其他人拥有的资源,并且每个人在获得所有需要的资源之前都不会放弃已经拥有的资源

        当多个线程完成功能需要同时获取多个共享资源的时候可能会导致死锁

public class DeadLock extends Thread {

    //声明两个对象,作为两个锁对象
    static Object o1 = new Object();
    static Object o2 = new Object();
    //设计一个布尔变量
    static boolean boo = true;

    @Override
    public void run() {
        if (boo) {
            synchronized (o1) {
                System.out.println(currentThread().getName() + "获取了第一个锁对象o1,等待第二个锁对象o2");
                boo = !boo;
                try {
                    sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o2) {
                    System.out.println(currentThread().getName() + "获取了两个锁对象o1和o2,即将结束并释放两个锁对象");
                }
            }
        } else {
            synchronized (o2) {
                System.out.println(currentThread().getName() + "获取了第一个锁对象o2,等待第二个锁对象o1");
                boo = !boo;
                try {
                    sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o1) {
                    System.out.println(currentThread().getName() + "获取了两个锁对象o2和o1,即将结束并释放两个锁对象");
                }
            }
        }
    }
}

class Test {
    public static void main(String[] args) {
        DeadLock d1 = new DeadLock();
        d1.setName("线程1");
        DeadLock d2 = new DeadLock();
        d2.setName("线程2");
        d1.start();
        Thread.currentThread().sleep(1000);//使d2准确进入else部分触发死锁
        d2.start();
    }
}

        运行结果:


 多线程在单例中的应用

  • 多线程访问单例 -- 饿汉式:
public class SigletonDemo {

    static HashSet<King> hs = new HashSet<>();
    static Runnable r = new Runnable() {
        @Override
        public void run() { //获取单例对象
            King king = King.currentInstance();
            //将获取到的单例对象添加到集合中
            hs.add(king);
        }
    };

    public static void main(String[] args) {
        //需求:多条线程同时去获取单例对象,然后将获取到的单例对象添加到HashSet中
        // 创建多个线程对象,同时访问 单例对象
        for (int i = 0; i < 10000; i++) {
            Thread thread = new Thread(r);
            thread.start();
        }
        System.out.println(hs);//[King()]
    }
}

@ToString
class King {
    private static King instance = new King();

    private King() {
    }

    public static King currentInstance() {
        return instance;
    }
}
  •   多线程访问单例 -- 懒汉式:
public class SigletonDemo {

    static HashSet<Queen> hs = new HashSet<>();
    static Runnable r = new Runnable() {
        @Override
        public void run() {
            // 获取单例对象
            Queen queen = Queen.currentInstance();
            // 将获取到的单例对象添加到集合中
            hs.add(queen);
        }
    };

    public static void main(String[] args) {
        // 需求:多条线程同时去获取单例对象,然后将获取到的单例对象添加到HashSet中
        // 创建多个线程对象,同时访问 单例对象
        for (int i = 0; i < 10000; i++) {
            Thread thread = new Thread(r);
            thread.start();
        }
        System.out.println(hs);//[Queen()]
    }
}

@ToString
class Queen {
    private static Queen instance;

    private Queen() {}

    //使用同步代码块,同步方法,以及同步锁都可以解决
    public synchronized static Queen currentInstance() {
        if (instance == null) {
            instance = new Queen();
        }
        return instance;
    }
}

线程的通信

        生产者与消费者设计模式:它描述的是有一块缓冲区作为仓库,生产者可以将产品放入仓库,消费者可以从仓库中取走产品,解决生产者/消费者问题,我们需要采用某种机制保护生产者和消费者之间的同步

        同步问题核心在于:如何保证同一资源被多个线程并发访问时的完整性,常用的方法就是加锁,保证资源在任意时刻只被一个线程访问

  • 方式一:采用wait()、notify() 和 notifyAll() 方法

        wait():当缓冲区已满或空时,生产者/消费者 线程停止自己的执行,放弃锁,使自己处于等待状态,让其他线程执行

                表示释放 对象 这个锁标记,然后在锁外边等待(对比sleep(),sleep是抱着锁休眠的)

                等待,必须放到同步代码段中执行

        notify():当 生产者/消费者 向缓冲区 放入/取出一个产品时,向其他等待的线程发出可执行的通知,同时放弃锁,使自己处于等待状态

                表示唤醒 对象 所标记外边在等待的一个线程

        notifyAll():全部唤醒

                表示唤醒 对象 所标记外边等待的所有线程
public class ProductAndCustomer {

    //定义一个标记,用来表示产品是否需要被生产
    static boolean shouldProduct = true;

    //静态成员内部类
    static class Productor implements Runnable{
        //需要生产的产品
        private Product p;

        public Productor(Product p){
            this.p = p;
        }

        @Override
        public void run() {
            while (true){
                synchronized (""){
                    if(shouldProduct){
                        //生产
                        this.p.setName("老婆饼");
                        System.out.println("生产者"+Thread.currentThread().getName()+"生产了一件产品"+this.p);

                        //修改状态
                        shouldProduct = false;

                        //通知消费者来进行消费
                        "".notifyAll();
                    }else {
                        //等待
                        try {
                            "".wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

    //消费者线程的target
    static class Consumer implements Runnable{
        //需要被消费的产品
        private Product p;

        public Consumer(Product p){
            this.p = p;
        }

        @Override
        public void run() {
            while (true){
                synchronized (""){
                    if(shouldProduct){
                        try {
                            "".wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }else {
                        //消费
                        System.out.println("消费者"+Thread.currentThread().getName()+"消费了一件产品"+this.p);

                        //修改状态
                        shouldProduct = true;

                        //通知生产者去生产
                        "".notifyAll();
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        //如果锁外面只有一个生产者或者一个消费者等待的话,则使用notify唤醒
        //如果锁外面有多个生产者或者多个消费者等待的话,则使用notifyAll唤醒

        //实例化一个产品
        Product product = new Product();

        //生产者
        Productor productor = new Productor(product);
        Thread t0 = new Thread(productor, "歪比歪比");
        Thread t1 = new Thread(productor, "歪比巴布");
        Thread t2 = new Thread(productor, "玛卡巴卡");
        Thread t3 = new Thread(productor, "依古比古");

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

        //消费者
        Consumer consumer = new Consumer(product);
        Thread thread0 = new Thread(consumer, "定个小目标王爸爸");
        Thread thread1 = new Thread(consumer, "脸盲刘爸爸");
        Thread thread2 = new Thread(consumer, "福报企业家马爸爸");
        Thread thread3 = new Thread(consumer, "充钱为王马爸爸");

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

class Product{
    private String name;
    int id;

    public void setName(String name){
        this.name = name;
        id++;
    }

    @Override
    public String toString() {
        return "Product{" +
                "name='" + name + '\'' +
                ", id=" + id +
                '}';
    }
}

        运行结果:

  • 方式二:采用ReentrantLock类中的newCondition()方法结合Condition类中的await()、signal、signalAll()方法
package practice.thread;

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

/**
 * prudence
 * 2020/5/13 10:34 上午
 */
public class ProductAndCustomerDemo {
    // 定义一个标记,用来表示产品是否需要被生产
    static boolean shouldProduct = true;

    static ReentrantLock lock = new ReentrantLock();

    // 监视器:生产者
    static Condition c1 = lock.newCondition();
    // 监视器:消费者
    static Condition c2 = lock.newCondition();

    //生产者线程的target
    static class Productor implements Runnable {
        private Product p;

        public Productor(Product p) {
            this.p = p;
        }

        @Override
        public void run() {
            while (true) {
                //加锁
                lock.lock();

                if (shouldProduct) {
                    //生产
                    this.p.setName("老婆饼");
                    System.out.println("生产者" + Thread.currentThread().getName() + "生产了一件产品" + this.p);

                    //修改状态
                    shouldProduct = false;

                    //通知消费者来进行消费
                    c2.signal();
                } else {
                    //等待
                    try {
                        c1.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //解锁
                lock.unlock();
            }
        }
    }

    //消费者线程的target
    static class Consumer implements Runnable {
        //需要被消费的产品
        private Product p;

        public Consumer(Product p) {
            this.p = p;
        }

        @Override
        public void run() {
            while (true) {
                //加锁
                lock.lock();

                if (shouldProduct) {
                    try {
                        c2.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    //消费
                    System.out.println("消费者" + Thread.currentThread().getName() + "消费了一件产品" + this.p);

                    //修改状态
                    shouldProduct = true;

                    //通知生产者去生产
                    c1.signalAll();
                }
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        //实例化一个产品
        Product product = new Product();

        //生产者
        ProductAndCustomerDemo.Productor productor = new ProductAndCustomerDemo.Productor(product);
        Thread t0 = new Thread(productor, "歪比歪比");
        Thread t1 = new Thread(productor, "歪比巴布");
        Thread t2 = new Thread(productor, "玛卡巴卡");
        Thread t3 = new Thread(productor, "依古比古");

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

        //消费者
        ProductAndCustomerDemo.Consumer consumer = new ProductAndCustomerDemo.Consumer(product);
        Thread thread0 = new Thread(consumer, "定个小目标王爸爸");
        Thread thread1 = new Thread(consumer, "脸盲刘爸爸");
        Thread thread2 = new Thread(consumer, "福报企业家马爸爸");
        Thread thread3 = new Thread(consumer, "充钱为王马爸爸");

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

        运行结果:

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值