多线程初阶(二)- 线程安全问题

目录

1.观察count++

 原因总结

 2.解决方案-synchronized关键字

(1)synchronized的特性

(2)如何正确使用

语法格式

3.死锁

(1)造成死锁的情况

(2)死锁的四个必要条件

4.Java标准库中的线程安全类

5.volatile关键字

(1)内存可见性问题

原因 

解决方案 

(2)不解决原子性问题

6.wait和notify 

(1)wait()

(2)notify()

(3)线程饿死问题 

7.wait和sleep的对比(面试题)


1.观察count++

我们观察以下代码:

public class Demo20 {private static int count = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() ->{for (int i = 0; i < 50000; i++) {count++;}});Thread t2 = new Thread(() ->{for (int i = 0; i < 50000; i++) {count++;}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);}
}

他的逻辑是将count在不同的线程下进行五万次++操作,理想的结果是100000,但由于是并发执行,结果并不能达到预期,每次的结果都不相同,因为多个线程并发执行,引起的bug 
这样的bug称为“"线程安全问题"或者叫做"线程不安全"

如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是 线程安全的。

我们从cpu的视角来观察count++操作,它是由3个指令的:

  1.  把内存中的数据读取到cpu寄存器里   load
  2. 把cpu寄存器里的数据+1    add
  3. 把寄存器的值写回内存       save

由于CPU是随即调度,抢占式先行所以在调度线程的时候不知道什么时候会切换线程
指令是cpu执行的最基本单位,要调度,至少把当前执行完,不会执行一半调度走,所以当针对一条指令的时候就不会出现安全性问题;但是由于count++是三个指令,可能会出现cpu 执行了其中的1个指令或者2个指令或者3个指令调度走的情况,这样就会出现线程安全问题产生bug。


无bug的情况:

有bug的情况(出现了覆盖的状况):

 原因总结

  1. 线程在操作系统中是随即调度,抢占式执行的(根本原因)
  2. 多个线程同时修改同一个变量
  3. 修改操作不是“原子”的
  4. 内存可见性问题
  5. 指令重排序问题


原子性:原子是不可分割的最小单位,cpu视角不可分割的最小单位就是一条指令,cpu在进行调度切换线程的时候势必会确保执行完一条指令才能调度走再执行下一条命令,所以像count++, +=,-=之类的操作都不具备原子性  赋值操作a=b是具备原子性的

 2.解决方案-synchronized关键字

针对原因一我们无法干预,操作系统内核,负责的工作,咱们作为应用层的程序员,无法干预

针对原因二取决于实际的需求.有的场景能这么改,有的场景不能这么改取决于实际的需求
在Java中这个方案不算很普适的方案.

针对原因三我们重点进行探讨,该操作不是原子的那怎么可以变成原子的呢
 

进行加锁操作,想象一个上厕所的场景,你对门进行了加锁,这样别人就不能进来,只有当你上完厕所出来才算解锁

注意:此处的加锁操作并非是将count++操作变成原子的,也没有干预到线程的调度,只是通过这种加锁的方式来保证一个线程在执行count++操作的过程中其他线程的count++不能插队进来

(1)synchronized的特性

  1. 互斥:synchronized会起到互斥效果,某个线程执行到某个对象的synchronized中时,其他线程如果也执行 到同一个对象synchronized就会阻塞等待.

    进入synchronized修饰的代码块,相当于加锁
    退出synchronized修饰的代码块,相当于解锁
    synchronized用的锁是存在Java对象头里的。

  2. 可重入:针对一个线程一把锁.这个线程针对这把锁,连续加锁两次这种情况理论上应该死锁,但由于该特性不会造成死锁

    在可重入锁的内部,包含了"线程持有者"和"计数器"两个信息.
        如果某个线程加锁的时候,发现锁已经被人占用,但是恰好占用的正是自己,那么仍然可以继续获取到锁,并让计数器自增.
        解锁的时候计数器递减为0的时候,才真正释放锁.(才能被别的线程获取到)
     

(2)如何正确使用

synchronized() {}


synchronized不是函数而是关键字,括号内也不是参数,而是用来指定一个锁对象(可以指定任何对象),通过锁对象来进行后续的判定

{}里面的代码,就是要打包到一起的代码~~
{}还可以放任意的其他代码,包括调用别的方法等合法的java代码
进入代码块就会进行加锁,出代码块就会进行解锁

public class Demo21 {private static int count = 0;private static Object locker = new Object();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() ->{for (int i = 0; i < 50000; i++) {synchronized (locker){count++;}}});Thread t2 = new Thread(() ->{for (int i = 0; i < 50000; i++) {synchronized (locker){count++;}}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);}

代码解释:t1,t2针对同一个对象locker进行加锁,t1先进行加锁,执行代码块中的代码,此时t2进行等待,t1执行完毕后,t2进行加锁再执行该线程下的代码
(这两者的++操作,不会穿插执行了,也就不会相互覆盖掉对方的结果了)
本质上是把随机的并发执行过程,强制变成了串行,从而解决了刚才的线程安全问题
上述操作能够正确执行的原因是,两个线程都加锁了,并且针对的是同一个对象加锁了

以下两种情况就不能正确执行

  1. 只有一个线程加锁
    public class Demo21 {private static int count = 0;private static Object locker1 = new Object();private static Object locker2 = new Object();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() ->{for (int i = 0; i < 50000; i++) {synchronized (locker1){count++;}}});Thread t2 = new Thread(() ->{for (int i = 0; i < 50000; i++) {synchronized (locker2){count++;}}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);}

  2. 多线程针对不同的对象加锁
     
    private static int count = 0;private static Object locker1 = new Object();private static Object locker2 = new Object();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() ->{for (int i = 0; i < 50000; i++) {synchronized (locker1){count++;}}});Thread t2 = new Thread(() ->{for (int i = 0; i < 50000; i++) {synchronized (locker2){count++;}}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);

以上情况为两个线程针对同一个对象加锁,当第一个线程解锁之后就会执行第二个线程进行加锁;如果是三个线程针对同一个对象加锁,当某个线程先加上锁,另外两个线程开始阻塞等待,此时这两个线程谁先拿到锁是无法预期的,但不存在线程安全问题

多个线程针对同一个对象加锁(大于2)

public class Demo21 {private static int count = 0;private static Object locker1 = new Object();private static Object locker2 = new Object();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() ->{for (int i = 0; i < 50000; i++) {synchronized (locker1){count++;}}});Thread t2 = new Thread(() ->{for (int i = 0; i < 50000; i++) {synchronized (locker2){count++;}}});Thread t3 = new Thread(() ->{for (int i = 0; i < 50000; i++) {synchronized (locker2){count++;}}});t1.start();t2.start();t3.start();t1.join();t2.join();t3.join();System.out.println(count);}

锁对象的作用:用来区分多个线程是否针对“同一个对象”加锁,
是同一个就会发生“阻塞”(锁竞争/锁冲突)
不是同一个对象就不会发生阻塞,两个线程仍然是随即调度的并发执行

注意事项: 
synchronized关键字本质上比join的串行执行,效率还是要高的
join的串行化是针对线程与线程之间,而synchronized关键字是针对线程中的一小部分逻辑进行加锁来实现串行化

语法格式

修饰类对象
在编写Java代码,本身是.java文件,通过javac编译成.class文件,jvm运行的时候把.class文件加载到内存中进而形成对应的类对象

一个 java进程中一个类的类对象只有唯一一个

private static int count = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() ->{for (int i = 0; i < 50000; i++) {synchronized (Demo21.class){count++;}}});Thread t2 = new Thread(() ->{for (int i = 0; i < 50000; i++) {synchronized (Demo21.class){count++;}}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);}


修饰普通方法

class Counter {public int count = 0;public synchronized void add(){count++;}
}class Counter {public int count = 0;public void add(){synchronized (this) {count++;}}
}public class Demo23 {public static void main(String[] args) throws InterruptedException {Counter counter = new Counter1();Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter1.add();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter1.add();}});t1.start();t2.start();t1.join();t2.join();System.out.println(Counter.count);}
}

修饰静态方法

class Counter {public static int count = 0;public synchronized static void add(){count++;}}public class Demo22 {public static int count = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() ->{for (int i = 0; i < 50000; i++) {synchronized (Counter.class){count++;}}});Thread t2 = new Thread(() ->{for (int i = 0; i < 50000; i++) {synchronized (Counter.class){count++;}}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);}
}

3.死锁

(1)造成死锁的情况

  1. 一个线程一把锁.这个线程针对这把锁,连续加锁两次

     这个情况在代码实例中,并没有真的出现死锁,synchronized针对这个情况做了特殊处理synchronized是“可重入锁”
    针对上述一个线程连续加锁两次的情况做了特殊处理,只有第一次加锁生效,之后的加锁不会生效直接放行

    class Counter1{public static int count = 0;public void add(){synchronized (this) {synchronized (this) {count++;}}}
    }

    那可重入锁是如何判断是否用加锁的情况呢?

  2. 两个线程两把锁
    t1获取锁A,t2获取锁B
    t1获取锁B,t2获取锁A
    public class Demo24 {private static Object locker1 = new Object();private static Object locker2 = new Object();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() ->{synchronized (locker1){System.out.println("t1加锁成功locker1");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (locker2){System.out.println("t1加锁成功locker2");}}});Thread t2 = new Thread(() ->{synchronized (locker2){System.out.println("t2加锁成功locker2");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (locker1){System.out.println("t2加锁成功locker1");}}});t1.start();t2.start();}
    }

  3. N个线程M把锁
    经典哲学家问题:有5个哲学家坐在一起吃饭,但只有5根筷子,哲学家就只做两件事情,一件事情为思考,另一件事情就是吃饭,当其中一个哲学家要吃饭时,就会拿起左右两边的筷子,那么此时如果左右相邻的哲学家也想吃饭时,就需要等待正在吃饭的哲学家吃完饭,放下筷子,才能继续吃,在通常情况下,整个系统可以很好的运转,但是当5个哲学家同时拿起左边的筷子时,就会出现死锁问题

(2)死锁的四个必要条件

  1. 锁是互斥的(锁的基本特性)
  2. 锁是不可被抢占的,线程1拿到锁A后,如果线程1不主动释放A,线程2就不能把锁A抢过来(锁的基本特性)

以上两点对于synchronized这样的锁,互斥和不可抢占都是基本特性,我们无法进行干预
 

  1. 请求和保持。线程1拿到锁A后,不释放A的前提下去拿锁B(代码结构)
        避免出现锁的嵌套即可解决
  2. 循环等待/环路等待/循环依赖 多个线程获取锁的过程存在循环等待(代码结构)
        给锁加编号,约定加锁顺序
     

如果在获取多把锁的时候,不要构成循环等待,就可以了~一~
假设代码按照请求和保持的方式,获取到N个锁,如何避免出现循环等待呢??一个简单有效的办法:给锁编号,1,2,3....N
约定所有的线程在加锁的时候,都必须按照一定的顺序来加锁.(比如,必须先针对编号小的锁,加锁,后针对编号大的锁加锁)

public class Demo24 {private static Object locker1 = new Object();private static Object locker2 = new Object();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() ->{synchronized (locker1){System.out.println("t1加锁成功locker1");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (locker2){System.out.println("t1加锁成功locker2");}}});Thread t2 = new Thread(() ->{synchronized (locker1){System.out.println("t2加锁成功locker1");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (locker2){System.out.println("t2加锁成功locker2");}}});t1.start();t2.start();}
}


4.Java标准库中的线程安全类

Java标准库中很多都是线程不安全的.这些类可能会涉及到多线程修改共享数据,又没有任何加锁措施.

ArrayList;LinkedList;HashMap;TreeMap;HashSet;TreeSet;StringBuilder

但是还有一些是线程安全的.使用了一些锁机制来控制.
Vector (不推荐使用)
HashTable(不推荐使用)
ConcurrentHashMap


StringBuffer

5.volatile关键字

(1)内存可见性问题

观察以下代码

public class Demo25 {private static int n = 0;public static void main(String[] args) throws InterruptedException {Thread t1= new Thread(() ->{while (n == 0){//}});Thread t2= new Thread(() ->{Scanner scanner = new Scanner(System.in);System.out.println("请输入一个整数:");n = scanner.nextInt();});t1.start();Thread.sleep(2000);t2.start();}
}


 

原因 


如何进行优化导致出现内存可见性问题的?



此时JVM执行这个代码时发现每次循环过程中(1)操作的开销非常大,而且每次执行(1)操作它的结果都是一样的,并且JVM根本没意识到用户可能未来会修改n,于是JVM就做了一个大胆的操作直接将(1)操作给优化掉了,每次循环不会去读取内存中的数据,而是直接读取寄存器/cache中的数据(缓存的结果)

当JVM做出上述决定后此时意味着,循环的开销大幅度的降低,但是当用户修改n的时候返现内存中的n已经改变了,但是t1线程每次循环不会真的读内存,并没有感知到n的改变,也就是说对于线程t1来说n的改变是“不可见的”,这样就引起了内存可见性的问题


内存可见性问题本质上是编译器/JVM对代码进行优化出现的bug,如果代码是单线程,优化后的代码非常准确,但在多线程中可能会出现误判,这就导致了内存可见性的问题
 

解决方案 

解决方案一:

在t1线程中添加sleep等待
和读内存相比,sleep相比之下就更慢了,足以等到你scanner输入之后t2线程修改后t1感知
 

public class Demo25 {private static int n = 0;public static void main(String[] args) throws InterruptedException {Thread t1= new Thread(() ->{while (n == 0){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("t1线程结束");});Thread t2= new Thread(() ->{Scanner scanner = new Scanner(System.in);System.out.println("请输入一个整数:");n = scanner.nextInt();});t1.start();t2.start();}
}

解决方案二:
添加volatile关键字,该关键字用来修饰一个变量,用来提示编译器这个变量是“易变”的,优化的前提是变量是频繁读取的,而且结果是固定的,此时编译器就会禁止上述优化,以此来确保灭磁都执行从内存中从新读取数据

引入该变量后,编译器生成该代码时,就会给这个变量的读取操作附近生成一些特殊指令,称为“内存屏障”,后续JVM执行到此处时就不会进行优化

public class Demo25 {private static volatile int n = 0;public static void main(String[] args) throws InterruptedException {Thread t1= new Thread(() ->{while (n == 0){//}System.out.println("t1线程结束");});Thread t2= new Thread(() ->{Scanner scanner = new Scanner(System.in);System.out.println("请输入一个整数:");n = scanner.nextInt();});t1.start();t2.start();}
}

(2)不解决原子性问题

public class Demo26 {private static volatile int count = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {count++;}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {count++;}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);}
}

 

6.wait和notify 

线程在操作系统上的调度是随机的,多个线程需要控制线程直接某个逻辑的先后顺序,此时就可以让后执行的逻辑使用wait,先执行的线程,完成某些逻辑之后,通过notify唤醒对方的wait

(1)wait()

作用:

使当前执行代码的线程进行等待(将线程放到等待队列中)

释放当前锁

满足一定条件时被唤醒,重新尝试获取这个锁

结束等待的条件: 
其他线程调用该对象的notify方法.

wait等待时间超时(wait方法提供⼀个带有timeout参数的版本,来指定等待时间).

其他线程调用该等待线程的interrupted方法,导致wait抛出InterruptedException 异常.

public class Demo27 {public static void main(String[] args) throws InterruptedException {Object object = new Object();System.out.println("wait之前");object.wait();System.out.println("wait之后");}
}

非法的监视器状态异常,这里的意思是在调用wait方法时,当前锁的状态是不正确的;很明显此处我们都没加锁又何谈解锁呢?wait方法会针对对象先进行解锁所以要使用synchronized关键字来上锁

加上锁之后由于没有notify解锁,所以会一直等待

wait在执行时会将进行解锁,阻塞等待(目的是为了收到通知)同时执行,这两个操作方法内部已经做好了

(2)notify()

notify方法是唤醒等待的线程.

  • notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁
  • 如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait状态的线程。(并没有"先来后到")
  • 在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁。
     
public class Demo27 {private static Object locker1 = new Object();public static void main(String[] args) {Thread t1 = new Thread(() ->{System.out.println("wait之前");synchronized (locker1) {try {locker1.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("wait之后");});Thread t2 = new Thread(() ->{System.out.println("notify之前");synchronized (locker1) {locker1.notify();}System.out.println("notify之后");});t1.start();t2.start();}

(3)线程饿死问题 

定义︰线程饿死是指一个或多个线程由于某种原因无法获取所需的资源或执行机会,导致它们无法继续正常执行,从而被阻塞在某个状态,不能完成其任务。这种情况通常是由于资源竞争或优先级设置不当导致的。

举例说明,第一个人(t1线程)去取钱并上了锁,但机器里没钱,第一个人可以先出来,可以反反复复进出,这就导致其他人只能干等着,无法获取到锁,此时就会产生线程饿死的情况
 

解决方案:

让第一个人拿到锁的同时进行判定,判定当前能否执行取钱的操作,能则正常执行,不能则主动释放锁,并且进行“阻塞等待”(调用wait实现),此时线程就就不会在后续参与锁的竞争,一直阻塞到取钱的条件具备,此时再由其它线程通知唤醒(notify实现)唤醒这个线程

7.wait和sleep的对比(面试题)

一个是用于线程之间的通信的,一个是让线程阻塞一段时间,

唯—的相同点就是都可以让线程放弃执行一段时间.


1. wait需要搭配synchronized使用. sleep 不需要.
2. wait是Object的方法sleep是Thread的静态方法.

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

若依二次开发

口味改造 原&#xff1a; 改造&#xff1a; 1./** 定义口味名称和口味列表的静态数据 */ 2.改变页面样式 3.定义储存当前选中的口味列表数组&#xff0c;定义改变口味名称时更新当前的口味列表 4.改变页面样式 6.格式转换 7.定义口味列表获取焦点时更新当前选中的口味列表

【DGL系列】简单理解graph.update_all和spmm的区别

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 目录 背景介绍 源码分析 小结一下 背景介绍 我们在看GNN相关的论文时候&#xff0c;都会说到邻接矩阵与特征矩阵之间是用到了spmm&#xff0c;在很久…

深入理解Linux网络(二):UDP接收内核探究

深入理解Linux网络&#xff08;二&#xff09;&#xff1a;UDP接收内核探究 一、UDP 协议处理二、recvfrom 系统调⽤实现 一、UDP 协议处理 udp 协议的处理函数是 udp_rcv。 //file: net/ipv4/udp.c int udp_rcv(struct sk_buff *skb) {return __udp4_lib_rcv(skb, &udp_…

【web】-反序列化-to_string

<?php highlight_file(__FILE__); class A{public $s;public function __destruct(){echo "hello".$this->s;}} class B{public $cmd;public function __toString(){system($this->cmd);return 1;} } unserialize($_GET[code]); __toString()当对象被当着…

《梦醒蝶飞:释放Excel函数与公式的力量》17.1使用命名范围和工作表函数

第17章&#xff1a;使用命名范围和工作表函数 17.1 命名范围的优势 在Excel中&#xff0c;使用命名范围是一个强大且灵活的功能&#xff0c;它可以极大地提高工作效率和公式的可读性。命名范围不仅使公式更容易理解&#xff0c;还减少了错误的可能性。以下将详细介绍命名范围的…

自然语言大模型介绍

1 简介 最近一直被大语言模型刷屏。本文是周末技术分享会的提纲&#xff0c;总结了一些自然语言模型相关的重要技术&#xff0c;以及各个主流公司的研究方向和进展&#xff0c;和大家共同学习。 2 Transformer 目前的大模型基本都是Transformer及其变种。本部分将介绍Transf…

24暑假算法刷题 | Day18 | LeetCode 530. 二叉搜索树的最小绝对差,501. 二叉搜索树中的众数,236. 二叉树的最近公共祖先

目录 530. 二叉搜索树的最小绝对差题目描述题解 501. 二叉搜索树中的众数题目描述题解 236. 二叉树的最近公共祖先题目描述题解 530. 二叉搜索树的最小绝对差 点此跳转题目链接 题目描述 给你一个二叉搜索树的根节点 root &#xff0c;返回 树中任意两不同节点值之间的最小差…

Python 更换 pip 源详细指南

目录 前言pip 国内源临时换源方法一&#xff1a;添加参数方法二&#xff1a;设置环境变量 永久换源方法三&#xff1a;修改配置方法四&#xff1a;pip 命令修改 总结 前言 在我们使用 Python 3 时&#xff0c;pip 是一个不可或缺的工具&#xff0c;它用于安装和管理第三方库。…

在虚拟机 CentOS7 环境下安装 MySQL5.7 数据库

配置目标 在虚拟机的 Linux CentOS7 环境下安装 MySQL5.7 版数据库&#xff0c;并能从宿主机 Windows 系统连接该数据库&#xff08;默认端口&#xff1a;3306&#xff09;。 1. 准备工作 WMware 虚拟机&#xff1a;VMware Workstation 16 ProCentOS7 镜像&#xff1a;CentO…

基于密钥的身份验证(Linux-Linux)

A主机&#xff1a; 1、生成密钥对 [rootservera ~]# ssh-keygen查看公钥 注&#xff1a;id_rsa为私钥&#xff08;证书&#xff09;&#xff0c;id_rsa.pub为公钥 2、注册公钥到服务器 [rootservera ~]# ssh-copy-id root172.25.250.106 查看.ssh 3、使用密钥连接服务器 #…

【yolov8】3、yolov8 环境安装 【GPU版】

pycharm下载安装 yolov8 环境安装 【GPU版】 1、要求1.1 什么是 CUDA 和 CUDNN1.2 查看cuda版本的3种方法&#xff08;版本在10.2以上的可以忽略本章节&#xff09;&#xff1a;1.3 没有找到NIVDIA图标&#xff0c;确认是否有英伟达显卡 2、pycharm下载安装进入官网 3、yolov8…

【Android】视图与常用控件总结

文章目录 一、视图基础1.1 设置视图的宽高1.2 设置视图的间距1.3 设置视图的对齐方式1.4 总结 二、控件2.1 TextView2.1.1 设置宽高2.1.2 设置内容2.1.3 设置大小2.1.4 设置颜色 2.2 Button2.3 EditText2.4 ImageView2.5 ProgressBar2.6 AlertDialog2.7 ProgressDialog 本文主要…

JAVASE进阶day14(网络编程续TCP,日志)

TCP 三次握手 四次挥手 package com.lu.day14.tcp;import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket;public class Client {public static void main(String[] args) {try(Socket socket new Socket("192.…

哈默纳科HarmonicDrive减速机组装注意事项

在机械行业中&#xff0c;精密传动设备HarmonicDrive减速机对于维持机械运作的稳定性和高效性起着至关重要的作用。然而在减速机的组装过程中&#xff0c;任何一个细微的错误都可能导致其运转时出现振动、异响等不良现象&#xff0c;严重时甚至可能影响整机的性能。因此&#x…

【开源库】libodb库编译及使用

前言 本文介绍windows平台下libodb库的编译及使用。 文末提供libodb-2.4.0编译好的msvc2019_64版本&#xff0c;可直接跳转自取 ODB库学习相关 【开源库学习】libodb库学习&#xff08;一&#xff09; 【开源库学习】libodb库学习&#xff08;二&#xff09; 【开源库学习】…

30种图像动画特效算法(C#多线程版) - 好文要转

最近想做一个屏幕保护软件&#xff0c;需要图片切换效果&#xff0c;于是就找到这个博文&#xff0c;强烈推荐&#xff1a; https://blog.51cto.com/mengliao/473169 其中的源码包在此下载&#xff1a;https://download.csdn.net/download/lzhdim/89532212 效果如下&#xff1a…

Pycharm 安装与使用

PyCharm的安装与使用 一、什么是PyCharm PyCharm是由JetBrains开发的专业Python集成开发环境&#xff08;IDE&#xff09;&#xff0c;提供智能代码补全、语法高亮和代码导航等编辑功能。它具有强大的调试工具和内置版本控制系统支持&#xff0c;方便代码管理和协作。PyCharm…

《小程序02:云开发之增删改查》

一、前置操作 // 一定要用这个符号包含里面的${}才会生效 wx.showToast({title: 获取数据成功&#xff1a;${colorLista}, })1.1&#xff1a;初始化介绍 **1、获取数据库引用&#xff1a;**在开始使用数据库 API 进行增删改查操作之前&#xff0c;需要先获取数据库的引用 cons…

<数据集>安全帽佩戴识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;3912张 图片分辨率&#xff1a;640640 标注数量(xml文件个数)&#xff1a;3912 标注数量(txt文件个数)&#xff1a;3912 标注类别数&#xff1a;2 标注类别名称&#xff1a;[no-helmet, helmet] 序号类别名称图片…

机器学习——降维算法PCA和SVD(sklearn)

目录 一、基础认识 1. 介绍 2. 认识 “ 维度 ” &#xff08;1&#xff09;数组和Series &#xff08;2&#xff09;DataFrame 表 &#xff08;3&#xff09;图像 3. 降维思想 4. 降维步骤 二、降维算法&#xff08;PCA&#xff09; 1. PCA实现 &#xff08;1&#…