【Java知识点整理】线程 和 同步锁

线程

一、线程的生命周期(线程类)

  • 什么时候开始

    • 当创建线程类的对象时?

    • 使用线程类对象的start()方法?——( ✅ )

      • 思考,使用start方法开始运行了吗?

        1. 没有,run没有立即执行

          • 因为CPU同时只能运行一个线程,但是每秒中可以快速运行非常多个线程,那么各个线程就需要有一个排队的概念
        2. start方法的作用其实就是准备就绪简单理解就是告诉CPU这个线程已经准备就绪了

          • 什么时候开始运行?是由CPU决定
          • 在Java中的线程是基于JVM下的线程,所以是由JVM控制
        3. 只有当这个线程就绪,才有可能会运行

  • 什么时候结束?

    • 当run方法执行完毕后,就结束了声明周期
  • 什么时候会暂停

    • 通过sleep()方法,进入睡眠状态
    • wait() 进入等待状态
    • 遇到阻塞命令,进入阻塞状态
    • 挂起状态,使用比较少了pause()

在这里插入图片描述

二、如何创建线程?

  • 继承Thread
    • 单继承
  • 实现接口Runnable
    • 用的比较多,一个类可以实现多个接口,比较方法使用
    • 这样的实现类又被成为子线程
  • Swing 的定时器,定时任务等

三、子线程

1、使用思路
  1. 创建一个某个类,继承Runnable ,并重写run()方法
  2. 要调用这个线程,需要创建一个Thread类,并且将实现类作为构造函数的参数传入
  3. 调用Thread类的成员方法start()启动即可

四、线程的属性

四个线程属性

1、是否是守护线程(后台线程) (isDaemon)
  • 什么是守护线程?
    • 当进程中其他线程结束后,守护线程会自动死亡(结束)
    • 守护的是用户线程
    • 它不影响JVM结束运行,当用户线程都结束了,它会和JVM一起结束。
  • 守护线程三个特性
    1. 线程类型默认继承自父线程,即守护线程创建的线程也是守护线程,
    2. 被谁启动,通常守护线程都是由JVM启动,在JVM启动的时候只有一个非守护线程,就是main线程,其他的都是守护线程。
    3. 不影响JVM的退出,JVM想退出的时候只看有没有用户线程,不看有没有守护线程。
  • 用法:setDaemon(true):设置为守护线程
2、优先级(Priority)
  • 一般不进行设置 。程序设计不应该依赖于优先级
  • 这个属性的目的是 —— 告诉线程调度器,用户希望哪些线程相对多运行、哪些少运行。
  • 一共1到10,10 种优先级,未经修改的情况下都是5。
3、名字
  • 一般也不进行设置。

  • 用法setName()设置线程名字。

    • 会影响线程内执行获取线程名称是获取到的名字

      获取名称的用法Thread.currentThread.getName()

4、线程ID
  • 和名字类似
  • 同一个ID会被先后不同的线程使用,唯一性不能保证;
  • ID不允许被修改。
  • ID是从1开始,并自增的

五、线程的方法

1、join方法
  • 用法:主线程方法内 thread1.join()
  • 线程1作为主线程的 的一部分(子线程)
  • 作用:
    • 只有当子线程运行结束后,主线程才会执行
    • 用于控制线程运行顺序。

六、不同的线程对同一个对象操作?

比如,两个线程分别对一个int类型的num,进行100万次++

  • 线程的执行num++的过程
    1. 先从num取值
    2. 将值存放于寄存器,执行++操作
    3. 将++后结果存放回num出
  • 两个线程同时操作num时,有可能第一步取值的时候,取到的是同一个值,就会导致丢失后续的第二步和第三步

那么要如何解决这个问题呢?

  • 涉及到两个线程之间的关系
    • 独占的资源:只运行一个人(线程)使用
      • 当线程拿到资源,对其所做的操作其他线程无法干扰
      • 做的操作【代码】 ——转换—— 【同步的代码块】
七、Synchronized 同步代码块

面试常考

  • 用法:(共三种用法

    synchronized (类名.class){
        类名.属性++;		// 操作这个类的属性的
    }
    
  • 举个例子

    public class ThreadAdd1 extends Thread{
        @Override
        public void run() {
            for (int i = 0; i < 1000_0000; i++) {
                synchronized (DemoAdd.class){
                    DemoAdd.num++;
                }
            }
            System.out.println("1结果:"+DemoAdd.num);
        }
    }
    
  • 其他相关知识点后续再补充

八、线程安全

线程安全?线程不安全?

对象或数组或集合:添加,删除,修改,查找

  • 线程不安全:当两个线程对集合中某些内容进行添加,会导致覆盖的线程发生

在这里插入图片描述

  • 解决方法,可以对添加操作 添加锁, 或者 选择线程安全的集合存放
    • 线程安全:Vector 慢
    • 线程不安全:ArrayList 快
## 完善单例模式
  • 原本的单例模式,在多人同时调用生成对象方法getInstance()时,可能会同时生成多个对象

  • 解决方法,加入同步锁

    private static JdbcUtils jdbcUtils;
    
    private JdbcUtils() {
    }
    // 单例模式
    public static JdbcUtils getInstance(){
        // 加入同步锁,线程是安全的,速度就变慢了
        synchronized (JdbcUtils.class){
            if (jdbcUtils == null){
                jdbcUtils = new JdbcUtils();
            }
        }
        return jdbcUtils;
    }
    
    • 加入同步锁后,线程是安全的,速度就变慢了

网络编程

一、继续完善群聊功能

  • 建议使用两个模块,相互独立!
  • 服务端:可能会阻塞的方法:aceept/readline
  • 客户端:可能会阻塞的方法:readline
  • 有阻塞的地方尽量放在线程内执行
1、优化服务器端
  • 将原本存储scoket对象的列表,升级为 存放【线程】对象的列表

  • 完善线程类

    • 建立连接,获取封装流在构造函数内实现

    • 新增方法封装了:实现输出信息的方法

    • 如例子:

      package com.cykj.net;
      
      import java.io.*;
      import java.net.Socket;
      
      public class SocketRevThread extends Thread{
          private Socket socket;
          private BufferedReader reader;
          private PrintWriter printWriter;
      
          public SocketRevThread() {
          }
      
          public SocketRevThread(Socket socket) {
              this.socket = socket;
              try {
                  // 读取socket的输入流
                  InputStream inputStream = socket.getInputStream();
                  // 转换为封装流
                  reader = new BufferedReader(new InputStreamReader(inputStream));
      
                  // 读取输出流并转为封装流
                  printWriter = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
              } catch (IOException e) {
                  e.printStackTrace();
              }
      
          }
      
          // run方法要利用到readline的阻塞
          @Override
          public void run() {
              try {
      
                  // 加入while循环 让线程持续进行
                  while (true){
                      // 阻塞 。获取到输入的内容
                      String str = reader.readLine();
                      System.out.println(str);
      
                      // 遍历列表 调用对应的write方法 实现消息群发
                      for (SocketRevThread socketThread : ServerMain.socketList ){
                         socketThread.write(str);
                      }
                  }
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
      
          // write方法用于 输出
          public void write(String line) {
              printWriter.println(line);
              printWriter.flush();
          }
      }
      
2、优化客户端
  • 创建连接可以直接在main方法内实现

    • 并且可以直接将对应的线程对象设置全局变量
  • 访问数据库应该是由服务器端来处理——

优化访问数据库的方式
  • 应该由服务器端来访问,

  • 客户端给服务器传递消息: 有账号密码、聊天信息——如何区分?

    • 如何区分不同的消息?
    • 定义一个特殊的格式,用某个特殊符号,将一行内容分为两段
      • 登陆信息: login && 账号 && 密码
      • 聊天信息: chat && 聊天信息
      • 注册信息:regist && 账号 && 密码 && 性别。。。
    • 发送时,按照格式进行拼接
    • 接受时,用split()方法,将字符串拆分好
    • 再返回结果时,最好也是按这个格式来返回信息
      • login && true
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Xcong_Zhu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值