多线程 详解(掌握这篇就够了)

一、线程简介

普通线程和多线程的应用
在这里插入图片描述

Process和Thread

  • 说起进程,就不得不说下程序。程序是指令和数据的有序集合,其本身没有任何运 行的含义,是一个静态的概念。
  • 进程则是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位
  • 通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的的单位

注意:很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能 执行一个代码,因为切换的很快,所以就有同时执行的错局。注意:很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错局。

程序 进程 线程

在操作系统中运行的程序就是我们所说的进程,比如我们电脑的应用 网易云、百度、微信等这些都是一个进程。

进程中实现的不同功能,我们称之为线程,比如一个视频中我们能够同时看到弹幕,听到声音,看图像这都可以叫做不同的线程

三者之间的关系:程序中包含多个进程 进程中同时包含多个线程(且至少有一个线程为主线程)

核心概念

  • 线程就是独立的执行路径;
  • 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程;
  • main() 称之为主线程,为系统的入口,用于执行整个程序;
  • 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与
  • 操作系统紧密相关的,先后顺序是不能认为的干预的。
  • 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;
  • 线程会带来额外的开销,如cpu调度时间,并发控制开销。
  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

二、线程创建(重点)

  • 继承Thread (重点)
  • 实现Callable接口 (重点)
  • 实现Callable接口 (了解)

1、继承Thread

  • 自定义线程类继承Thread类
  • 重写run()方法,编写线程执行体
  • 创建线程对象,调用start()方法启动线程
public class Thread1 {

   //线程开启不一定执行,由cpu进行调度,
   public static void main(String[] args) {

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

      try {
         Thread.sleep(2);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }

      for (int i=0;i<200;i++){
         System.out.println("我是主线程"+i);
      }
   }
}
//主线程 子线程的执行顺序 由cpu进行调度,cpu执行速度很快,一般先执行主线程
class MyThread extends Thread{

   @Override
   public void run() {
      for (int i=0;i<50;i++){
         System.out.println("我是子线程"+i);
      }
   }
}

2、实现Runnable接口(推荐使用)

  • 定义MyRunnable类实现Runnable接口
  • 实现run()方法,编写线程执行体
  • 创建线程对象,调用start()方法启动线程
//多线程抢票
public class ticket {

   public static void main(String[] args) {

      MyTicket myTicket = new MyTicket();

      new Thread(myTicket,"A").start();
      new Thread(myTicket,"B").start();
   }
}


class MyTicket implements Runnable {


   public int tickets = 100;

   @Override
   public void run() {
      while (tickets>0) {
         System.out.println(Thread.currentThread().getName() + "抢到了第" + tickets-- + "张票");
      }
   }
}

3、 实现Callable接口

  1. 实现Callable接口,需要返回值类型
  2. 重写call方法,需要抛出异常
  3. 创建目标对象
  4. 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
  5. 提交执行:Future result1 = ser.submit(t1);
  6. 获取结果:boolean r1 = result1.get()
  7. 关闭服务:ser.shutdownNow();
//多线程下载图片
//实现图片的下载要先导入common-io的jar包
public class TestCallable {
   public static void main(String[] args) throws ExecutionException, InterruptedException {

      //执行线程
      downThread t1 = new downThread("https://img-bss.csdnimg.cn/1615183593661.png","a.jpg");
      downThread t2= new downThread("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fa0.att.hudong.com%2F30%2F29%2F01300000201438121627296084016.jpg&refer=http%3A%2F%2Fa0.att.hudong.com&app=2002&size=f9999," +
            "10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1618125226&t=206ef43a6621700711f384e286ee4220","b.jpg");

      //创建执行服务 开启线程池
      ExecutorService ser= Executors.newFixedThreadPool(2);

      //提交执行
      Future<Boolean> future1=ser.submit(t1);
      Future<Boolean> future2=ser.submit(t2);

      //获取结果
      boolean flag1= future1.get();
      boolean flag2= future2.get();
      System.out.println(flag1);
      System.out.println(flag2);

      //关闭服务
      ser.shutdownNow();

   }
}

class downThread implements Callable<Boolean> {

   public String url;
   public String name;

   public downThread(String url,String name){
      this.url=url;
      this.name=name;
   }

   @Override
   public Boolean call(){
      //执行体
      webDownLoad webDownLoad = new webDownLoad();
      webDownLoad.photoDown(url,name);
      System.out.println(name+"图片下载完成");
      return true;
   }
}

class webDownLoad {

   //定义下载图片的方法
   public void photoDown(String url, String name) {
      	//借助封装的类 FileUtils 将该路径的图片 拷贝到指定文件
      String file="G:\\idea\\bilibili\\kuang\\Thread\\src\\com\\zz\\demo01";
      try {
         //将网络路径 url 的资源下载到指定的文件夹中去,并命名为name
         FileUtils.copyURLToFile(new URL(url), new File(file,name));

      } catch (IOException e) {
         e.printStackTrace();
      }
   }
}

4、龟兔赛跑

1.首先来个赛道距离,然后要离终点越来越近

  1. 判断比赛是否结束
  2. 打印出胜利者
  3. 龟兔赛跑开始
  4. 故事中是乌龟赢的,兔子需要睡觉,所以我们来模拟兔子睡觉
  5. 终于,乌龟赢得比赛
//龟兔赛跑 实例
/**
 * 1 自定义一个boolean方法 决出胜利者
 * 2 将逻辑代码放入 线程体run方法中
 * 3 开启乌龟、兔子两条线程
 */
public class game {
   public static void main(String[] args) {

      Race race = new Race();

      new Thread(race, "兔子").start();
      new Thread(race, "乌龟").start();
   }
}

class Race implements Runnable {

   public int steps = 10000;
   public String winner;

   @Override
   public void run() {

      while (steps > 0) {

         if (Thread.currentThread().getName().equals("兔子")) {
            try {
               Thread.sleep(1);
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
         }

         boolean flag = gameover(steps);
         if (flag) {
            break;
         } else {
            //没到终点就继续跑
            System.out.println(Thread.currentThread().getName() + "跑了" + steps-- + "步");
         }
      }
   }
   //决出胜利者=
   public boolean gameover(int step) {
      if (winner != null) {
         //已经产生了胜利者
         return true;
      }

      if (step <= 1) {
         winner = Thread.currentThread().getName();
         System.out.println(winner + "赢了");
         return true;
      }
      return false;
   }
}

5、小结

继承Thread类

  • 子类继承Thread类具备多线程能力
  • 启动线程:子类对象. start()
  • 不建议使用:避免OOP单继承局限性

实现Runnable接口

  • 实现接口Runnable具有多线程能力
  • 启动线程:传入目标对象+Thread对象.start()
  • 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用

在这里插入图片描述

静态代理

代理模式在我们生活中很常见,例如 按理说我们在生活中购物的时候,可以直接从工厂进行购物,但是我们生活中常常不是这样,一般都是厂家委托给供销商(超市)进行销售,而我们不直接和厂商进行关联,这其中就引用了静态代理的思想,厂家相当于真实角色,而超市相当于代理角色,厂家产品相当于接口,我们作为购买者 客户端。
其实代理角色的作用就是,辅助真实角色完成一些事情,在真实角色业务的前提下,还可以增加其他的业务,一起帮助真实角色共同完成。后面AOP切面编程就是运用了该思想。
在这里插入图片描述

这是常见代理模式常见的 UML 示意图。

下面通过模拟婚庆公司,来实现静态代理 我们真实角色,婚庆公司代理角色 结婚事件为接口

/**
 * @Author zz
 * @Date 2021/3/12 15:27
 */

//静态代理 多线程也是静态代理原理实现
public class Proxy {

   public static void main(String[] args) {

        //真实角色
		you you = new you();

		//代理角色
		Marry marry = new WeddingCompany(you);

		//代理角色 帮助真实角色实现
		marry.marride();
   }
}

interface Marry{
   //定义一个结婚接口
   void marride();
}

//真实角色 结婚
class you implements Marry{

   @Override
   public void marride() {
      System.out.println("xxx结婚了");
   }
}

//婚庆公司 帮助我实现结婚 婚庆公司可以在结婚前后增加所需要的业务
class WeddingCompany implements Marry{

   //引入真实角色
   public Marry target;

   public WeddingCompany(Marry target){
      this.target=target;
   }

   @Override
   public void marride() {
      before();
      target.marride();
      after();
   }

   private void before() {
      System.out.println("结婚前,进行场地的布置");
   }

   private void after() {
      System.out.println("结婚后,收拾东西回公司");
   }
}

结果:
结婚前
xxx结婚了
结婚后

现在可以看到,代理模式可以在不修改被代理对象you的基础上,通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口marride(),或者是共同继承某个类。

上面介绍的是静态代理的内容,为什么叫做静态呢?因为它的类型是事先预定好的,比如上面代码中的 WeddingCompany代理角色这个类。下面要介绍的内容就是动态代理。

Thread静态代理底层剖析

理解完了静态代理,我们应用静态代理的思想来分析多线程

Thread底层也是通过静态代理原理实现的,通过我们开启子线程来定义真实角色,Thread为代理角色,Runnable为要实现得到接口,run为接口中的方法

Runnable接口,以及里面的run方法

public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

我们在开启子线程的过程中,其实就是定义一个真实角色MyThread实现了一个Runnable接口,并重写了Runnable接口里面的run()方法 真实角色

class MyThread implements Runnable{
   @Override
   public void run() {
     System.out.println(“我是子线程,同时是真实角色”);
   }
}

这里代理对象为Thread类,我们通过Thread源码,分析得Thread也是实现了Runnable接口,并实现了run方法 代理角色

 * @author  unascribed
 * @see     Runnable
 * @see     Runtime#exit(int)
 * @see     #run()
 * @see     #stop()
 * @since   JDK1.0
 */
//这里代理角色Thread也实现了Runnable接口
public
class Thread implements Runnable {
    /* Make sure registerNatives is the first thing <clinit> does. */
    private static native void registerNatives();
    static {
        registerNatives();
    }
//代理对象中的 构造器
 public Thread(Runnable target, String name) {
        init(null, target, name, 0);
}

代理真实对象实现方法

public static void main(String[] args) {

   //真实角色
   Runnable myRunnable = new MyRunnable();

   //代理角色
   Thread thread = new Thread(myRunnable,"A");
   //实现的方法
   thread.start();
   //thread.run();
}

示例代码

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

      //真实角色
      Runnable myRunnable = new MyRunnable();

      //代理角色
      Thread thread = new Thread(myRunnable,"A");
      //实现的方法
      thread.start();
      //thread.run();
   }
}
class MyRunnable implements Runnable{
   @Override
   public void run() {
      System.out.println("我是子线程");
   }
}

动态代理

既然是代理,那么它与静态代理的功能与目的是没有区别的,唯一有区别的就是动态与静态的差别。

前面我们知道静态代理的实现是要我们手动创建代理对象WeddingCompany实现Marry接口,那么动态代理所体现的不同就是我们可以让程序在运行的时候自动在内存中创建一个实现Marry接口的代理类,而不用手动去定义WeddingCompany这个代理类,这就是它被称为动态代理的原因。

可能概念比较抽象,那么我们通过代码来进行实现

在以上静态代理的场景下来实现动态代理

/**
 * @Author zz
 * @Date 2021/3/12 18:45
 */
public class ProxyDongTai {

	public static void main(String[] args) {
		//真实角色
		MarryDT realRole = new youDT();

		//代理角色 注意这里不是实现接口InvocationHandler
		WeddingCompanyDT proxy=new WeddingCompanyDT(realRole);

		//生成代理类
		//MarryDT proxyRole = (MarryDT) Proxy.newProxyInstance(youDT.class.getClassLoader(), realRole.getClass().getInterfaces(), proxy);
		
		//调用方法 生成代理类
		MarryDT proxyRole = (MarryDT)proxy.getProxy();
		
		proxyRole.marride();


	}
}

interface MarryDT {
	//定义一个结婚接口
	void marride();
}

//真实角色 结婚
class youDT implements MarryDT {

	@Override
	public void marride() {
		System.out.println("xxx结婚了");
	}
}

//动态代理类 要实现InvocationHandler接口
class WeddingCompanyDT implements InvocationHandler {

	//引入接口 真实对象
	public MarryDT target;

	public WeddingCompanyDT(MarryDT target) {
		this.target = target;
	}

	//生成得到的代理类
    // loader 自然是类加载器
    // interfaces 代码要用来代理的接口
    // h是一个 InvocationHandler 对象

	public Object getProxy(){
		return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
	}


	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

		before();
		method.invoke(target,args);
		after();

		return null;
	}

	private void after() {
		System.out.println("结婚后");
	}

	private void before() {
		System.out.println("结婚前");
	}
}

结果:
结婚前
xxx结婚了
结婚后

newProxyInstance解析

动态代码涉及了一个非常重要的类 Proxy。正是通过 Proxy 的静态方法 newProxyInstance 才会动态创建代理。

Proxy

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

下面讲解它的 3 个参数意义。

  • loader 自然是类加载器
  • interfaces 代码要用来代理的接口
  • h 一个 InvocationHandler 对象

InvocationHandler

InvocationHandler 是一个接口,官方文档解释说,每个代理的实例都有一个与之关联的 InvocationHandler 实现类,如果代理的方法被调用,那么代理便会通知和转发给内部的 InvocationHandler 实现类,由它决定处理。

public interface InvocationHandler {

    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

InvocationHandler 内部只是一个 invoke() 方法,正是这个方法决定了怎么样处理代理传递过来的方法调用。

  • proxy 代理对象
  • method 代理对象调用的方法
  • args 调用的方法中的参数

因为,Proxy 动态产生的代理会调用 InvocationHandler 实现类,所以 InvocationHandler 是实际执行者。

//动态代理类 要实现InvocationHandler接口
class WeddingCompanyDT implements InvocationHandler {

   //引入接口 真实对象
   public MarryDT target;

   public WeddingCompanyDT(MarryDT target) {
      this.target = target;
   }

   //生成得到的代理类
   public Object getProxy(){
      //ClassLoader loader,
      //Class<?>[] interfaces
      //InvocationHandler h
      return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
   }
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      before();
      method.invoke(target,args);
      after();
      return null;
   }

   private void after() {
      System.out.println("结婚后");
   }

   private void before() {
      System.out.println("结婚前");
   }
}

动态代理多个接口

以上实例体现的是一个人结婚并且由婚礼公司提供婚事的安排,那如果有多个人结婚那 那要怎么实现那

这里我们只需要写多个接口,就可以模拟多个人结婚的实例,其中都是由婚庆公司(动态代理类)来处理婚事

也可以在一个结婚接口中,定义多个方法来实现

/**
 * @Author zz
 * @Date 2021/3/12 18:45
 */
public class ProxyDongTai {
	public static void main(String[] args) {
		//你的 真实角色
		MarryDT realRoleYou = new youDT();

		//他的真实角色
		MarryTa realRoleTa=new TaDT();

		//代理角色 注意这里不是实现接口InvocationHandler
		WeddingCompanyDT proxyYou=new WeddingCompanyDT(realRoleYou);

		WeddingCompanyDT proxyTa=new WeddingCompanyDT(realRoleTa);

		//生成代理类
//		MarryDT proxyRoleYou =(MarryDT)Proxy.newProxyInstance(youDT.class.getClassLoader(), realRoleYou.getClass().getInterfaces(), proxyYou);
//		MarryTa proxyRoleTa =(MarryTa)Proxy.newProxyInstance(TaDT.class.getClassLoader(), realRoleTa.getClass().getInterfaces(), proxyTa);

		//调用方法 生成代理类
		MarryDT proxyRoleYou = (MarryDT)proxyYou.getProxy();

		MarryTa proxyRoleTa = (MarryTa)proxyTa.getProxy();


		System.out.println("实现自动代理多个接口");
		System.out.println();
		proxyRoleYou.marride();
		System.out.println("-----------");
		proxyRoleTa.marriedTa();

	}
}


interface MarryDT {
   //定义一个结婚接口
   void marride();
}

interface MarryTa{
   //定义一个他结婚的接口
   void marriedTa();
}

class TaDT implements MarryTa{

   @Override
   public void marriedTa() {
      System.out.println("他也结婚了");
   }
}

//真实角色 结婚
class youDT implements MarryDT {

   @Override
   public void marride() {
      System.out.println("你结婚了");
   }
}

//动态代理类 要实现InvocationHandler接口
class WeddingCompanyDT implements InvocationHandler {

   //引入接口 真实对象
// public MarryDT target;
// public MarryTa ta;

// public WeddingCompanyDT(MarryDT target) {
//    this.target = target;
// }
//
// public WeddingCompanyDT(MarryTa ta) {
//    this.ta = ta;
// }

   /**
    * 这里定义真实角色,我们一般使用Object类,若后面可能要引入多个接口,
    * 这样在getProxy()方法以及invoke()方法中,不会出现多个不同的接口不能同时进行代理的情况
    */
   public Object realTarget;


   public WeddingCompanyDT(Object realTarget){
      this.realTarget=realTarget;
   }

   //生成得到的代理类
   public Object getProxy(){
      //ClassLoader loader,
      //Class<?>[] interfaces
      //InvocationHandler h
      return Proxy.newProxyInstance(this.getClass().getClassLoader(),realTarget.getClass().getInterfaces(),this);
   }


   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

      before();
      method.invoke(realTarget,args);
      after();

      return null;
   }

   private void after() {
      System.out.println("结婚后");
   }

   private void before() {
      System.out.println("结婚前");
   }
}

结果:
实现自动代理多个接口

结婚前
你结婚了
结婚后
-----------
结婚前
他也结婚了
结婚后

这里要注意下,若程序中出现多个接口代理的情况下,我们要把在代理类中引入真实角色的类型改为Object类型

/**
 * 这里定义真实角色,我们一般使用Object类,若后面可能要引入多个接口,
 * 这样在getProxy()方法以及invoke()方法中,不会出现多个不同的接口不能同时进行代理的情况
 */
public Object realTarget;

public WeddingCompanyDT(Object realTarget){
   this.realTarget=realTarget;
}

其中也可以在主方法中,生成代理类newProxyInstance来实现这样如果多个接口都实现的话,代码就比较冗余

public static void main(String[] args) {
		//你的 真实角色
		MarryDT realRoleYou = new youDT();

		//他的真实角色
		MarryTa realRoleTa=new TaDT();

		//代理角色 注意这里不是实现接口InvocationHandler
		WeddingCompanyDT proxyYou=new WeddingCompanyDT(realRoleYou);

		WeddingCompanyDT proxyTa=new WeddingCompanyDT(realRoleTa);

		//生成代理类
		MarryDT proxyRoleYou =(MarryDT)Proxy.newProxyInstance(youDT.class.getClassLoader(), realRoleYou.getClass().getInterfaces(), proxyYou);
		MarryTa proxyRoleTa =(MarryTa)Proxy.newProxyInstance(TaDT.class.getClassLoader(), realRoleTa.getClass().getInterfaces(), proxyTa);

		//调用方法 生成代理类
//		MarryDT proxyRoleYou = (MarryDT)proxyYou.getProxy();
//
//		MarryTa proxyRoleTa = (MarryTa)proxyTa.getProxy();


		System.out.println("实现自动代理多个接口");
		System.out.println();
		proxyRoleYou.marride();
		System.out.println("-----------");
		proxyRoleTa.marriedTa();

	}

动态代理的秘密

我们想下为什么 Proxy 能够动态产生不同接口类型的代理,我的猜测是肯定通过传入进去的接口然后通过反射动态生成了一个接口实例。
比如 MarryDT是一个接口,那么 Proxy.newProxyInstance() 内部肯定会有

new MarryDT();

这样相同作用的代码,不过它是通过反射机制创建的。那么事实是不是这样子呢?直接查看它们的源码好了。需要说明的是,我当前查看的源码是 1.8 版本。

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
    Objects.requireNonNull(h);

    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    /*
     * Look up or generate the designated proxy class.
     */
     //这里newProxyInstance 的确创建了一个实例,我们点进去
    Class<?> cl = getProxyClass0(loader, intfs);

    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {
        if (sm != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }

        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException|InstantiationException e) {
        throw new InternalError(e.toString(), e);
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString(), t);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString(), e);
    }
}

newProxyInstance 的确创建了一个实例,它是通过 cl 这个 Class 文件的构造方法反射生成。cl 由 getProxyClass0() 方法获取。

private static Class<?> getProxyClass0(ClassLoader loader,
                                       Class<?>... interfaces) {
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }

    // If the proxy class defined by the given loader implementing
    // the given interfaces exists, this will simply return the cached copy;
    // otherwise, it will create the proxy class via the ProxyClassFactory
    //从注释我们可以得到说,如果由实现给定接口的给定加载器定义的代理类存在,则只会返回缓存的副本;否则,它将通过ProxyClassFactory创建代理类
    //我们点进去ProxyClassFactory这个类
    return proxyClassCache.get(loader, interfaces);
}

直接通过缓存获取,如果获取不到,注释说会通过 ProxyClassFactory 生成。

/**
 * A factory function that generates, defines and returns the proxy class given
 * the ClassLoader and array of interfaces.
 */
private static final class ProxyClassFactory
    implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
    // prefix for all proxy class names
    private static final String proxyClassNamePrefix = "$Proxy";

    // next number to use for generation of unique proxy class names
    private static final AtomicLong nextUniqueNumber = new AtomicLong();

    @Override
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

        Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
        for (Class<?> intf : interfaces) {
            /*
             * Verify that the class loader resolves the name of this
             * interface to the same Class object.
             */
            Class<?> interfaceClass = null;
            try {
                interfaceClass = Class.forName(intf.getName(), false, loader);
            } catch (ClassNotFoundException e) {
            }
            if (interfaceClass != intf) {
                throw new IllegalArgumentException(
                    intf + " is not visible from class loader");
            }
            /*
             * Verify that the Class object actually represents an
             * interface.
             */
            if (!interfaceClass.isInterface()) {
                throw new IllegalArgumentException(
                    interfaceClass.getName() + " is not an interface");
            }
            /*
             * Verify that this interface is not a duplicate.
             */
            if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                throw new IllegalArgumentException(
                    "repeated interface: " + interfaceClass.getName());
            }
        }

        String proxyPkg = null;     // package to define proxy class in
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

        /*
         * Record the package of a non-public proxy interface so that the
         * proxy class will be defined in the same package.  Verify that
         * all non-public proxy interfaces are in the same package.
         */
        for (Class<?> intf : interfaces) {
            int flags = intf.getModifiers();
            if (!Modifier.isPublic(flags)) {
                accessFlags = Modifier.FINAL;
                String name = intf.getName();
                int n = name.lastIndexOf('.');
                String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                if (proxyPkg == null) {
                    proxyPkg = pkg;
                } else if (!pkg.equals(proxyPkg)) {
                    throw new IllegalArgumentException(
                        "non-public interfaces from different packages");
                }
            }
        }

        if (proxyPkg == null) {
            // if no non-public proxy interfaces, use com.sun.proxy package
            proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
        }

        /*
         * Choose a name for the proxy class to generate.
         */
        long num = nextUniqueNumber.getAndIncrement();
        String proxyName = proxyPkg + proxyClassNamePrefix + num;

        /*
         * Generate the specified proxy class.
         */
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
            proxyName, interfaces, accessFlags);
        try {
            return defineClass0(loader, proxyName,
                                proxyClassFile, 0, proxyClassFile.length);
        } catch (ClassFormatError e) {
            /*
             * A ClassFormatError here means that (barring bugs in the
             * proxy class generation code) there was some other
             * invalid aspect of the arguments supplied to the proxy
             * class creation (such as virtual machine limitations
             * exceeded).
             */
            throw new IllegalArgumentException(e.toString());
        }
    }
}

这个类的注释说,通过指定的 ClassLoader 和 接口数组 用工厂方法生成 proxy class。 然后这个 proxy class 的名字是:

// Proxy class 的前缀是 “$Proxy”,
private static final String proxyClassNamePrefix = "$Proxy";

long num = nextUniqueNumber.getAndIncrement();

String proxyName = proxyPkg + proxyClassNamePrefix + num;

所以,动态生成的代理类名称是包名+$Proxy+id序号

生成的过程,核心代码如下:

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);


return defineClass0(loader, proxyName,
                    proxyClassFile, 0, proxyClassFile.length);

下面用一张图让大家记住动态代理涉及到的角色。

在这里插入图片描述

红框中 $Proxy0 就是通过 Proxy 动态生成的。
$Proxy0 实现了要代理的接口。
$Proxy0 通过调用 InvocationHandler 来执行任务。

代理的作用

可能有同学会问,已经学习了代理的知识,但是,它们有什么用呢?

主要作用,还是在不修改被代理对象的源码上,进行功能的增强。

这在 AOP 面向切面编程领域经常见。

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

主要功能
日志记录,性能统计,安全控制,事务处理,异常处理等等。

上面的引用是百度百科对于 AOP 的解释,至于,如何通过代理来进行日志记录功能、性能统计等等,这个大家可以参考 AOP 的相关源码,然后仔细琢磨。

同注解一样,很多同学可能会有疑惑,我什么时候用代理呢?

这取决于你自己想干什么。你已经学会了语法了,其他的看业务需求。对于实现日志记录功能的框架来说,正合适。

至此,静态代理和动态代理者讲完了。

总结

  1. 代理分为静态代理和动态代理两种。
  2. 静态代理,代理类需要自己编写代码写成。
  3. 动态代理,代理类通过 Proxy.newInstance() 方法生成。
  4. 不管是静态代理还是动态代理,代理与被代理者都要实现两样接口,它们的实质是面向接口编程。
  5. 静态代理和动态代理的区别是在于要不要开发者自己定义 Proxy 类。
  6. 动态代理通过 Proxy 动态生成 proxy class,但是它也指定了一个 InvocationHandler 的实现类。
  7. 代理模式本质上的目的是为了增强现有代码的功能。

大佬的解释太全面了 膜拜了!!!有详细了解的可以看大佬博客,链接我放下面了

代理详解

lambda表达式

lambda表达式其实质属于函数式编程的概念

简化多线程,使用Lambda表达式

new Thread(()->{
   System.out.println("我使用了Lambda表达式");
}).start();

为什么要使用Lambda表达式

  • 避免匿名内部类定义过多
  • 可以让你的代码看起来很简洁
  • 去掉了一堆没有意义的代码,只留下核心的逻辑。

理解Lambda表达式

  • 理解Functional Interface(函数式接口)是学习Java8 lambda表达式的关键所在。

  • 函数式接口的定义

    • 任何接口,如果只包含唯一个抽象方法(接口中只存在一个方法),那么它就是一个函数式接口,前提是函数式接口的方法,才能使用lambda表达式

      class MyRunnable implements Runnable{
         @Override
         public void run() {
            System.out.println("我是子线程");
         }
      }
      
    • 对于函数式接口,我们可以通过lambda表达式来创建该接口的对象。

    • lambda表示只能有一行代码的情况下才能简化成一行,若有多行必须用大括号括起来

    • 若方法中含有参数,可以去掉参数类型,若含有多个参数,也可以去掉参数类型,要去掉都去掉,而且必须加上括号。

代码推到Lambda表达式 代码示例如下

public class TestLambda {

   //2、静态内部类
   static class ILike2 implements Like{

      @Override
      public void lambda() {
         System.out.println("我是静态内部类2");
      }
   }

   public static void main(String[] args) {

      Like iLike = new ILike();
      iLike.lambda();

      new ILike2().lambda();

      //3、局部内部类
      class ILike3 implements Like{
         @Override
         public void lambda() {
            System.out.println("我是局部内部类3");
         }
      }
      new ILike3().lambda();

      //4、匿名内部类 没有类的名称 要借助接口或者父类实现
      iLike=new Like() {
         @Override
         public void lambda() {
            System.out.println("我是匿名内部类4");
         }
      };

      iLike.lambda();

      //5、lambda表达式实现
      iLike=()->{
         System.out.println("我是lambdab 5");
      };
      iLike.lambda();


   }
}
//接口
interface Like{
   void lambda();
}
//1、外部类实现
class ILike implements Like{

   @Override
   public void lambda() {
      System.out.println("我是外部实现类1");
   }
}

三、线程状态(五大状态)

状态概念

线程共包括以下 5 种状态:(严格说应该有6种)

1. 新建状态(New): 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。

2. 就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。

3. 运行状态(Running): 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。

4. 阻塞状态(Blocked): 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:

  • (01) 等待阻塞 – 通过调用线程的wait()方法,让线程等待某工作的完成。
  • (02) 同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
  • (03) 其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

5. 死亡状态(Dead): 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

详细解释线程的6种状态 线程六种状态详解

线程状态图:

在这里插入图片描述

在这里插入图片描述

线程方法

方法说明
setPriority(int newPriority)更改线程的优先级
static void sleep(long millis)在指定的毫秒内让正在执行的线程进入休眠状态
void join()等待该线程终止
static void yield()暂停正在执行的线程对象,并执行其他的线程
void interrupt()中断线程,别使用这个方法
boolean isAlive()测试线程是否处于活动状态

停止线程(使用标志位)

  • 不推荐使用JDK提供的 stop()、 destroy()方法。【已废弃】
  • 推荐线程自己停止下来
  • 建议使用一个标志位进行终止变量 当flag=false,则终止线程运行。
public class TestStop implements Runnable{

   boolean flag=true;
   @Override
   public void run() {
      int i=0;
      while (flag){
         System.out.println("子线程"+i++);
      }
   }

   //标志位停止线程
   private void stop() {
      this.flag=false;
   }

   public static void main(String[] args) {

      TestStop testStop = new TestStop();
      new Thread(testStop).start();

      for (int i = 0; i < 1000; i++) {
         System.out.println("主线程"+i);
         if (i==900){
            testStop.stop();
            System.out.println("线程该停止了。。。。");
         }
      }
   }
}

主线程和子线程共同抢占CPU,两者交替执行,当主线程的i==900时,调用了子线程的stop方法,那么子线程停止就不在执行,那么此时程序中就存在一个主线程在执行,直到停止。

线程休眠(sleep)

  • sleep (时间) 指定当前线程阻塞的毫秒数;
  • sleep存在异常InterruptedException;
  • sleep时间达到后线程进入就绪状态;
  • sleep可以模拟网络延时,倒计时等。
  • 每一个对象都有一个锁,sleep不会释放锁;

模拟网络延迟

//模拟网络延时 放大问题的发生性
public class TestSleep implements Runnable {

   //模拟卖票
   public int tickets=50;
   @Override
   public void run() {

      while (tickets>0){
         try {
            Thread.sleep(2000);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
         System.out.println(Thread.currentThread().getName()+"买到了第"+tickets--+"张票");
      }

   }

   public static void main(String[] args) {


      TestSleep testSleep = new TestSleep();

      new Thread(testSleep,"小明").start();
      new Thread(testSleep,"小王").start();
      new Thread(testSleep,"老张").start();
   }
}

以上例子通过三个线程卖票,模拟网络延迟,放大了问题的发生性,我们知道当我们定义多个线程共同抢夺同一个资源时,也没有使用锁肯定是不安全的(两个人拿到同一张票),我们不使用睡眠时,很难发现;当我们使用了sleep后程序就实现了一个睡眠,可以放大问题的发生性,我们很容易看到问题发生的现状(两个人抢到了同一张票)。

sleep获取当前时间

//获取当前系统的时间
public class TestSleep2 {

   public static void main(String[] args) {

      //获取当前的时间
      Date date = new Date(System.currentTimeMillis());
      while (true){
         try {
            Thread.sleep(1000);
            System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
            //更新当前时间
            date=new Date(System.currentTimeMillis());
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
   }
}

线程礼让(yield)

  • 礼让线程 让当前正在执行的线程暂停但不阻塞,是从运行状态返回到就绪状态
  • 将线程从运行状态转为就绪状态
  • 让cpu重新调度,礼让不一定成功!看CPU心情
public class TestYield implements Runnable{
   @Override
   public void run() {
      System.out.println(Thread.currentThread().getName()+"执行");
      //进行礼让,让另一个线程跑,可能不礼让成功
      Thread.yield();
      System.out.println(Thread.currentThread().getName()+"停止");
   }

   public static void main(String[] args) {
      TestYield testYield = new TestYield();

      new Thread(testYield,"a").start();
      new Thread(testYield,"b").start();

   }
}
礼让成功:
b执行
a执行
b停止
a停止
不礼让:
b执行
b停止
a执行
a停止

线程插队(join)

  • Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
  • 可以想象成插队
  • a b 两个线程,在a队伍跑的过程中,b线程来插队,那么a就发生了阻塞,停止执行,直到b线程执行完,a才能执行。
 */
//线程插队join
public class TestJoin implements  Runnable{
   @Override
   public void run() {
      for (int i = 0; i < 100; i++) {
         System.out.println("子线程"+i);
      }
   }

   public static void main(String[] args) {

      TestJoin testJoin = new TestJoin();
      Thread thread = new Thread(testJoin);
      thread.start();


      for (int i = 0; i < 500; i++) {
         //当我们主线程i==200时,让子线程进行一个插队
         if (i==50){
            try {
               thread.join();
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
         }

         System.out.println("主线程"+i);
      }
   }
}

当主线程的i为50时,此时子线程要全部跑完

线程状态观测

在这里插入图片描述

    • NEW
      线程尚未开始在这个国家。
    • RUNNABLE
      处于这种状态中的java虚拟机执行的线程。
    • BLOCKED
      线程阻塞等待监控锁在这个国家。
    • WAITING
      处于这种状态的线程被无限期地等待另一个线程来执行特定的动作。
    • TIMED_WAITING
      处于这种状态的线程正在等待另一个线程上执行一个动作指定的等待时间。
    • TERMINATED
      处于这种状态的线程退出。

    一个线程可以在一个给定的时间点上只有一个状态。这些状态是不反映任何操作系统线程状态的虚拟机状态。

    public class TestThreadState {
    
       public static void main(String[] args) {
          Thread thread=new Thread(()->{
    
             try {
                Thread.sleep(1000);
             } catch (InterruptedException e) {
    
             }
             System.out.println("子线程");
          });
    
          //创建完对象后,观察线程的状态
          Thread.State state = thread.getState();
          System.out.println(state);
    
          //start后线程进入就绪状态
          thread.start();
          System.out.println(thread.getState());
    
          while (state!=Thread.State.TERMINATED){
             //若线程不死 就一直输出
             //等待状态说明执行中,出现了阻塞睡眠
             try {
                Thread.sleep(100);
                state=thread.getState();
                System.out.println(state);
             } catch (InterruptedException e) {
    
             }
          }
          //线程停止之后不能再次运行,会出现异常
             //Exception in thread "main" java.lang.IllegalThreadStateException
    		 //at java.lang.Thread.start(Thread.java:708)
    		 //at com.zz.state.TestThreadState.main(TestThreadState.java:40)
          //thread.start();
       }
    }
    输出结果:
    NEW
    RUNNABLE
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    子线程
    TERMINATED
    

线程优先级

  • Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行
  • 线程的优先级用数字表示,范围从1~10.
    • Thread.MIN_PRIORITY = 1;
    • Thread.MAX_PRIORITY = 10;
    • Thread.NORM_PRIORITY = 5;
  • 使用以下方式改变或获取优先级
    • getPriority() . setPriority(int xxx)
  • 线程优先级最大设置为10,最小为1,不在这个范围内的都会报出异常
  • 每一个程序都是主线程先执行,后执行子线程,默认线程优先级为5

优先级的设定建议在start之前进行设置

优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,这都是看CPU的调度,也有可能低优先级的线程先调度

public class TestPriority implements Runnable{
   @Override
   public void run() {
      System.out.println(Thread.currentThread().getName()+"----->"+Thread.currentThread().getPriority());
   }

   public static void main(String[] args) {

      System.out.println("主线程优先级"+Thread.currentThread().getPriority());

      TestPriority testPriority = new TestPriority();

      Thread thread1=new Thread(testPriority,"线程1");
      thread1.setPriority(1);
      thread1.start();

      Thread thread2=new Thread(testPriority,"线程2");
      thread2.setPriority(Thread.MAX_PRIORITY);//10
      thread2.start();

      Thread thread3=new Thread(testPriority,"线程3");
      thread3.setPriority(3);
      thread3.start();
   }
}

守护(daemon)线程

  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕 如gc线程
  • 如,后台记录操作日志,监控内存,垃圾回收等待…
  • 设置为守护线程核心就是将线程的setDaemon(true)设置为true
//守护线程
public class TestDaemon {

   public static void main(String[] args) {


      God god = new God();

      You you = new You();

      Thread thread = new Thread(god);
      //将上帝设置为守护线程
      //默认为false,正常的线程都是用户线程
      thread.setDaemon(true);
      //开启守护线程
      thread.start();

      new Thread(you).start();
   }

}

//上帝模拟守护线程
class God implements Runnable{

   @Override
   public void run() {
      while (true){
         System.out.println("上帝一直保佑着你");
      }

   }
}

//用户线程
class You implements Runnable{
   @Override
   public void run() {
      for (int i = 0; i < 36500; i++) {
         System.out.println("hello world 第"+i+"天");
      }

      System.out.println("GoodBy world");
   }
}

当用户线程死后,我们看到守护线程还在执行一段时间,原因是当我们销毁用户线程后也需要一段时间,因为cpu切换速度比较快,所以我们的守护线程也会运行一段时间。

四、线程同步(重点)

并发和并行

并发是两个队列交替使用同一台咖啡机,并行是两个队列同时使用两台咖啡机,如果串行,一个队列使用一台咖啡机,那么哪怕前面那个人便秘了去厕所呆半天,后面的人也只能死等着他回来才能去接咖啡,这效率无疑是最低的。

img

并发是不是一个线程,并行是多个线程?

并发和并行都可以是很多个线程,就看这些线程能不能同时被(多个)cpu执行,如果可以就说明是并行,而并发是多个线程被(一个)cpu 轮流切换着执行。

并发(多线程操作同一个资源)

  • CPU一核,模拟出来多条线程,天下武功,唯快不破,快速交替

并行(多个人一起行走)

  • CPU多核,多个线程可以同时执行;提高性能可以用线程池

并发编程的本质:充分利用CPU性能

线程同步

  • 现实生活中,我们会遇到 ” 同一个资源 , 多个人都想使用 ” 的问题 , 比如,食堂排队 打饭 , 每个人都想吃饭 , 最天然的解决办法就是 , 排队 . 一个个来.

  • 处理多线程问题时 , 多个线程访问同一个对象 , 并且某些线程还想修改这个对象 . 这时候我们就需要线程同步 . 线程同步其实就是一种等待机制 , 多个需要同时访问此对象的线程进入这个对象的等待池 形成队列, 等待前面线程使用完毕 , 下一个线程再使用

  • 由于同一进程的多个线程共享同一块存储空间 , 在带来方便的同时,也带来了访问 冲突问题 , 为了保证数据在方法中被访问时的正确性 , 在访问时加入 锁机制

    synchronized , 当一个线程获得对象的排它锁 , 独占资源 , 其他线程必须等待 , 使用后释放锁即可 . 存在以下问题 :

    • 一个线程持有锁会导致其他所有需要此锁的线程挂起 ;
    • 在多线程竞争下 , 加锁 , 释放锁会导致比较多的上下文切换 和 调度延时,引起性能问题 ;
    • 如果一个优先级高的线程等待一个优先级低的线程释放锁 会导致优先级倒置 , 引起性能问题

同步方法

  • 由于我们可以通过 private 关键字来保证数据对象只能被方法访问 , 所以我们只需要针对方法提出一套机制 , 这套机制就是 synchronized 关键字 , 它包括两种用法 synchronized 方法 和synchronized 块
    • synchronized 方法 是我们共享的资源通过一个方法执行 同步方法 : public synchronized void method(int args) {}
    • synchronized 块 是我们共享的资源是一个对象 块 synchronized (accout){ }
  • synchronized方法控制对 “对象” 的访问 , 每个对象对应一把锁 , 每个synchronized方法都必须获得调用该方法的对象的锁才能执行 , 否则线程会阻塞 , 方法一旦执行 , 就独占该锁 , 直到该方法返回才释放锁 , 后面被阻塞的线程才能获得这个锁 , 继续执行

缺陷 : 若将一个大的方法申明为synchronized 将会影响效率

同步方法弊端

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mejYjQnY-1616034588101)(C:\Users\张哲\AppData\Local\Temp\1615854026729.png)]

同步块

  • 同步块 : synchronized (Obj ) { }
  • Obj 称之为 同步监视器
    • Obj 可以是任何对象 , 但是推荐使用共享资源作为同步监视器
    • 同步方法中无需指定同步监视器 , 因为同步方法的同步监视器就是this , 就是这个对象本身 , 或者是 class [ 反射中讲解 ]
  • 同步监视器的执行过程
    • 第一个线程访问 , 锁定同步监视器 , 执行其中代码 .
    • 第二个线程访问 , 发现同步监视器被锁定 , 无法访问 .
    • 第一个线程访问完毕 , 解锁同步监视器 .
    • 第二个线程访问, 发现同步监视器没有锁 , 然后锁定并访问

代码示例

卖票

这里多个线程资源抢占票,若不适用锁机制不同对象会抢到同一张票,还会造成票的负数,显然这是线程不安全的,那么我们使用synchronized方法实现线程安全

//多个对象进行抢票 通过给票上锁来实现线程安全
public class Ticket {
   public static void main(String[] args) {

      MyTicket myTicket = new MyTicket();
      new Thread(myTicket).start();
      new Thread(myTicket).start();
      new Thread(myTicket).start();
   }
}

class MyTicket implements Runnable {


   public int tickets = 20;

   boolean flag=true;
   @Override
   public void run() {
      while (true) {
         buy();
         try {
            Thread.sleep(10);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
   }

   //synchronized 同步方法,锁的是 this MyTicket这个资源
   public synchronized void buy() {
      if (tickets<0){
         flag=false;
         return;
      }
         System.out.println(Thread.currentThread().getName() + "抢到了" + tickets--);
   }
}

取钱(ACID补充)

两个线程进行取钱,取同一个账户,若不加锁两个线程会同时操控一个资源进行,会造成当a取钱时,b也同时进行取钱,就会造成ab同时消耗这个资源,会造成数据的脏读。

补充

1、脏读:事务A读到了事务B未提交的数据。

2、不可重复读:事务A第一次查询得到一行记录row1,事务B提交修改后,事务A第二次查询得到row1,但列内容发生了变化。

3、幻读:事务A第一次查询得到一行记录row1,事务B提交修改后,事务A第二次查询得到两行记录row1和row2。

ACID特效

事务是恢复和并发控制的基本单位。

事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。

原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。

一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。

隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

/**
 * 模拟夫妻取钱,开启两个线程,同时取钱,一个账户
 */
public class Bannk {

   public static void main(String[] args) {
      Accout accout = new Accout(100,"卖废品");
      MyThread myThread1 = new MyThread(accout,50);
      myThread1.start();
      MyThread myThread2 = new MyThread(accout,50);
      myThread2.start();
   }
}

class Accout{
   //银行卡余额
   public int money;
   //存钱备注
   public String name;

   public Accout(int money, String name) {
      this.money = money;
      this.name = name;
   }
}

class MyThread extends Thread{

   public Accout accout;
   //取的钱
   public int quMoney;
   //手里的钱
   public int nowMoney;

   public MyThread(Accout accout, int quMoney) {
      this.accout = accout;
      this.quMoney = quMoney;
   }

   boolean flag=true;
   @Override
   public void run() {
      //锁的对象时变化的对象,这里若使用同步方法,指代this代表银行,明显不行
      //使用代码块,锁住变化的对象账户accout
         synchronized (accout){
            //判断取的钱 是否多余账户余额
            if (quMoney>accout.money){
               flag=false;
               System.out.println(Thread.currentThread().getName()+"取了"+quMoney+"银行卡余额不足,仅剩"+accout.money);
               return;
            }
            try {
               Thread.sleep(1000);
            } catch (InterruptedException e) {

            }

            //正常取钱
            accout.money=accout.money-quMoney;
            nowMoney+=quMoney;

            System.out.println(Thread.currentThread().getName()+"取了"+quMoney+",手里:"+nowMoney+",银行卡余额:"+accout.money);
         }
   }
}

ArrayList

不安全原因是当两个线程同时执行的时候,将两个元素加到了同一个数组,就会造成集合的长度小于10000,

Vector 是县城线程安全的

public class unSafeList {

   public static void main(String[] args) {


      ArrayList<Object> list = new ArrayList<>();
      //Vector<Object> objects = new Vector<>();
      //不安全原因是当两个线程同时执行的时候,将两个元素加到了同一个数组

         for (int i = 0; i < 10000; i++) {
            new Thread(()->{
               //这里变化的资源 是list所以锁住该对象
               synchronized (list){
                  list.add(Thread.currentThread().getName());
               }

            }).start();
         }

      try {
         Thread.sleep(10);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }

      System.out.println("集合中大小:"+list.size());
   }
}

CopyOnWriteArrayList 线程安全

CopyOnWriteArrayList 是JUC下的一个方法,在java.util.concurrent.CopyOnWriteArrayList这个包下面

//线程安全
public class TestConyOnWriteArraylist {

   public static void main(String[] args) {
      CopyOnWriteArrayList<Object> list = new CopyOnWriteArrayList<>();

      for (int i = 0; i < 1000; i++) {
         new Thread(()->{
            list.add(Thread.currentThread().getName());
         }).start();
      }

      try {
         Thread.sleep(1000);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }

      System.out.println(list.size());

   }
}

源码分析 CopyOnWriteArrayList为什么是线程安全的

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    private static final long serialVersionUID = 8673264195747942595L;

    /** The lock protecting all mutators */
    //这里使用了可重复锁,是JUC里面的锁,后面会讲到
    final transient ReentrantLock lock = new ReentrantLock();

    /** The array, accessed only via getArray/setArray. */
    //使用了volatile关键字 保证了原子性
    private transient volatile Object[] array;

    /**
     * Gets the array.  Non-private so as to also be accessible
     * from CopyOnWriteArraySet class.
     */
    final Object[] getArray() {
        return array;
    }

序列化和反序列化(Serializable)

什么是序列化接口?

一个对象序列化的接口,一个类只有实现了Serializable接口,它的对象才能被序列化。

什么是序列化?

序列化是将对象状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它将流转换为对象。这两个过程结合起来,可以轻松地存储和传输数据。

序列化和反序列化转换过程

把对象转换为字节序列的过程称为对象的序列化

把字节序列恢复为对象的过程称为对象的反序列化

什么情况下需要序列化?

  • 当我们需要把对象的状态信息通过网络进行传输,或者需要将对象的状态信息持久化,以便将来使用时都需要把对象进行序列化
  • 为什么还要继承Serializable。那是存储对象在存储介质中,以便在下次使用的时候,可以很快捷的重建一个副本

在开发过程中并没有使用序列化 为什么那?来看一下序列化源码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rLx5DtLI-1616034588102)(C:\Users\张哲\AppData\Local\Temp\1615861257263.png)]

一个接口里面什么内容都没有,我们可以将它理解成一个标识接口。

比如在课堂上有位学生遇到一个问题,于是举手向老师请教,这时老师帮他解答,那么这位学生的举手其实就是一个标识,自己解决不了问题请教老师帮忙解决。在Java中的这个Serializable接口其实是给jvm看的,通知jvm,我不对这个类做序列化了,你(jvm)帮我序列化就好了。

Serializable接口就是Java提供用来进行高效率的异地共享实例对象的机制,实现这个接口即可。

什么是JVM?

JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。

为什么要使用SerializableUID变量

从说明中我们可以看到,如果我们没有自己声明一个serialVersionUID变量,接口会默认生成一个serialVersionUID

但是强烈建议用户自定义一个serialVersionUID,因为默认的serialVersinUID对于class的细节非常敏感,反序列化时可能会导致InvalidClassException这个异常。

在前面我们已经新建了一个实体类User实现Serializable接口,并且定义了serialVersionUID变量。

读写文件的代码示例

import java.io.*;

/**
 * @Author zz
 * @Date 2021/3/16 16:03
 */
public class TestSerializable {

   public static void main(String[] args) {
      writeUser();
      readUser();
   }

   public static void writeUser(){
      User user = new User();
      user.setId(1);
      user.setName("zz");
      user.setAge(21);

      String file="G:\\idea\\bilibili\\kuang\\Thread\\src\\com\\zz\\ThreadSyn\\a.txt";
      try {
         ObjectOutputStream osw = new ObjectOutputStream(new FileOutputStream(new File(file)));
         osw.flush();
         osw.writeObject(user);
         System.out.println("序列化成功");
         osw.close();
      } catch (Exception e) {
         System.out.println(e.getMessage());
      }
   }

   public static void readUser(){
      String file="G:\\idea\\bilibili\\kuang\\Thread\\src\\com\\zz\\ThreadSyn\\a.txt";
      try {
         ObjectInputStream isr = new ObjectInputStream(new FileInputStream(new File(file)));
         User user = (User) isr.readObject();
         System.out.println(user.toString());
         System.out.println("反序列化成功");
      } catch (Exception e) {
         System.out.println(e.getMessage());
      }

   }
}

class User implements Serializable{

   private static final Long serialVersionUID=1L;
   private int id;
   private String name;
   private int age;

   public static Long getSerialVersionUID() {
      return serialVersionUID;
   }

   public int getId() {
      return id;
   }

   public void setId(int id) {
      this.id = id;
   }

   public String getName() {
      return name;
   }

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

   public int getAge() {
      return age;
   }

   public void setAge(int age) {
      this.age = age;
   }

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

结果:
序列化成功
User{id=1, name='zz', age=21}
反序列化成功

这个serialVersionUID是用来辅助对象的序列化与反序列化的,原则上序列化后的数据当中的serialVersionUID与当前类当中的serialVersionUID一致,那么该对象才能被反序列化成功。这个serialVersionUID的详细的工作机制是:在序列化的时候系统将serialVersionUID写入到序列化的文件中去,当反序列化的时候系统会先去检测文件中的serialVersionUID是否跟当前的文件的serialVersionUID是否一致,如果一直则反序列化成功,否则就说明当前类跟序列化后的类发生了变化,比如是成员变量的数量或者是类型发生了变化,那么在反序列化时就会发生crash,并且回报出错误:

实体类不实现序列化接口,将会报以下异常:

java.io.NotSerializableException: com.zz.ThreadSyn.User
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at com.zz.ThreadSyn.TestSerializable.writeUser(TestSerializable.java:26)
	at com.zz.ThreadSyn.TestSerializable.main(TestSerializable.java:12)
java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: com.zz.ThreadSyn.User
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1572)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:428)
	at com.zz.ThreadSyn.TestSerializable.readUser(TestSerializable.java:38)
	at com.zz.ThreadSyn.TestSerializable.main(TestSerializable.java:13)
Caused by: java.io.NotSerializableException: com.zz.ThreadSyn.User
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at com.zz.ThreadSyn.TestSerializable.writeUser(TestSerializable.java:26)
	at com.zz.ThreadSyn.TestSerializable.main(TestSerializable.java:12)

死锁

  • 多个线程各自占有一些共享资源 , 并且互相等待其他线程占有的资源才能运行 , 而导致两个或者多个线程都在等待对方释放资源 , 都停止执行的情形 . 某一个同步块 同时拥有 “ 两个以上对象的锁 ” 时 , 就可能会发生 “ 死锁 ” 的问题 .

代码示例

代码示例 小明 小王两个线程 枪 布娃娃 两个资源 小明首先拥有枪资源,小王首先拥有布娃娃资源;两个线程各自争夺对方手中的资源,产生死锁

public class DeadLock {

   public static void main(String[] args) {

      DeadThread thread1= new DeadThread(0,"小明");
      thread1.start();

      DeadThread thread2= new DeadThread(1,"小王");
      thread2.start();

   }
}

//两个资源
//枪
class Gun{

   String name="枪";


}
//布娃娃
class Doll {

   String name = "布娃娃";

}



class DeadThread extends Thread{

   static Gun gun=new Gun();

   static Doll doll=new Doll();

   int choice;
   String name;

   public DeadThread(int choice,String name){
      this.choice=choice;
      this.name=name;
   }

   @Override
   public void run() {
      makeup();
   }


   public void makeup(){
      if (choice==0){
         synchronized (gun){
            System.out.println(Thread.currentThread().getName()+"获得了----》"+gun.name+"的锁");
            //当走在这里的时候,小王也拿到了doll的锁并没有释放,
            synchronized (doll){
               System.out.println(Thread.currentThread().getName()+"获得了----》"+gun.name+"的锁");
            }
         }
      }else {
         synchronized (doll){
            System.out.println(Thread.currentThread().getName()+"获得了----》"+doll.name+"的锁");
            //这里小明一开始就拿到了 gun的锁并没有释放,故就产生了死锁
            synchronized (gun){
               System.out.println(Thread.currentThread().getName()+"获得了----》"+gun.name+"的锁");
            }
         }
      }
   }
}

结果 程序一直停在此状态,也不停止

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RPnBjyzx-1616034588102)(C:\Users\张哲\AppData\Local\Temp\1615965740288.png)]

解决死锁

当小王想要抢小明手中枪的时候,必须等待对方释放锁后,才能拿得到,不在产生死锁

public void makeup(){
   if (choice==0){
      synchronized (gun){
         System.out.println(Thread.currentThread().getName()+"获得了----》"+gun.name+"的锁");
         //当走在这里的时候,小王也拿到了doll的锁并没有释放,
      }
      try {
         Thread.sleep(100);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      synchronized (doll){
         System.out.println(Thread.currentThread().getName()+"获得了----》"+doll.name+"的锁");
      }
   }else {
      synchronized (doll){
         System.out.println(Thread.currentThread().getName()+"获得了----》"+doll.name+"的锁");
         //这里小明一开始就拿到了 gun的锁并没有释放,故就产生了死锁

      }
      try {
         Thread.sleep(100);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      synchronized (gun){
         System.out.println(Thread.currentThread().getName()+"获得了----》"+gun.name+"的锁");
      }
   }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xpUrSMH4-1616034588103)(C:\Users\张哲\AppData\Local\Temp\1615965981916.png)]

死锁避免方法

产生死锁的四个必要条件:

  1. 互斥条件:一个资源每次只能被一个进程使用。
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件 : 进程已获得的资源,在末使用完之前,不能强行剥夺。
  4. 循环等待条件 : 若干进程之间形成一种头尾相接的循环等待资源关系。

上面列出了死锁的四个必要条件,我们只要想办法破其中的任意一个或多个条件就可以避免死锁发生

Lock(锁)

  • 从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当
  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
  • ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁
public class TestLock {

   public static void main(String[] args) {
      LockThread lockThread = new LockThread();

      new Thread(lockThread).start();
      new Thread(lockThread).start();
      new Thread(lockThread).start();


   }
}

class LockThread implements Runnable {

   private int tickets = 10;
   ReentrantLock lock = new ReentrantLock();

   @Override
   public void run() {
      while (true) {

         try {
            lock.lock();
            if (tickets > 0) {
               System.out.println(Thread.currentThread().getName() + "---->" + tickets--);
               Thread.sleep(1000);
            } else {
               break;
            }

         } catch (Exception e) {
            e.printStackTrace();
         } finally {
            lock.unlock();
         }
      }

   }
}

synchronized 与 Lock 的对比

  • Lock是显式锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,出了作用域自动释放
  • Lock只有代码块锁,synchronized有代码块锁和方法锁
  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
  • 优先使用顺序:
    • Lock > 同步代码块(已经进入了方法体,分配了相应资源)> 同步方法(在方法体之外)

线程协作

生产者消费者模式

线程通信

  • 应用场景 : 生产者和消费者问题
  • 假设仓库中只能存放一件产品 , 生产者将生产出来的产品放入仓库 , 消费者将仓库中产品取走消费 .
  • 如果仓库中没有产品 , 则生产者将产品放入仓库 , 否则停止生产并等待 , 直到仓库中的产品被消费者取走为止 .
  • 如果仓库中放有产品 , 则消费者可以将产品取走消费 , 否则停止消费并等待 ,直到仓库中再次放入产品为止。

这是一个线程同步问题 , 生产者和消费者共享同一个资源 , 并且生产者和消费者之 间相互依赖 , 互为条件 .

  • 对于生产者 , 没有生产产品之前 , 要通知消费者等待 . 而生产了产品之后 , 又需要马上通知消费者消费
  • 对于消费者 , 在消费之后 , 要通知生产者已经结束消费 , 需要生产新的产品以供消费.
  • 产者消费者问题中 , 仅有synchronized是不够的
  • nchronized 可阻止并发更新同一个共享资源 , 实现了同步
  • nchronized 不能用来实现不同线程之间的消息传递 (通信)

Java提供了几个方法解决线程之间的通信问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4BTZVjS8-1616034588104)(C:\Users\张哲\AppData\Local\Temp\1615980346603.png)]

注意:均是Object类的方法 , 都只能在同步方法或者同步代码块中使用,否则会抛出异常IllegalMonitorStateException

生产者消费者问题

管程法

并发协作模型 “ 生产者 / 消费者模式 ” —>管程法

生产者 : 负责生产数据的模块 (可能是方法 , 对象 , 线程 , 进程) ;

消费者 : 负责处理数据的模块 (可能是方法 , 对象 , 线程 , 进程) ;

缓冲区 : 消费者不能直接使用生产者的数据 , 他们之间有个 “ 缓冲区

生产者将生产好的数据放入缓冲区 , 消费者从缓冲区拿出数据

public class TestShoppig {

   public static void main(String[] args) {

      SyContainer syContainer = new SyContainer();

      new Product(syContainer).start();
      new Consumer(syContainer).start();

   }
}

//定义两个线程 生产者 消费者
class Product extends Thread {

   SyContainer syContainer;
   public Product(SyContainer syContainer){
      this.syContainer=syContainer;
   }

   @Override
   public void run() {
      super.run();

      for (int i = 1; i < 100; i++) {
         syContainer.push(new Chicken(i));
         System.out.println("生产了--->" + i);
      }
   }
}

class Consumer extends Thread {

   SyContainer syContainer;
   public Consumer(SyContainer syContainer){
      this.syContainer=syContainer;
   }

   @Override
   public void run() {
      super.run();
      for (int i = 1; i < 100; i++) {
         Chicken pop = syContainer.pop();
         System.out.println("消费了"+pop.count);
      }
   }
}

//鸡
class Chicken {
   //数量
   int count;

   public Chicken(int id) {
      this.count = id;
   }
}

//盘子
class SyContainer {

   int count = 0;
   //一个盘子放10只鸡
   Chicken[] chickens = new Chicken[10];

   //生产者 放入产品
   public synchronized void push(Chicken chicken) {
      if (count == chickens.length) {
         //盘子已满 通知消费者消费,生产者等待
         try {
            this.wait();
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      } else {
         //未满 生产丢入盘子
         chickens[count] = chicken;
         count++;
         //通知消费者消费,唤醒消费者
         this.notifyAll();

      }
   }

   //消费者消费产品
   public synchronized Chicken pop() {
      if (count == 0) {
         //盘子为空,通知生产者生产,消费者等待\\
         try {
            this.wait();
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
      //可以消费
      count--;
      Chicken chicken = chickens[count];
      this.notifyAll();
      return chicken;
   }
}

信号灯法

并发协作模型 “ 生产者 / 消费者模式 ”

package com.zz.ThreadComm;

/**
 * @Author zz
 * @Date 2021/3/17 21:48
 */
public class TestTv {

   public static void main(String[] args) {

      Tv tv = new Tv();
      new Thread(new Player(tv)).start();
      new Thread(new Watcher(tv)).start();
   }
}

class Tv {

   //演员表演 观众观看
   private String voice;

   boolean flag=true;

   public synchronized void play(String voice){
      if (!flag){
         try {
            this.wait();
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
      System.out.println("表演了"+voice);
      //唤醒观众去看
      this.notifyAll();
      this.flag=!this.flag;
      this.voice=voice;
   }

   public synchronized void watch(){
      if (flag){
         try {
            //等待演出
            this.wait();
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
      System.out.println("观看了--->"+voice);

      this.notifyAll();
      this.flag=!this.flag;
   }
}

//演出者
class Player implements Runnable{

   Tv tv;

   public Player(Tv tv) {
      this.tv = tv;
   }

   @Override
   public void run() {

      for (int i = 0; i < 10; i++) {
         if (i%2==0){
            tv.play("光头强");
         }
         tv.play("喜洋洋");
      }
   }


}

//观众
class Watcher implements Runnable{

   Tv tv;

   public Watcher(Tv tv) {
      this.tv = tv;
   }

   @Override
   public void run() {
      for (int i = 0; i < 10; i++) {
         tv.watch();
      }
   }
}

线程池法

  • 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
  • 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。
  • 可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
  • 好处:
    • 提高响应速度(减少了创建新线程的时间)
    • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
    • 便于线程管理(…)
      • corePoolSize:核心池的大小
      • maximumPoolSize:最大线程数
      • keepAliveTime:线程没有任务时最多保持多长时间后会终止
  • JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors
  • ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
    • void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
    • Future submit(Callable task):执行任务,有返回值,一般又来执行Callable
    • void shutdown() :关闭连接池
  • Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
public class TestPool {

   public static void main(String[] args) {
      //创建线程池 后面参数为线程池大小
      ExecutorService executorService = Executors.newFixedThreadPool(5);

      executorService.execute(new ThreadPool());
      executorService.execute(new ThreadPool());
      executorService.execute(new ThreadPool());
      executorService.execute(new ThreadPool());
      executorService.execute(new ThreadPool());

      //关闭连接
      executorService.shutdown();


   }
}

class ThreadPool implements Runnable {

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

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3Gx8l1Pk-1616034588106)(C:\Users\张哲\AppData\Local\Temp\1615993136998.png)]

  • 3
    点赞
  • 72
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值