逸学java【初级菜鸟篇】11.多线程【多方位详解】

  1. hi,我是逸尘,一起学java吧

目标(任务驱动)

本章没有任务目标,略述概述多线程一隅,全是精华。

线程

进程

在提到线程是什么之前我们还需要提到另一个名词 他就是进程

  • 进程一个内存中运行的应用程序,每个进程都有⼀个独立的内存空间,⼀个应用程序可以同时运行多个进程;
  • 进程也是程序的⼀次执⾏过程,是系统运行程序的基本单位;
  • 系统运行⼀个程序即是 ⼀个进程从创建、运行到消亡的过程。

我们可以打开我们的任务管理器

 我们通俗所说的一个正在运行的程序,其实它就可以说是一个进程。

线程

在上面学习了每个进程都有⼀个独立的内存空间,可以有同时运行很多线程的能力,线程就包含在进程里,那么线程就是一个进程的子概念。

  • 线程(thread)一个程序内部 一条执行路径
  • 是操作系统能够进行运算调度的最小单位。
  • 我们之前启动程序执行后,main方法的执行其实就是一条单独的执行路径。

进程和线程的关系 

多线程

多线程是指从软硬件上实现多条执行流程的技术(也可以单说是在程序里可以”同时“运行多个不同的任务的技术)这里的“同时”需要加上双引号。

简单的说,程序同时完成很多事情时,就是多线程。

我们为什么需要多线程

前面的内容中我们学习的所有代码都是顺序执行的(跳转的不算),一个任务完成之后才去执行下一个,但是说代码源于生活,我们在日常生活中,并不是这样呆板的活着的(也不可能),比如我们先呼吸一下,然后血液循环,然后说话,然后思考。我们一般是都是一并发生的,呼吸的同时可以血液循环可以说话,正如屏幕前看文章的你是可以呼吸的,是可以同时发生的,你可以一边呼吸一边思考我这句话是不是正确的。

在java中同样,需要这样的一并发生的设计处理,比如,一边实现服务的文上传一边下载,,需要协同处理,所以我们java中支持多线程,我们需要多线程。

ps:并不是所有的语言都支持多线程

并发

可以”同时“运行多个不同的任务(活动)这样的思想也被成为并发

多线程就一种并发编程的技术。

但是需要了解的是我们的并发并不是真正意义上的同时它是伪同时

ps:那是因为早期计算机的 CPU 都是单核的,一个 CPU 在同一时间只能执行一个进程/线程,当系统中有多个进程/线程等待执行时,CPU 只能执行完一个再执行下一个,为了解决所谓同时问题(并发),只能通过一种算法将 CPU 资源合理地分配给多个任务,轮询为系统的每个线程服务, 由于cpu切换的速度很快,给我们的感觉这些线程在同时执行,这就是真正的并发。

简单的说就是语言层面可以实现同时,但是我们早期电脑的单核cpu无法从技术满足。

并行

并行则是针对多核 CPU 提出的。和单核 CPU 不同,多核 CPU 真正实现了“同时执行多个任务。

真同时

我们现在的多线程是并发还是并行

因为多线程在单核下,多线程必定是并发的,不过现在的统一进程的多线程是可以运行在多核CPU下,所以可以是并行的。

并发和并行同时的,咱们就知道就可以了

实现线程

实现多线程的方法在我们的JAVA官方中指出的是两种

方法一 实现Runnable接口

Runnable接口是实现接口,可以继续继承类和实现接口,扩展性强这是这种方法的优点。

回顾接口内容:这是一个类实现接口,必须重写完全部接口的全部抽象方法,否则这个类需要定义成抽象类。

我们需要重写run()方法,而run()方法里面就是我们真正的任务功能内容。

实现过程 

  1.  我们需在类上要实现Runnable接口
  2. 需要我们重写run()方法
  3. 创建一个任务对象(其实就是实例化该类的对象)
  4. 把任务对象交给Thread的对象处理
  5. 调用start()启动线程,调用run()的内容(最终调用target.run())

实际上是创建一个任务对象,把任务线程对象处理。

package com.yd.thread;

/**
 * 实现Runnable接口实现创建线程
 */
        //我们需在类上要实现Runnable接口
public class RunnableStyle implements Runnable{
    public static void main(String[] args) {
        //Runnable对象和Thread对象相关联
        
        //创建一个任务对象(其实就是实例化该类的对象)
        RunnableStyle runnableStyle = new RunnableStyle();
        //把任务对象交给Thread的对象处理
        Thread threadOne = new Thread(runnableStyle);
        //调用start()启动线程
        threadOne.start();


    }
        //需要我们重写run()方法
    @Override
    public void run() {
        System.out.println("用Runnable接口来实现多线程");
    }
}

这样我们就实现了一个线程。

最终调用target.run()是什么意思

我们把Runnable任务对象交给Thread处理,我们来看一下Thread的run()内容,(我们可以ctrl+F12搜寻对应类的方法)

有值,运行

target是我们的本Runnable任务

 这样就是我们的Thread调用Runnable()重写的run方法,只是加上了一个判断后,运行了run()

简单的说我们这个run()还是是对Runnable()重写的run方法,而不是Thread的run()

方法二 继承Thread类

 我们查看源码可以看出来其实Thread类实质上是实现了Runnable的接口,

它run方法是对Runnable接口中的run()方法的具体实现(run()整个都被重写)。

当我们启动一个程序时,自动生成一个线程,这个线程就是我们主方法的运行路径,当我们自己实现线程时,我们程序员自己负责自己的线程(启动或者是什么的),我们的主方法线程的启动是java虚拟级负责的。 

比如我们下面的创建   那么我们的单线程程序就变成了主线程和我们创建的线程,也就是多线程了

实现过程

  1.  我们需在类上要继承Thread类
  2. 需要我们重写run()方法
  3. 创建该类对象
  4. 调用start()启动线程,调用run()的内容(run()重写)
package com.yd.thread;

public class ThreadStyle extends Thread{
    public static void main(String[] args) {
        new ThreadStyle().start();
    }

    //需要我们重写run()方法
    @Override
    public void run() {
        System.out.println("用继承Thread来实现多线程");
    }
}

run()重写是什么意思

以上面代码为例,这个run的内容将覆盖if判断的内容,而不是调用(重名run)

总结 

准确的讲,创建线程只有一种方式那就是构造Thread类,而实现线程的执行单元有两种方式
方法一:实现Runnable接口的run方法,并把Runnable实例传给Thread类
方法二:重写Thread的run方法(继承Thread类)

推荐使用方法一

我们提到当我们启动一个程序时,自动生成一个线程,这个线程就是我们主方法的运行路径,当我们自己实现线程时,我们程序员自己负责自己的线程(启动或者是什么的),我们的主方法线程的启动是java虚拟级负责的。 

Thread提供了很多与线程操作相关的方法

常用的就是获取当前线程对象currentthread()设置名称setname(),获取线程名称getname()

当然还有很多在实际开发中不常用的操作方法,但是也会在后面去一一讲解。

扩展 其他形式(万变不离其宗)

Lambda表达式的写法来表达线程实现【写法简单】

package com.yd.thread;

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

        //简化写法
        new Thread(()->System.out.println(Thread.currentThread().getName())).start();


        //1.匿名内部类
        //        new Runnable() {
        //            @Override
        //            public void run() {
        //                System.out.println(Thread.currentThread().getName());
        //            }
        //        };


        //2.lambda写法  放入任务放到Thread类
        //        Runnable runnable = () -> System.out.println(Thread.currentThread().getName());
        //        new Thread(runnable);


    }
}

 ps:函数式接口,所以可以简化

用Callable接口,结合FutureTask类完成【可以返回结果】

我们的前面的实现是把实现Runnable接口的类的对象放入Thread中,运行,但是我们重写的run方法均不能直接返回结果。

这个时候我们可以用Callable结合FutureTask类来实现有返回值的方式放入Thread中,运行。

package com.yd.thread;

import java.util.concurrent.Callable;

public class Two implements Callable<String> {
    private int n;

    //构造函数的重载
    public Two(int n){
        this.n=n;

    }

    //重写的是call
    @Override
    public String call() throws Exception {
        int sum=0;
        for (int i = 0; i <= n; i++) {
            sum+=i;
        }
        return "n"+"和是"+sum;
    }
}
package com.yd.thread;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class TwoTest {
    public static void main(String[] args) {
        //创建一个Callable对象    /他不是线程任务,所以要再封装一下,但是需要它能返回的能力
       Callable<String> twoCallA = new Two(1);
        //交给FutureTask,封装Callable对象,变成未来任务对象  可以在线程执行完成后调用get方法,获取返回的结果               /创建一个Runnable对象任务
        FutureTask<String> stringFutureTaskA = new FutureTask<>(twoCallA);
        //交给Thread类
        Thread tA = new Thread(stringFutureTaskA);
        //启动线程
        tA.start();

        //同样再开一个线程
        Callable<String> twoCallB = new Two(100);
        FutureTask<String> stringFutureTaskB = new FutureTask<>(twoCallB);
        Thread tB = new Thread(stringFutureTaskB);
        tB.start();



        //获取线程执行完毕的结果
        try {
            String sA = stringFutureTaskA.get();
            System.out.println(sA);
            String sB = stringFutureTaskB.get();
            System.out.println(sB);
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

线程池的方式【是一种复用线程的技术】

不使用线程池,如果用户每发起一个请求,后台就创建一个新线程来处理,下次新任务来了又要创建新线程,而创建新线程开销是很大的,这样会严重影响系统的性能

使用线程池不用每次重新创建线程,保持重复利用

 

方法一

我们通常使用Executorservice的实现类ThreadpoolExecutor自创建一个线程池对象

ThreadPoolExcecutor构造器参数说明

参数一:指定线程池的线程数量(核心线程):corepoolsize          不能小于0
参数二:指定线程池可支持的最大线程数: maximumpoolsize     最大数量>核心线程数量
参数三:指定临时线程的最大存活时间: keepalivetime                不能小于0
参数四:指定存活时间的单位(秒,分,时,天): unit                  时间单位
参数五:指定任务队列:workqueue                                               不能为null
参数六:指定用哪个线程工厂创建线程:threadfactory                  不能为null
参数七:指定线程忙,任务满的时候,新任务来了怎么办: handler 不能为null

参数五一般我们选用的就是ArrayBlockingQueue和LinkedBlockingQueue,

队列是一种数据结构,我们会在下一个课程去学习,不过我们可以简单了解一下我们配置的通常几个

  1. ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列(数组结构可配合指针实现一个环形队列)。
  2. LinkedBlockingQueue: 一个由链表结构组成的有界阻塞队列,在未指明容量时,容量默认为 Integer.MAX_VALUE。
  3. PriorityBlockingQueue: 一个支持优先级排序的无界阻塞队列,对元素没有要求,可以实现 Comparable 接口也可以提供 Comparator 来对队列中的元素进行比较。跟时间没有任何关系,仅仅是按照优先级取任务。
  4. DelayQueue:类似于PriorityBlockingQueue,是二叉堆实现的无界优先级阻塞队列。要求元素都实现 Delayed 接口,通过执行时延从队列中提取任务,时间没到任务取不出来。

简单的说参数五的作用也就是我们需要把任务怎么放进去,怎么排列。

参数六的我们默认写Executors.defaultThreadFactory()就可以了它的本质其实就是创建一个

Thread类对象,只不过多了一写处理

参数七的策略如下

​ 一般是用这三个

Abortpolicy  中止策略  默认

DiscardOldestPolicy 放弃最旧的策略

callerRunsPolicy调用主方运行策略

然后是Executorservice常用方法

 就是执行和关闭(线程池正常不会关闭)的方法

package com.yd.thread;

import java.util.concurrent.*;

public class ExecutorDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //指定线程池的线程数量是3       相当于工作的员工是三个
        //指定线程池可支持的最大线程数7  相当于公司总共最多有七个人  临时工两个【等着】
        //指定临时线程的最大存活时间6    相当于临时工空闲多久被开除6加上后面的单位


        //指定任务队列                相当于吃饭的座位  任务相当于是菜
        //指定线程任务池的线程工厂      相当于HR 创建线程的方式
        //任务策略                   相当于忙不过来的解决办法,拒绝方法
        ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 7, 6, TimeUnit.MINUTES,
                new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());


        //现在是提交任务给线程池执行,我们之前是给给Tread类运行

        //执行Callable任务
         Future<String> submitA = pool.submit(new Two(1));
        Future<String> submitB = pool.submit(new Two(100));
        System.out.println(submitA.get());
        System.out.println(submitB.get());


        //执行Runnable任务
        pool.execute(new RunnableStyle());
        pool.execute(new RunnableStyle());
        pool.execute(new RunnableStyle());
        pool.execute(new RunnableStyle());
        pool.execute(new RunnableStyle());

    }

}

我们的Runnable任务

package com.yd.thread;

/**
 * 实现Runnable接口实现创建线程
 */
        //我们需在类上要实现Runnable接口
public class RunnableStyle implements Runnable{
    public static void main(String[] args) {
        //Runnable对象和Thread对象相关联

        //创建一个任务对象(其实就是实例化该类的对象)
        RunnableStyle runnableStyle = new RunnableStyle();
        //把任务对象交给Thread的对象处理
        Thread threadOne = new Thread(runnableStyle);
        //调用start()启动线程
        threadOne.start();


    }
        //需要我们重写run()方法
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"用继承Runnable来实现多线程");
    }
}

我们的callable任务

package com.yd.thread;

import java.util.concurrent.Callable;

public class Two implements Callable<String> {
    private int n;

    //构造函数的重载
    public Two(int n){
        this.n=n;

    }

    //重写的是call
    @Override
    public String call() throws Exception {
        int sum=0;
        for (int i = 0; i <= n; i++) {
            sum+=i;
        }
        return Thread.currentThread().getName()+"  n"+"和是"+sum;
    }
}

可复用 

方法二 【存在风险,了解即可】

还有一种创建线程池的方案

使用Executors(线程池的工具类)调用已经搭配好的线程池对象

​ 

注意:Executors的底层其实也是基于线程池的实现类ThreadPoolExecutor创建线程池对象的,只是搭配好了。

但是在大型并发的使用可能会有风险,所以我们了解即可

定时器【周期调用的技术】   

定时器类似于我们生活中的“闹钟”,达到设定的时间后,就执行某个指定的代码。

但是我们需要注意的是它是保持大约的固定的时间间隔进行,但是一般来说没啥问题

引入了这个并发包单线程变为多线程  

  

     

    • task(command) 要安排的任务。

      delay - 任务执行前的延迟毫秒数。

      period - 连续任务执行之间的时间(以毫秒为单位)。

第一个参数:是一个TimerTask对象,TimerTask是一个继承了Runnable接口的类,我们只需要new一个TimerTask对象,重写里面的run方法即可。

第二个参数:表示指定的时间,默认单位是毫秒,即多长时间之后执行任务代码。

第三个参数:是连续任务执行之间的时间(以毫秒为单位)

最后一个参数 :是单位

 NANOSECONDS(纳秒)、MICROSECONDS(微秒)、MILISECONDS(毫秒)、SECONDS(秒)、MINUTE(分钟)、HOURS(小时)和DAYS(天)

  1. 创建Timer定时器,调用定时器的方法执行定时器任务
  2. 创建TimerTask定时器任务,可以通过匿名内部类的方式创建
package com.yd.thread;



import java.util.Timer;
import java.util.TimerTask;

public class TimerDemo {
    public static void main(String[] args) {
        // 创建定时器
        Timer timer = new Timer();
        // 创建定时器任务
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println("1"+Thread.currentThread().getName());
            }
        }, 1, 1000);
        
        //任务2
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println("2"+Thread.currentThread().getName());
            }
        }, 1000, 200);
    }
}

操作线程的方法 

在操作线程之前我们要学习一下线程周期

线程周期

七种状态

接下来我们线程状态将结合着操作来谈

线程的加入

线程的加入就是:某个程序为多线程程序,假如存在线程A,现在需要插入B,并且要求B先执行完毕。

这就好比你现在看文章然后父母喊你吃饭一样,先得吃饭,然后才可以继续看,或者没有话费这样的更准确,先去充话费然后再打电话。

线程的加入可以用thread的join()方法来完成。

join()它的作用是将当前线程挂起,等待其他线程结束后再执行当前线程,

即当前线程等待另一个调用join()方法的线程执行结束后再往下执行。

通常在main主线程内,等待其它调用join()方法的线程执行结束再继续执行main主线程。

package com.yd.thread;

public class Synchronized {
    public static void main(String[] args) throws InterruptedException {

        //实例化对象
        RunnableStyle one = new RunnableStyle();
        SynchronizedTest t = new SynchronizedTest();


        //把任务放到Thread
        Thread A = new Thread(t, "小尘");
        Thread a = new Thread(one);

        A.start();
        A.join(1000);
        a.start();
//        thread.join(1000);
        System.out.println(Thread.currentThread().getName()+"主线程的内容");


    }
}

​线程的睡眠

​线程的睡眠可以使用 sleep()方法,需要指定一个毫秒为单位,使线程在规定参数时间不能到就绪状态,同时醒来不能保证运行状态,但是能保证就绪状态,所以需要抛出异常。

​线程的中断

废除了stop()方法

我们会用提倡使用在run()中使用无限循环的形式,然后使用一个布尔类型标记控制循环的停止。

还有如果是使用sleep()或者wait()方法进入就绪状态,我们可以使用Thread的interrupt()方法来通知线程离开run()方法,同时抛出一个异常,可以在处理异常的时候完成业务中断,如终止while循环,或者是在while做一个isinterrupt判断。

package com.yd.thread;

public class RunnableDemo implements Runnable{


    public static void main(String[] args) throws InterruptedException {
        RunnableDemo runnableDemo = new RunnableDemo();
        Thread thread = new Thread(runnableDemo);
        thread.start();
        Thread.sleep(2000);
//我们可以做一个对比,把下面注释去掉
        thread.interrupt();
    }


    //需要我们重写run()方法
    @Override
    public void run() {
        int num=0;
        //Integer.MIN_VALUE为int的最大值,还没有被中断就可以运行
        while (!Thread.currentThread().isInterrupted()&&num<=Integer.MAX_VALUE/2){
            if (num%100==0){
                System.out.println(num+"是10000倍数");
            }
            num++;
        }
        System.out.println("运行结束");
    }

}

​线程的优先级

线程可以划分优先级,优先级较高的线程得到的 CPU 资源较多,即 CPU 优先执行优先级较高的线程对象的任务,但是需要注意的是优先级小并不是得不到运行,只是概率小。

在 Java 中使用 setPriority 方法来设置优先级,同时把优先级划分成 1~10 这10个等级,

如果小于 1 或者大于 10,则 JDK 会抛出异常

// 线程最小的优先级等级
public final static int MIN_PRIORITY = 1;

// 线程默认的优先级等级
public final static int NORM_PRIORITY = 5;

// 线程最大的优先级等级
public final static int MAX_PRIORITY = 10;

​线程的礼让

线程礼让是指在某个特定的时间点,让线程暂停抢占CPU资源的行为,但是其只是一种暗示,不能保证,在我们线程中操作线程都不能一定保证操作。

线程的礼让使用yield方法来实现

package com.mie.yield;

public class YieldThread1 extends Thread{
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 10; i++) {
			if (i==5) {
				yield();//当i==5时线程礼让
			}
			System.out.println(Thread.currentThread().getName()+"----"+i);
		}
	}

}

package com.mie.yield;

public class YieldThread2 extends Thread{
       @Override
    public void run() {
    	// TODO Auto-generated method stub
    	   for (int j = 0; j < 10; j++) {
    		   System.out.println(Thread.currentThread().getName()+"====="+j);
		}
    	
    }
}

package com.mie.yield;

public class Test {
	public static void main(String[] args) {
		YieldThread1 yieldThread1=new YieldThread1();
		YieldThread2 yieldThread2=new YieldThread2();
		yieldThread1.setName("线程1");
		yieldThread2.setName("线程2");
		yieldThread1.start();
		yieldThread2.start();
	}

}

​线程的守护

默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。

  • 垃圾回收器线程就是一种守护线程

要将普通线程设置为守护线程,方法很简单,只需要调用 Thread.setDaemon() 方法即可。

public class MyThread extends Thread{
    private int i = 0;
    @Override
    public void run() {
       try {
           while (true) {
               i++;
               System.out.println("i=" + (i));
               Thread.sleep(1000);
           }
       }catch (InterruptedException e){
           e.printStackTrace();
       }
    }
}
public class Run3 {
    public static void main(String[] args) {
        try {
            MyThread thread = new MyThread();
            thread.setDaemon(true); //该线程为守护线程
            thread.start();
            Thread.sleep(5000);
            System.out.println("主线程(用户线程)结束,thread线程(守护线程)也不再打印了!");
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

线程的安全

多个线程同时操作同一个共享资源的时候可能会出现业务安全问题,称为线程安全问题。

最典型的案例就是账户取钱

小尘和小土是兄弟,他们有一个共同的账户,余额是2k,模拟2人同时去取钱2k。

如果说同时执行取2k,用代码实现发现钱都可以取到,平白无故多2k。

同样的案例还有很多比如,银行排号,火车站售票

为了解决这样奇葩的问题,我们得让多个线程实现先后依次访问共享资源,这样可以解决安全问题。

加锁

我们第一个想法就是,加锁,抢一个锁,谁先拿到谁先访问

这就好比上洗手间,一个人先到把门关上,出来再将门打开,下一个人才可以进入。

那么我们可以怎么操作呢

同步块

把访问共享资源的核心代码给上锁,以此保证线程安全

每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行。

ctrl+art+t 快捷键  

synchronized(Object){

}

同步机制使用了synchronized关键字,使用关键字代码块被称为同步块

我们将共享资源放在synchronized定义的区域,当其他线程获取这个锁的时候,就必须等待锁释放后才可以进入该区域。

Object是任意一个对象,每一个对象都存在一个标志位,并且有两个值,0和1。

如果是0代表同步块有线程运行,当期线程处于就绪状态,直到处于同步代码块内容执行完毕,值为1,当期线程才可以执行代码块内容。

我们模拟一个买橘子的场景,五个人买橘子每次只能买30个,一共老板只有100个。

package com.yd.thread;

public class SynchronizedTest implements Runnable{
    int num=100;
    public void BuyOranges(int buyNum){
        String name = Thread.currentThread().getName();
        synchronized (this){
            if (this.num>buyNum){
                System.out.println(name+"买了"+buyNum+"橘子");
                //更新数量
                this.num-=buyNum;
            }else {
                System.out.println(name+"没有那么多橘子让你买");
            }
        }
    }
    
    @Override
    public void run() {
        BuyOranges(30);
    }

}
package com.yd.thread;

public class Synchronized {
    public static void main(String[] args) {
        //实例化一个对象
        SynchronizedTest t = new SynchronizedTest();
        //把任务放到Thread
        Thread A = new Thread(t, "小尘");
        Thread B  = new Thread(t, "小土");
        Thread C  = new Thread(t, "小水");
        Thread E  = new Thread(t, "小火");
        Thread D  = new Thread(t, "小金");
        A.start();
        B.start();
        C.start();
        D.start();
        E.start();

    }
}

 

我们的锁对象不要影响其他线程执行

规范上:建议使用共享资源作为锁对象。
对于实例方法建议使用this作为锁对象。
对于静态方法建议使用字节码(类名.class)对象作为锁对象。

同步方法

把访问共享资源的核心方法给上锁,以此保证线程安全。

修饰符synchronized返回值类型方法名称(形参列表){
      操作共享资源的代码

}

原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。

同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码

如果方法是实例方法:同步方法默认用this作为的锁对象,但是代码要高度面向对象。

如果方法是静态方法:同步方法默认用类名.class作为的锁对象。

lock锁

lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作。

Lock并不是用来代替synchronized的而是当使用synchronized不满足情况或者不合适的时候来提供高级功能的

lock是接口,不能直接实例化,这里采用它的实现类Reentrantlock来构建lock锁对象

锁对象创建完以后,在方法的对应的位置添加。

灵活性地提高带来了额外的责任。 缺少块结构锁定需要手动地去释放锁。 在大多数情况下,应使用以下惯用法:

Lock lock = new ReentrantLock();
lock.lock();
try{
  
   }finally {
   lock.unlock();
}

package com.yd.thread;

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

public class SynchronizedTest implements Runnable{
    int num=100;
    Lock lock = new ReentrantLock();
    public  void  BuyOranges(int buyNum) {
        try {
            String name = Thread.currentThread().getName();
            lock.lock();
            if (this.num > buyNum) {
                System.out.println(name + "买了" + buyNum + "橘子");
                //更新数量
                this.num -= buyNum;
            } else {
                System.out.println(name + "没有那么多橘子让你买");
            }
        }  finally {
            lock.unlock();
        }

    }
    
    @Override
    public void run() {
        BuyOranges(30);
    }

}

当然我们线程的内容(涉及到并发,安全内容)需要结合我们后面高级内容具体的来谈,在这里只是基础的把知识点内容复述一下。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/208652.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何删除mac苹果电脑上面的流氓软件?

在使用苹果电脑的过程中&#xff0c;有时候我们也会遇到一些不需要的软件。无论是因为不再需要&#xff0c;或者是为了释放磁盘空间&#xff0c;删除这些软件是很重要的。本文将为大家介绍怎样删除苹果电脑上的软件&#xff01; CleanMyMac X全新版下载如下: https://wm.make…

springboot + vue 智能物流管理系统

qq&#xff08;2829419543&#xff09;获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;springboot 前端&#xff1a;采用vue技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xf…

FL Studio(水果软件)2024最新中文版云盘下载

如今&#xff0c;越来越多的音乐人选择使用音乐制作软件来进行音乐的创作&#xff0c;一台电脑、一款软件以及一个外接MIDI就是一个小型的音乐工作站。FL Studio成了音乐界萌新的首选&#xff0c;目前最新的版本为FL Studio2024版本。 你可以不知道如何做音乐&#xff0c;但是…

你对SPA单页面的理解,它的优缺点分别是什么?

面试官&#xff1a;你对SPA单页面的理解&#xff0c;它的优缺点分别是什么&#xff1f;如何实现SPA应用呢 一、什么是SPA SPA&#xff08;single-page application&#xff09;&#xff0c;翻译过来就是单页应用SPA是一种网络应用程序或网站的模型&#xff0c;它通过动态重写当…

微信小程序 slider 翻转最大和最小值

微信小程序 slider 翻转最大和最小值 场景代码示例index.wxmlindex.jsutil.js 参考资料 场景 我想使用 slider 时最左边是 10 最右是 -10。 但是想当然的直接改成<slider min"10" max"-10" step"1" /> 并没用。 查了文档和社区也没有现成…

HarmonyOs 4 (二) HelloWord

目录 一 开发工具下载安装1.1 下载安装包1.2 下载相关依赖 二 开发者注册与个人实名认证三 第一个程序2.1 创建第一个程序2.2 认识开发者界面2.3 目录结构认识2.3.1 父目录认识2.3.2 AppScope 目录2.3.3 entry目录2.3.3.1 ets 目录2.3.3.2 resources目录 2.3.4 认识配置文件2.3…

(四)Tiki-taka算法(TTA)求解无人机三维路径规划研究(MATLAB)

一、无人机模型简介&#xff1a; 单个无人机三维路径规划问题及其建模_IT猿手的博客-CSDN博客 参考文献&#xff1a; [1]胡观凯,钟建华,李永正,黎万洪.基于IPSO-GA算法的无人机三维路径规划[J].现代电子技术,2023,46(07):115-120 二、Tiki-taka算法&#xff08;TTA&#xf…

SQL Server 2016(创建数据库)

1、实验环境。 某公司有一台已经安装了SQL Server 2016的服务器&#xff0c;现在需要新建数据库。 2、需求描述。 创建一个名为"db_class"的数据库&#xff0c;数据文件和日志文件初始大小设置为10MB&#xff0c;启用自动增长&#xff0c;数据库文件存放路径为C:\db…

Python 网络爬虫(一):HTML 基础知识

《Python入门核心技术》专栏总目录・点这里 文章目录 1. 什么是 HTML2. HTML 的特点3. HTML 的标签和属性4. HTML 的结构4.1 文档类型声明4.2 根元素4.3 头部部分4.4 主体部分4.5 表格标签4.6 区块4.7 嵌套和层次结构4.8 表单4.9 注释 5. HTML 交互事件 大家好&#xff0c;我是…

C#文件夹基本操作(判断文件夹是否存在、创建文件夹、移动文件夹、删除文件夹以及遍历文件夹中的文件)

目录 一、判断文件夹是否存在 1.Directory类的Exists()方法 2. DirectoryInfo类的Exists属性 二、创建文件夹 1. Directory类的CreateDirectory()方法 2.DirectoryInfo类的Create()方法 三、移动文件夹 1. Directory类的Move()方法 2.DirectoryInfo类的MoveT…

Docker容器间网络共享

Docker容器间网络共享 1、新建网络2、容器绑定网卡3、验证 Docker环境中为了一套应用部署多个环境、并且不修改配置文件的情况下&#xff0c;做到一键部署。要求不同容器直接的网络交互&#xff0c;使用容器名称。 网络相关常用命令 #查看网络内部信息docker network inspect b…

应用于智慧零售的AI边缘计算盒子+AI算法软硬一体化方案

中国是世界上最大的消费市场&#xff0c;零售行业拥有极大的发展潜力&#xff0c;阿里、腾讯两大互联网巨头正在加紧、加大布局&#xff1b; 信迈智慧零售方案可涵盖快消行业、服饰行业、餐饮行业、酒店行业、美家行业、消费电子行业、新零售商行业、服饰连锁、大卖场/商超、百…

详解Linux常用命令

目录 1. ps 命令 2. top 命令 3. grep 命令 4. df 命令 5. tail 命令 6. head 命令 7. cat 命令 8. --help 和 man 命令 9. cd 命令 10. mkdir 命令 11. rm 命令 12. mv 和 cp 命令 13. touch 命令 14. vi 或 vim 命令 15. chmod 修改权限 16. 打包和压缩文件 …

39.从0到上线三天搭建个人网站(第三天)

点赞收藏加关注&#xff0c;你也能住大别墅&#xff01; 一、第三天主要工作 1.完成detail页面的开发 2.将所有数据以及部分静态资源存在uniCloud&#xff0c;为以后做管理后台做准备 3.创建云对象getData&#xff0c;在beforecreate&#xff08;&#xff09;中获取数据 4.…

【漏洞复现】智跃人力资源管理系统GenerateEntityFromTable.aspx接口存在SQL注入漏洞 附POC

漏洞描述 智跃人力资源管理系统是基于B/S网页端广域网平台,一套考勤系统即可对全国各地多个分公司进行统一管控,成本更低。信息共享更快。跨平台,跨电子设备。智跃人力资源管理系统GenerateEntityFromTable.aspx接口处存在SQL注入漏洞,攻击者可通过该漏洞获取数据库中的信…

国内首所国际职业培训学院落户深圳盐田揭幕开业

11月26日&#xff0c;中科国药•中科大有大健康上市企业孵化平台迎来了国内首所国际职业学院——深圳市盐田区国际职业培训学院的正式落成与揭幕仪式。中科大有高新科技有限公司董事长、长江商学院MBA\FMBA金融导师、深圳市中科国药生物医药研究院理事长、深圳市盐田区国际职业…

​iOS Class Guard github用法、工作原理和安装详解及使用经验总结

iOS Class Guard是一个用于OC类、协议、属性和方法名混淆的命令行工具。它是class-dump的扩展。这个工具会生成一个symbol table&#xff0c;这个table在编译期间会包含进工程中。iOS-Class-Guard能有效的隐藏绝大多数的类、协议、方法、属性和 实例变量 名。iOS-Class-Guard不…

java设计模式学习之【桥接模式】

文章目录 引言桥接模式简介定义与用途&#xff1a;实现方式 使用场景优势与劣势桥接模式在Spring中的应用绘图示例代码地址 引言 想象你正在开发一个图形界面应用程序&#xff0c;需要支持多种不同的窗口操作系统。如果每个系统都需要写一套代码&#xff0c;那将是多么繁琐&am…

一小时玩转【负载均衡】

&#x1f604;作者简介&#xff1a; 小曾同学.com,一个致力于测试开发的博主⛽️&#xff0c;主要职责&#xff1a;测试开发、CI/CD 如果文章知识点有错误的地方&#xff0c;还请大家指正&#xff0c;让我们一起学习&#xff0c;一起进步。 &#x1f60a; 座右铭&#xff1a;不…

流媒体方案之FFmepeg——实现物联网视频监控项目

目录 前言 一、FFmpeg介绍 二、FFmpeg简易理解 三、FFmpeg的重要概念 四、软硬件准备 五、移植、运行FFmpeg 六、运行FFmpeg 前言 最近想做一个安防相关的项目&#xff0c;所以跟着韦东山老师的视频来学习视频监控方案的相关知识&#xff0c;韦东山老师讲的课非常好&…
最新文章