マルチスレッドとスレッド同期を実現する Java の古典的な手法

WBOY
リリース: 2022-04-26 18:05:36
転載
2746 人が閲覧しました

この記事では、javaに関する関連知識を提供します。主にコア クラス ライブラリのマルチスレッドとスレッド同期に関連する問題を紹介します。一緒に見てみましょう。皆さんのお役に立てれば幸いです。役に立ちます。

マルチスレッドとスレッド同期を実現する Java の古典的な手法

## 推奨学習: 「

Java ビデオ チュートリアル

1 マルチスレッド

1.1 プロセス

  • プロセス: 実行中のプログラムです。
      割り当てと呼び出しを行うシステムです。リソース 独立したユニット
    • 各プロセスは独自のメモリ空間とシステム リソースを持ちます
  • プロセスの 3 つの特徴
      独立性: プロセスは互いに独立しており、独自の独立したメモリ領域を持っています
    • ダイナミクス: プロセスは、メモリ、CPU、ネットワークを動的に占有する実行中のプログラムです。リソースを待機しています
    • 同時実行: CPU はタイムシェアリング方式でポーリングと切り替えを行い、各プロセスを順番に処理します。切り替え速度が非常に速いため、まるで同時に実行しているように感じます。これが同時実行です (同時実行: 同じです)同時に複数の実行があります)

1.2 スレッド

  • Thread: それはプロセスです。 の単一の順次制御フローは実行パスです。
      シングルスレッド: プロセスには実行パスが 1 つだけあります。
    • マルチスレッド: プロセスには複数の実行パスがあります

1.3 マルチスレッドの実装方法

1.3.1 方法 1: Tread クラスを継承する

  • プロセス:

      1. Tread クラスを継承する MyTread クラスを定義します
    • 2. MyTread クラス内で書き換えます
    • run()Method
    • 3. MyTread クラスのオブジェクトを作成します
    • 4. スレッドを開始します:
    • void start()
  • #なぜ run() メソッドをオーバーライドする必要があるのでしょうか?
  • run() は、スレッドによって実行されるコードをカプセル化するために使用されるため、
  • run() メソッドとstart()メソッド?
  • run(): スレッドによって実行されるコードをカプセル化し、それを直接呼び出します。これは、通常のメソッドの呼び出しと同等です。
    • start(): スレッドを開始し、次に、このスレッドで JVM を呼び出します。 run() メソッド
  • Example
  • MyTread クラス:
  • package test;//1、定义一类MyTread继承Tread类public class MyThread extends Thread{ 2、在MyTread类中重写run()方法 @Override public void run() { for(int i=0;i
    ログイン後にコピー
テスト クラス
  • package test;public class Demo { public static void main(String[] args) { //3、创建MyTread类的对象 MyThread my1 = new MyThread(); MyThread my2 = new MyThread(); //4、启动线程:void start():启动线程,由Java虚拟机调用此线程的run()方法 my1.start(); my2.start(); }}
    ログイン後にコピー
1.3.2 方法 2: 実行可能インターフェイスの実装

    プロセス
  • :1. MyRunnable クラスを定義して Runnable インターフェイスを実装します
    • 2. MyRunnable クラスの
    • run()
    • メソッドをオーバーライドします3 . MyRunnable クラスのオブジェクトを作成します
    • 4. Tread クラスのオブジェクトを作成し、MyRunnable オブジェクトをコンストラクター メソッドのパラメーターとして使用します
    • 5. スレッドを開始します
    利点:
  • ##回避 Java 単一継承の制限を克服します
    • #複数の同一プログラムのコードが同じリソースを処理する状況に適しています。スレッドおよびプログラムのコードとデータを効果的に分離し、オブジェクト指向設計理論をより適切に反映します
    package test;public class Demo { public static void main(String[] args) { //3、创建MyRunnable类的对象 MyRunnable mr = new MyRunnable(); //4、创建Tread类的对象,把MyRunnable对象作为构造方法的参数// Thread t1 = new Thread(mr);// Thread t2 = new Thread(mr); //Thread(Runnable target,String name) Thread t1 = new Thread(mr,"高铁"); Thread t2 = new Thread(mr,"飞机"); //5、启动线程 t1.start(); t2.start(); }}
    ログイン後にコピー
1.3.3 方法 3: Callable インターフェイスを実装する

1.4 スレッド名の設定と取得

Thread クラスでスレッド名の設定と取得を行うメソッド
メソッド名 説明 void setName(Stringname) パラメータ名と同じになるようにこのスレッドの名前を変更します String getName() このスレッドの名前を返します public Thread(String name) スレッド名はコンストラクター メソッドを通じて設定することもできます public static Thread currentThread() 現在実行中のスレッド オブジェクトへの参照を返します (main() メソッドでスレッドを返すことができます) public static void sleep(long time) How実行を続行する前に現在のスレッドをスリープ状態にするまでのミリ秒数
#
  • MyThread类
package test;public class MyThread extends Thread{ //构造方法添加线程名称 public MyThread(){} public MyThread(String name) { super(name); } @Override public void run() { for(int i=0;i
ログイン後にコピー
  • 测试类
package test;public class Demo { public static void main(String[] args) { /* MyThread my1 = new MyThread(); MyThread my2 = new MyThread(); //2,void setName(Stringname) 将此线程的名称更改为等于参数name my1.setName("高铁"); my2.setName("飞机");*/ //3,通过构造方法设置线程名称 //需要自己定义的类中提供此带参构造方法,并通过super访问父类带参构造方法 /*MyThread my1 = new MyThread("高铁"); MyThread my2 = new MyThread("飞机"); my1.start(); my2.start();*/ //4,public static Thread currentThread() 返回对当前正在执行的线程对象的引用(可以返回main()方法中线程) System.out.println(Tread.currentThread().getName()); //main }}
ログイン後にコピー

1.5 线程调度

  • 线程有两种调度模型

    • 分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
    • 抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些
  • Java使用的是抢占式调度模型

  • 假如计算机只有一个CPU, 那么CPU在某一个时刻只能执行条指令, 线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的

  • Thread类中设置和获取线程优先级的方法

方法名 说明
public final int getPriority() [praɪˈɔːrəti] 返回此线程的优先级
public final void setPriority(int newPriority) 更改此线程的优先级
  • 线程默认优先级是5;线程优先级范围是:1-10
  • 线程优先级高仅仅表示线程获取的CPU时间的几率高,但是要在次数比较多,或者多次运行的时候才能看到你想要的效果
package test;public class Demo { public static void main(String[] args) { ThreadPriority tp1 = new ThreadPriority(); ThreadPriority tp2 = new ThreadPriority(); ThreadPriority tp3 = new ThreadPriority(); tp1.setName("高铁"); tp2.setName("飞机"); tp3.setName("汽车"); //1,public final int getPriority() [praɪˈɔːrəti] 返回此线程的优先级// System.out.println(tp1.getPriority()); //5// System.out.println(tp2.getPriority()); //5// System.out.println(tp3.getPriority()); //5 //2,public final void setPriority(int newPriority) 更改此线程的优先级 System.out.println(Thread.MAX_PRIORITY); //10 System.out.println(Thread.MIN_PRIORITY); //1 System.out.println(Thread.NORM_PRIORITY); //5 //设置正确优先级 tp1.setPriority(5); tp2.setPriority(10); tp3.setPriority(1); tp1.start(); tp2.start(); tp3.start(); }}
ログイン後にコピー

1.6 线程控制

方法名 说明
static void sleep(long millis) 使当前正在执行的线程停留(暂停执行)指定的毫秒数
void join() 等待这个线程死亡
void setDaemon(boolean on) [ˈdiːmən] 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机很快将退出 (并不是立刻退出)

案例:sleep()方法

  • 线程类
package test;public class ThreadSleep extends Thread{ @Override public void run() { for(int i=0;i
ログイン後にコピー
  • 测试类
package test;public class Demo { public static void main(String[] args) { ThreadSleep ts1 = new ThreadSleep(); ThreadSleep ts2 = new ThreadSleep(); ThreadSleep ts3 = new ThreadSleep(); ts1.setName("曹操"); ts2.setName("刘备"); ts3.setName("孙权"); ts1.start(); ts2.start(); ts3.start();// 曹操:0// 孙权:0// 刘备:0// 孙权:1// 曹操:1// 刘备:1// ... }}
ログイン後にコピー

案例:join()方法

package test;public class Demo { public static void main(String[] args) { ThreadJoin tj1 = new ThreadJoin(); ThreadJoin tj2 = new ThreadJoin(); ThreadJoin tj3 = new ThreadJoin(); tj1.setName("康熙"); tj2.setName("四阿哥"); tj3.setName("八阿哥"); tj1.start(); //2,void join() 等待这个线程死亡 try { tj1.join(); } catch (InterruptedException e) { e.printStackTrace(); } tj2.start(); tj3.start();// 康熙:0// 康熙:1// 康熙:2// 四阿哥:0// 四阿哥:1// 八阿哥:0// 八阿哥:1// 八阿哥:2// 四阿哥:2// ... }}
ログイン後にコピー

案例:setDaemon()方法

package test;public class Demo { public static void main(String[] args) { ThreadJoin tj1 = new ThreadJoin(); ThreadJoin tj2 = new ThreadJoin(); ThreadJoin tj3 = new ThreadJoin(); tj2.setName("关羽"); tj3.setName("张飞"); //设置主线程为刘备 Thread.currentThread().setName("刘备"); //3,void setDaemon(boolean on) 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出 tj1.setDaemon(true); tj2.setDaemon(true); tj1.start(); tj2.start(); for(int i=0;i

1.7 线程生命周期

マルチスレッドとスレッド同期を実現する Java の古典的な手法

1.8 数据安全问题之案例:买票

マルチスレッドとスレッド同期を実現する Java の古典的な手法
マルチスレッドとスレッド同期を実現する Java の古典的な手法

ログイン後にコピー
  • 为什么出现问题?(这也是我们判断多线程程序是否会有数据安全问题的标准)

    • 是否是多线程环境
    • 是否有共享数据
    • 是否有多条语句操作共享数据
  • 如何解决多线程安全问题呢?

  • 基本思想:让程序没有安全问题的环境

  • 怎么实现呢?

    • 把多条语句操作共享 数据的代码给锁起来,让任意时刻只能有一一个线程执行即可
    • Java提供 了同步代码块的方式来解决

1.9 线程同步_同步代码块

  • 锁多条语句操作共享数据,可以使用同步代码块实现
  • 格式
synchronized(任意对象) { 多条语句操作共享数据的代码}
ログイン後にコピー
  • 好处:让多个线程实现先后依次访问共享资源,解决了多线程的数据安全问题

  • 弊端:当线程很多的时候,因为每个线程都会去判断同步上的锁,这是很消耗资源的,无形中降低程序的运行效率

  • sellTicket类

package test;//1,定义一个类SellTicket实现Runnable接口,里面定义一个成员变量: private int tickets= 100;public class SellTicket implements Runnable{ private int tickets = 100; private Object obj = new Object(); //2,在ellTicket类中重写run0方法实现卖票, 代码步骤如下 @Override public void run() { while(true) { //tickes=100 //t1,t2,t3 //假设t1抢到CPU执行器 synchronized (obj){ //t1进来后把代码锁起来了 if (tickets > 0) { try { Thread.sleep(100); //t1休息100毫秒 } catch (InterruptedException e) { e.printStackTrace(); } //窗口1正在出售第100张票 System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票"); tickets--; //tickets=99 } //t1出来了,锁就被释放了 } } }}
ログイン後にコピー
  • 测试类
package test;public class SellTicketDemo { public static void main(String[] args) { //创建SellTicket类的对象 SellTicket st = new SellTicket(); //创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称 Thread t1 = new Thread(st,"窗口1"); Thread t2 = new Thread(st,"窗口2"); Thread t3 = new Thread(st,"窗口3"); //启动线程 t1.start(); t2.start(); t3.start(); }}
ログイン後にコピー

1.10 线程同步_同步方法

  • 作用:把出现线程安全问题的核心方法给锁起来,每次只能一个线程进入访问,其他线程必须在方法外面等待
  • 同步方法:就是把synchronized关键字加到方法上;锁对象为:this
    • 格式:修饰符 synchronized 返回值类型 方法名(方法参数) {}
  • 同步静态方法:就是把synchronized关键字加到静态方法上面;锁对象为:类名.class
    • 格式:修饰符 static synchronized 返回值类型 方法名(方法参数) {}
package test;public class SellTicket implements Runnable{//1非静态 private int tickets = 100; private static int tickets = 100; private Object obj = new Object(); private int x = 0; @Override public void run() { while(true) { if(x%2==0) {//1非静态 synchronized (this) { synchronized (SellTicket.class) { if (tickets > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票"); tickets--; //tickets=99 } } } else {// synchronized (obj) {// if (tickets > 0) {// try {// Thread.sleep(100);// } catch (InterruptedException e) {// e.printStackTrace();// }// System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");// tickets--; //tickets=99// }// } sellTicket(); } x++; } }//1非静态// private synchronized void sellTicket() {// if (tickets > 0) {// try {// Thread.sleep(100);// } catch (InterruptedException e) {// e.printStackTrace();// }// System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");// tickets--; //tickets=99// }// } private static synchronized void sellTicket() { if (tickets > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票"); tickets--; //tickets=99 } }}
ログイン後にコピー

1.11 线程安全的类(了解)

源码中方法都被synchronized修饰

StringBuffer

  • 线程安全, 可变的字符序列
  • 从版本JDK 5开始,被StringBuilder替代。通常应该使用StringBuilder类, 因为它支持所有相同的操作,但它更快,因为它不执行同步

Vector

  • 从Java 2平台v1.2开始,该类改进了List接口, 使其成为Java Collections Framework的成员。 与新的集合实现不同,Vector被同步。 如果不需要线程安全的实现,建议使用ArrayList代替Vector

Hashtable

  • 该类实现了一个哈希表,它将键映射到值。任何非null对象都可以用作键或者值
  • 从Java 2平台v1.2开始,该类进行了改进,实现了Map接口,使其成为Java Collections Framework的成员。与新的集合实现不同,Hashtable被同步。 如果不需要线程安全的实现,建议使用HashMap代替Hashtable

Collections类中static List snchronizedList(List list):返回由指定列表支持的同步(线程安全)的列表

package test;import java.util.ArrayList;import java.util.Collection;import java.util.Collections;public class Demo { public static void main(String[] args) { //static  List snchronizedList(List list):返回由指定列表支持的同步(线程安全)的列表 Collection list = Collections.synchronizedList(new ArrayList()); /*源码都是返回Synchronized public static  List synchronizedList(List list) { return (list instanceof RandomAccess ? new Collections.SynchronizedRandomAccessList(list) : new Collections.SynchronizedList(list)); }*/ }}
ログイン後にコピー

1.12 Lock锁

  • Lock是接口不能直接实例化,采用实现类ReentrantLock来实例化(JDK5以后)
  • ReentrantLock构造方法:
方法名 说明
ReentrantLock() 创建一个ReentrantLock的实例对象
  • Lock中获得锁和释放锁方法:
方法名 说明
void lock() 获得锁
void unlock() 释放锁
  • 推荐使用try{} finall{}代码块来加锁和释放锁
package test;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class SellTicket implements Runnable{ private static int tickets = 100; private Lock lock = new ReentrantLock(); @Override public void run() { while(true) { try { lock.lock(); if (tickets > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票"); tickets--; } }finally { lock.unlock(); } } }}
ログイン後にコピー

1.13 线程通讯

  • 线程通信一定是多个线程在操作同一个资源才需要通信
方法名 说明
public void wait() 让当前线程进入到等待状态,此方法必须锁对象调用
public void notify() 唤醒当前锁对象上等待状态的某个线程,此方法必须锁对象调用
public void notifyAll() 唤醒当前锁对象上等待状态的全部线程,此方法必须锁对象调用

1.14 生产者消费者

1.14.1 生产者消费者概述

マルチスレッドとスレッド同期を実現する Java の古典的な手法

  • 为了体现生产和消费过程中的等待和唤醒,Java就提供了几个方法供我们使用,这几个方法在Object类中
  • Object类的等待和唤醒方法
方法名 说明
void wait() 导致当前线程等待,直到另一个线程调用该对象的 notify() 方法或 notifyAll() 方法
void notify() 唤醒正在等待对象监视器的单个线程
void notifyAll() 唤醒正在等待对象监视器的所有线程

1.14.2 生产者消费者案例

マルチスレッドとスレッド同期を実現する Java の古典的な手法

  • 奶箱类
package test;//1:定义奶箱类public class Box { //定义一个成员变量,表示第x瓶奶 private int milk; //定义一个成员变量表示奶箱的状态 private boolean state = false; //提供存储牛奶和获取牛奶的操作 public synchronized void put(int milk) { //如果有牛奶等待消费 if(state) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //如果没有牛奶,就生产牛奶 this.milk = milk; System.out.println("送奶工将第" + this.milk + "瓶奶放入奶箱"); //生产完毕后,修改奶箱状态 state = true; //唤醒其他等待线程 notifyAll(); } public synchronized void get() { //如果没有牛奶,就等到生产 if(!state) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //如果有牛奶,就消费牛奶 System.out.println("用户拿到第" + this.milk + "瓶奶"); //消费完毕后,修改奶箱状态 state = false; //唤醒其他等待线程 notifyAll(); }}
ログイン後にコピー
  • 生产者类
package test;//2:生产者类(Producer):实现Runnable接口public class Producer implements Runnable { private Box b; public Producer(Box b) { this.b = b; } //重写run()方法,调用存储牛奶的操作 @Override public void run() { for (int i = 1; i
ログイン後にコピー
  • 消费者类
package test;//3:消费者类(Customer);实现Runnable接口public class Customer implements Runnable{ private Box b; public Customer(Box b) { this.b = b; } //重写run()方法,调用获取牛奶的操作 @Override public void run() { while(true) { b.get(); } }}
ログイン後にコピー
  • 测试类
package test;public class BoxDemo { public static void main(String[] args) { //创建奶箱对象,这是共享数据区域 Box b = new Box(); //创建生产者对象,把奶箱对象作为构造方法参数传递。因为在这个类中要谓用存储牛奶的操作 Producer p = new Producer(b); //创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作 Customer c =new Customer(b); //创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递 Thread t1 = new Thread(p); Thread t2 = new Thread(c); //启动线程 t1.start(); t2.start();// 送奶工将第1瓶奶放入奶箱// 用户拿到第1瓶奶// 送奶工将第2瓶奶放入奶箱// 用户拿到第2瓶奶// 送奶工将第3瓶奶放入奶箱// 用户拿到第3瓶奶// 送奶工将第4瓶奶放入奶箱// 用户拿到第4瓶奶// 送奶工将第5瓶奶放入奶箱// 用户拿到第5瓶奶 }}
ログイン後にコピー

推荐学习:《java视频教程

以上がマルチスレッドとスレッド同期を実現する Java の古典的な手法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:csdn.net
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!