一篇搞懂Java多线程(全网最细)(三)

简介: 一篇搞懂Java多线程(全网最细)(三)

五、线程同步

1. 概念

       即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该地址进行操作,而其他线程又处于等待状态,实现线程同步的方法有很多种。

1、为什么要创建多线程?

在一般情况下,创建一个线程是不能提高程序执行效率的,所以要创建多个线程。

2、为什么要线程同步?

多个线程同时运行的时候可能调用线程函数,在多个线程同时对同一个内存地址进行写入,由于CPU时间调度上的问题,写入数据会被多次的覆盖,所以就要使线程同步。

3、线程同步是什么意思?

同步就是协同步调,按预定的先后次序进行运行。如:你做完,我再做。

错误理解 “同”字从字面上容易理解为一起动作,其实不是。“同”字应是指协同、协助、互相配合。

正确理解 :所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回,同时其他线程也不能调用这个方法。

线程同步的作用 :

  • 线程有可能和其他线程共享一些资源,比如:内存、文件、数据库等
  • 当多个线程同时读写同一份共享资源的时候,可能会引起冲突。这时候,我们需要引入线程“同步”机制,即各位线程之间要有个先来后到,不能一窝蜂挤上去抢作一团。
  • 线程同步的真是意思其实是“排队”:几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。

2. 同步互斥访问(synchronized)

       基本上所有解决线程安全问题的方式都是采用“序列化临界资源访问”的方式,即在同一时刻只有一个线程操作临界资源,操作完了才能让其他线程进行操作,也称同步互斥访问。

在Java中一般采用synchronized和Lock来实现同步互斥访问。

3. Synchronized关键字

首先了解一下互斥锁 :就是能达到互斥访问目的的锁

  1. 如果对一个变量加上互斥锁,那么在同一时刻,该变量只能有一个线程能访问,即当一个线程访问临界资源时,其他线程只能等待。
  2. 在Java中,每一个对象都有一个锁标记(monitor),也被称为监视器,当多个线程访问对象时,只有获取了对象的锁才能访问。
  3. 在我们编写代码的时候,可以使用synchronized修饰对象的方法或者代码块,当某个线程访问这个对象synchronized方法或者代码块时,就获取到了这个对象的锁,这个时候其他对象是不能访问的,只能等待获取到锁的这个线程执行完该方法或者代码块之后,才能执行对象的方法。

4. synchronized同步代码块实现

  1. 第一种:synchronized代码块方式(灵活)
  2. 第二种:在实例方法上使用synchronized
  3. 第三种 : 在静态方法上使用synchronized
package com.thread;
/**
 * synchronized同步代码块实现
 * 
 * @author 云村小威
 *
 * @2023年7月21日 下午12:37:53
 */
public class Text07 {
  // 第一种
  public void myThread1(Thread thread) {
    // this代表当前对象
    synchronized (this) {
      for (int i = 0; i < 5; i++) {
        System.out.println(thread.getName() + ":" + i);
      }
    }
  }
  // 第二种
  public synchronized void myThread2(Thread thread) {
    // this代表当前对象
    for (int i = 0; i < 5; i++) {
      System.out.println(thread.getName() + ":" + i);
    }
  }
  // 第三种
  public static synchronized void myThread3(Thread thread) {
    // this代表当前对象
    for (int i = 0; i < 5; i++) {
      System.out.println(thread.getName() + ":" + i);
    }
  }
}

5. 什么是类锁和对象锁

5.1 概念

对象锁:在java中每个对象都有一个唯一的锁,对象锁用于对象实例方法或者一个对象实例上面的   —— 一个对象一把锁,100个对象100把锁。

类锁:是用于一个类静态方法或者class对象的,一个类的实例对象可以有多个,但是只有一个class对象 —— 100个对象,也只是1把锁。

注意上述第三种方式synchronized同步代码块实现在静态方法添加synchronized这把锁属于类了,所有这个类的对象都共享这把锁,这就叫类锁。

5.2 释放锁时机

       如果一个方法或者代码块被synchronized关键字修饰,当线程获取到该方法或代码块的锁,其他线程是不能继续访问该方法或代码块的。而其他线程想访问该方法或代码块,就必须要等待获取到锁的线程释放这个锁,而在这里释放锁只有两种情况 :

  1. 线程执行完代码块,自动释放锁;
  2. 程序报错,JVM让线程自动释放锁;

举例:这里我调用了上述的第一种和第二种实现同步方式

public static void main(String[] args) {
    new Text07().myThread1(new Thread());
    new Text07().myThread2(new Thread());
  }

6. 线程等待与线程唤醒

Object类 作用
Void wait() 让活动在当前对象的线程无限等待(释放之前占有的锁)
Void notify() 唤醒当前对象正在等待的线程(只提示唤醒,不会释放锁)
Void notifyAll() 唤醒在此对象监视器上等待的所有线程。

方法详解 :

       wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方法是object类自带的。wait和notify方法不是通过线程对象调用的;

六、线程安全问题(生产者和消费者模式)

1、什么是 “ 生产者和消费者模式 ” ?

  1. 生产线负责生产,消费线程负责消费。
  2. 生产线程和消费线程要达到均衡
  3. 这是一种特殊的业务需求,在这种特殊的情况下需要使用wait方法和notify方法。

2、模拟一个业务需求

  • 仓库我们采用List集合
  • List集合中假设只能存储1个元素
  • 1个元素就表示仓库满了
  • 如果List集合中元素个数是0,就表示仓库空了
  • 保证List集合永远都是最多存储1个元素
  • 必须做到这种效果:生产1个消费1个。

模拟生产者与消费者案例:

       首先我创建了一个产品类来定义我们需要生产的对象,接着创建一个工厂类写了生产和出售两个方法并实现同步锁,通过put方法将生产的鸡脚保存到容器里如果容器小于10就消费者等待(wait() 方法),如果大于就唤醒( notifyAll()方法 )所有等待的消费者。然后再定义生产者类它需要不停做事,它什么时候休息根据工厂类生产的鸡脚是否大于20,否则不可休息得一直做事。最后是消费者调用工厂类的sale方法只要保存鸡脚的容器大于10就开始购买对容器进行减少,如果没有了就让消费者进行等待。

package com.thread;
import java.util.Vector;
/**
 * 生产者与消费者案例
 * 
 * @author 云村小威
 *
 * @2023年7月22日 上午10:51:47
 */
public class Text08 {
  public static void main(String[] args) {
    // 店铺运营
    // 创建一个工厂
    Factory f = new Factory();
    // 招三个成产员工
    Producer p1 = new Producer(f);
    Producer p2 = new Producer(f);
    Producer p3 = new Producer(f);
    new Thread(p1, "工作人员A").start();
    new Thread(p2, "工作人员B").start();
    new Thread(p3, "工作人员C").start();
    // 引入三个消费者
    Sale s1 = new Sale(f);
    Sale s2 = new Sale(f);
    Sale s3 = new Sale(f);
    new Thread(s1, "消费者A").start();
    new Thread(s2, "消费者B").start();
    new Thread(s3, "消费者C").start();
  }
}
/**
 * 消费者
 */
class Sale implements Runnable {
  Factory f = null;
  public Sale(Factory f) {
    this.f = f;
  }
  public void run() {
    while (true) {
      // 让消费者延迟购买的时间
      try {
        Thread.sleep(1500);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      f.sale();
    }
  };
}
/**
 * 生产类
 */
class Producer implements Runnable {
  Factory f = null;
  public Producer(Factory f) {
    this.f = f;
  }
  @Override
  public void run() {
    // 需要不停的做事
    while (true) {
      try {
        Thread.sleep(1000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      // 调用生产数据的方法
      f.put(new Product(1, "鸡脚"));
    }
  }
}
/**
 * 工厂类
 */
class Factory {
  // 准备储存产品的集合容器,多线程优先使用Vector集合,该集合类中的方法大部分都是带有同步的概念机制。
  Vector<Product> vc = new Vector<Product>();
  // 专门定义一个生产数据方法
  public synchronized void put(Product product) {
    // 判断产品于目标数量是否相同
    if (vc.size() >= 20) {
      // 产品数量满足让工作人员处于等待状态
      try {
        this.wait();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    } else {
      // 让等待的工作人员生产产品
      this.notifyAll();
      System.out.println("正在生产中...当前货架的鸡脚数目是:" + vc.size());
      vc.add(product);
      System.out.println("货架鸡脚总数目是:" + vc.size());
    }
  }
  // 专门定义一个负责消费数据方法
  public synchronized void sale() {
    // 只要容器存在货物就可以进行购买
    if (vc.size() > 10) {
      this.notifyAll(); // 唤醒所有等待的消费者
      System.out.println("当前货架上的鸡脚数目是:" + vc.size() + ",正在出售鸡脚。");
      vc.remove(0);
      System.out.println("\t目前货架上的鸡脚数目是:" + vc.size());
    } else {
      // 没有鸡脚,让消费者处于等待状态
      try {
        this.wait();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
}
/**
 * 产品类
 */
class Product {
  private int pid;
  private String name;
  public Product() {
    // TODO Auto-generated constructor stub
  }
  public int getPid() {
    return pid;
  }
  public void setPid(int pid) {
    this.pid = pid;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public Product(int pid, String name) {
    super();
    this.pid = pid;
    this.name = name;
  }
}

七、多线程小案例

1. 老虎机案例

package com.thread;
import java.util.Random;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
/**
 * 老虎机游戏程序
 * @author 云村小威
 *
 * @2023年7月22日 上午11:27:56
 */
public class Game extends Application {
  Random rd = new Random(); // 随机数对象
  boolean flag = true; // 控制开关默认打开
  int timer = 50; // 控制事件变量
  // 通过数组保存没有规律的多张图片
  String[] images = { "lib\\7.png", "lib\\橙子.png", "lib\\柠檬.png", "lib\\苹果.png", "lib\\西瓜.png", "lib\\香蕉.png", };
  // 总布局
  Pane pane = new Pane();
  private Label a1 = new Label("", new ImageView(new Image("lib\\苹果.png", 150, 150, false, false)));
  private Label a2 = new Label("", new ImageView(new Image("lib\\西瓜.png", 150, 150, false, false)));
  private Label a3 = new Label("", new ImageView(new Image("lib\\柠檬.png", 150, 150, false, false)));
  private Button btn1 = new Button("开始游戏");
  private Button btn2 = new Button("点击停止");
  {
    a1.setStyle("-fx-border-color:#ccc;");
    a1.setLayoutX(60);
    a1.setLayoutY(40);
    pane.getChildren().add(a1);
    a2.setStyle("-fx-border-color:#ccc;");
    a2.setLayoutX(225);
    a2.setLayoutY(40);
    pane.getChildren().add(a2);
    a3.setStyle("-fx-border-color:#ccc;");
    a3.setLayoutX(390);
    a3.setLayoutY(40);
    pane.getChildren().add(a3);
    btn1.setLayoutX(180);
    btn1.setLayoutY(220);
    btn1.setPrefSize(100, 40);
    pane.getChildren().add(btn1);
    btn2.setLayoutX(340);
    btn2.setLayoutY(220);
    btn2.setPrefSize(100, 40);
    pane.getChildren().add(btn2);
  }
  @Override
  public void start(Stage stage) throws Exception {
    // 创建一个场景
    Scene scene = new Scene(pane, 600, 300);
    stage.setScene(scene);
    stage.show();
    /**
     * 开始游戏点击事件
     */
    btn1.setOnAction(e -> {
      flag = true;
      new Thread() {
        public void run() {
          while (flag) {
            try {
              Thread.sleep(timer);
            } catch (InterruptedException e) {
              // TODO Auto-generated catch block
              e.printStackTrace();
            }
            Platform.runLater(new Runnable() {
              @Override
              public void run() {
                int sj1 = rd.nextInt(6);
                a1.setGraphic(new ImageView(new Image(images[sj1])));
                int sj2 = rd.nextInt(6);
                a2.setGraphic(new ImageView(new Image(images[sj2])));
                int sj3 = rd.nextInt(6);
                a3.setGraphic(new ImageView(new Image(images[sj3])));
              }
            });
            timer += 6;
            if (timer >= 300) {
              timer = 50;
              break;
            }
          }
        }
      }.start();
    });
    /**
     * 停止点击事件
     */
    btn2.setOnAction(e -> {
      flag = false;
    });
  }
  public static void main(String[] args) {
    launch();
  }
}

运行效果:

2. FX版无限聊天

1. 服务器

package com.net;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
/**
 * 服务器
 * 
 * @author 云村小威
 *
 * @2023年7月19日 下午9:40:54
 */
public class ServerFx extends Application {
  /**
   * 总布局 BorderPane
   */
  BorderPane bor = new BorderPane();
  // 上
  HBox hb = new HBox();
  TextField t = new TextField();
  Button btn = new Button("启动服务");
  {
    hb.getChildren().addAll(t, btn);
    hb.setAlignment(Pos.CENTER);
    hb.setPadding(new Insets(20));
    hb.setSpacing(20);
    btn.setPrefSize(100, 40);
    bor.setTop(hb);
  }
  // 中
  TextArea ta = new TextArea();
  {
    ta.setEditable(false); // 文本域不可修改
    ta.setStyle("-fx-border-color: #ccc;-fx-border-width: 10 10 10 10;");
    bor.setCenter(ta);
  }
  // 下
  HBox hb2 = new HBox();
  TextField text = new TextField();
  Button btn1 = new Button("发送");
  {
    hb2.getChildren().addAll(text, btn1);
    hb2.setAlignment(Pos.CENTER);
    hb2.setPadding(new Insets(20));
    hb2.setSpacing(20);
    bor.setBottom(hb2);
  }
  /**
   * 将通信对象声明在start方法外面
   */
  Socket s = null;
  ServerSocket ss = null;
  /**
   * 定义所需的流对象 扩大权限方便关闭流
   */
  InputStream inputStream = null;
  BufferedReader br = null;
  OutputStream outputStream = null;
  BufferedWriter bw = null;
  @Override
  public void start(Stage stage) throws Exception {
    stage.setTitle("服务器");
    Scene scene = new Scene(bor, 800, 700);
    stage.setScene(scene);
    stage.show();
    /**
     * 启动服务点击事件
     */
    btn.setOnAction(e -> {
      // 创建服务器
      try {
        ss = new ServerSocket(Integer.parseInt(t.getText()));
      } catch (IOException e1) {
        e1.printStackTrace();
      }
      ta.appendText("服务已启动,等待客户端连接...\n");
      // 开启一个线程 自动接受客户端发送的信息
      new Thread() {
        public void run() {
          // 多线程控制接受客户端信息
          try {
            s = ss.accept();
            ta.appendText("客户端已连接...\n");
          } catch (IOException e) {
            e.printStackTrace();
          }
          while (true) {
            // 读取 获取输入流
            try {
              inputStream = s.getInputStream();
              br = new BufferedReader(new InputStreamReader(inputStream));
              String count = br.readLine();
              ta.appendText("\t客户端:" + count + "\n");
            } catch (IOException e) {
              e.printStackTrace();
            }
          }
        }
      }.start();
    });
    /**
     * 发送信息点击事件
     */
    btn1.setOnAction(e -> {
      try {
        outputStream = s.getOutputStream();
        bw = new BufferedWriter(new OutputStreamWriter(outputStream));
        // 获取文本框内容
        String count = text.getText();
        bw.write(count);
        bw.newLine();
        bw.flush();
        ta.appendText("服务器:" + count + "\n");
        text.setText("");
      } catch (IOException e1) {
        e1.printStackTrace();
      }
    });
  }
  public static void main(String[] args) {
    launch();
  }
}

2. 客户端

package com.net;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.text.Text;
import javafx.stage.Stage;
/**
 * 客户端
 * 
 * @author 云村小威
 *
 * @2023年7月19日 下午9:41:46
 */
public class ClientFx extends Application {
  /**
   * 总布局 BorderPane
   */
  BorderPane bor = new BorderPane();
  // 上
  HBox hb = new HBox();
  Text ip = new Text("IP");
  TextField t = new TextField("127.0.0.1");
  Text tcp = new Text("端口号");
  TextField t2 = new TextField();
  Button btn = new Button("连接");
  {
    t.setDisable(true);// 设置ip地址不可编辑
    hb.getChildren().addAll(ip, t, tcp, t2, btn);
    hb.setAlignment(Pos.CENTER);
    hb.setPadding(new Insets(20));
    hb.setSpacing(20);
    btn.setPrefSize(100, 40);
    bor.setTop(hb);
  }
  // 中
  TextArea ta = new TextArea();
  {
    ta.setEditable(false); // 文本域不可修改
    ta.setStyle("-fx-border-color: #ccc;-fx-border-width: 10 10 10 10;");
    bor.setCenter(ta);
  }
  // 下
  HBox hb2 = new HBox();
  TextField text = new TextField();
  Button btn1 = new Button("发送");
  {
    hb2.getChildren().addAll(text, btn1);
    hb2.setAlignment(Pos.CENTER);
    hb2.setPadding(new Insets(20));
    hb2.setSpacing(20);
    bor.setBottom(hb2);
  }
  /**
   * 定义所需的流对象 扩大权限方便关闭流
   */
  InputStream inputStream = null;
  BufferedReader br = null;
  OutputStream outputStream = null;
  BufferedWriter bw = null;
  // 定义客户端对象
  Socket s = new Socket();
  @Override
  public void start(Stage stage) throws Exception {
    stage.setTitle("客户端");
    stage.setResizable(false); // 设置窗口不可动
    Scene scene = new Scene(bor, 800, 700);
    stage.setScene(scene);
    stage.show();
    /**
     * 连接点击事件
     */
    btn.setOnAction(e -> {
      String ip = t.getText(); // 获取ip地址
      int port = Integer.parseInt(t2.getText());
      try {
        s = new Socket(ip, port);
        ta.appendText("客户端成功连接服务器...\n");
      } catch (IOException e1) {
        e1.printStackTrace();
      }
      // 开启一个线程 (让它自动接受数据)
      new Thread() {
        public void run() {
          while (true) {
            try {
              inputStream = s.getInputStream();
              br = new BufferedReader(new InputStreamReader(inputStream));
              String count = br.readLine();
              ta.appendText("\t服务器:" + count + "\n");
            } catch (IOException e) {
              e.printStackTrace();
            }
          }
        }
      }.start();
    });
    /**
     * 发送信息点击事件
     */
    btn1.setOnAction(e -> {
      try {
        outputStream = s.getOutputStream();
        bw = new BufferedWriter(new OutputStreamWriter(outputStream));
        // 获取文本框内容
        String count = text.getText();
        bw.write(count);
        bw.newLine();
        bw.flush();
        ta.appendText("客户端:" + count + "\n");
        text.setText("");
      } catch (IOException e1) {
        e1.printStackTrace();
      }
    });
  }
  public static void main(String[] args) {
    launch();
  }
}

运行效果:

相关文章
|
2月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
199 1
|
2月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
226 1
|
3月前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
Java 数据库 Spring
178 0
|
3月前
|
算法 Java
Java多线程编程:实现线程间数据共享机制
以上就是Java中几种主要处理多线程序列化资源以及协调各自独立运行但需相互配合以完成任务threads 的技术手段与策略。正确应用上述技术将大大增强你程序稳定性与效率同时也降低bug出现率因此深刻理解每项技术背后理论至关重要.
291 16
|
4月前
|
缓存 并行计算 安全
关于Java多线程详解
本文深入讲解Java多线程编程,涵盖基础概念、线程创建与管理、同步机制、并发工具类、线程池、线程安全集合、实战案例及常见问题解决方案,助你掌握高性能并发编程技巧,应对多线程开发中的挑战。
|
4月前
|
数据采集 存储 前端开发
Java爬虫性能优化:多线程抓取JSP动态数据实践
Java爬虫性能优化:多线程抓取JSP动态数据实践
|
5月前
|
Java API 调度
从阻塞到畅通:Java虚拟线程开启并发新纪元
从阻塞到畅通:Java虚拟线程开启并发新纪元
379 83
|
5月前
|
安全 算法 Java
Java 多线程:线程安全与同步控制的深度解析
本文介绍了 Java 多线程开发的关键技术,涵盖线程的创建与启动、线程安全问题及其解决方案,包括 synchronized 关键字、原子类和线程间通信机制。通过示例代码讲解了多线程编程中的常见问题与优化方法,帮助开发者提升程序性能与稳定性。
252 0
|
5月前
|
存储 Java 调度
Java虚拟线程:轻量级并发的革命性突破
Java虚拟线程:轻量级并发的革命性突破
357 83