image.png
image.png

继承Thread类

// 创建线程方式一:继承Thread类,重写run方法,调用start开启线程
// 总结:线程开启不一定立即执行,由CPU调度执行
image.png

实现Runnable

操作同一个资源的问题

发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱
image.png

Callable(了解)

image.png

模拟龟兔赛跑

赛道race实例只有一个,多个线程共享 胜利者: 静态变量,多个线程共同访问 步数step(计数器i)是栈内局部变量,每个线程独享

image.png

image.png

静态代理模式

  1. new Thread(new Runnable() {
  2. @Override
  3. public void run() {
  4. // 执行体
  5. }
  6. }).start();

image.png
Thread也实现了Runnable接口。Thread代理了Runnable,Thread.start()内部会调用Runnable接口的run()

Lamda表达式

函数式接口
image.png
接口——普通实现——静态成员内部类——局部内部类——匿名内部类——Lamda表达式简化
image.png
image.png
image.png
image.png

停止线程

  1. 建议线程正常停止(run方法退出)——> 利用次数,不建议死循环
  2. 建议使用标志位(就是while里面的变量true或false)
  3. 不要使用stop或destroy等过时或者JDK不建议使用的方法

Thread.sleep的几个问题

Sleep可以放大问题的发生性(如果不Sleep,在极短时间片段内其它线程不太容易插入进来)。

Thread.sleep
sleep就是正在执行的线程主动让出CPU,CPU去执行其他线程,在sleep指定的时间过后,CPU才会回到这个线程上继续往下执行,如果当前线程进入了同步锁,sleep方法并不会释放锁,即使当前线程使用sleep方法让出了CPU,但其他被同步锁挡住了的线程也无法得到执行。 几个问题

  • Thread.sleep(1000),1000ms后是否立即执行?

不一定,在未来的1000毫秒内,线程不想再参与到CPU竞争。那么1000毫秒过去之后,这时候也许另外一个线程正在使用CPU,那么这时候操作系统是不会重新分配CPU的,直到那个线程挂起或结束;况且,即使这个时候恰巧轮到操作系统进行CPU 分配,那么当前线程也不一定就是总优先级最高的那个,CPU还是可能被其他线程抢占去

  • Thread.sleep(0),是否有用?

Thread.Sleep(0)的作用,就是“触发操作系统立刻重新进行一次CPU竞争,重新计算优先级”。竞争的结果也许是当前线程仍然获得CPU控制权,也许会换成别的线程获得CPU控制权。这也是我们在大循环里面经常会写一句Thread.sleep(0) ,因为这样就给了其他线程比如Paint线程获得CPU控制权的权力,这样界面就不会假死在那里

  • java中sleep方法不会释放锁,但是又说sleep会把执行权让给其他线程,这不是前后矛盾吗?没释放锁其他线程就是拿到执行权不还是执行不了吗?

这个两个操作并不矛盾,sleep不释放锁代表其他线程不会获取到当前线程所持有的所有的锁,但是如果有线程不依赖sleep线程所持有的锁,则线程有机会获得cpu的持行权

礼让、插队、状态、优先级

多线程课程 - 图14

守护线程

  • 虚拟机不用等待用户线程执行完毕,只要用户线程执行完毕,则守护线程也会结束。

如后台记录操作日志、监控内存、垃圾回收等待
thread.setDaemon(_true_) true为用户线程,默认false守护线程

线程同步

线程同步和不安全案例

  • 买票
  • 取钱
  • 线程不安全的集合,如HashMap等

    synchronized

    image.png
    image.png
  1. 每个新new的Runnable实现对象都持有一把锁
  2. 核心就是:锁共同操作的对象
  3. 方法的synchronized锁的是所属的对象实例(同步方法的同步监视器就是this,或者说是class对象) ```java public class UnsafeBank {

    public static void main(String[] args) {

    1. Account account = new Account(100, "建设银行");
    2. Drawing youDrawing = new Drawing(account, 50, "you");
    3. Drawing wifeDrawing = new Drawing(account, 100, "wife");
    4. new Thread(youDrawing).start();
    5. new Thread(wifeDrawing).start();

    } }

// 因为不涉及操作多个对象,所以继承Thread class Drawing extends Thread {

  1. final Account account;
  2. @Override
  3. public void run() {
  4. // 其实核心就是:锁共同操作的对象,这里you和wife是两个不同的实例,共同操作的是account
  5. synchronized (account) {
  6. // ...
  7. }
  8. }

}

```

Lock锁

ReentrantLock可重入锁实现了Lock,和Synchronized差不多,是显式的。
image.png

线程协作

synchronized只解决线程不同,不能解决线程通信。

wait/notify生产者与消费者

管程法 WithContainer.java

信号灯法WithSignal.java

补充

volatile

缓存:计算机 上一层都是下一层的缓存。

  • 内存:主存(即共享内存)。
  • 本地内存(比如机器的寄存器)
  • CPU高速缓存。

image.png
常规 ——> 加了volatile的变量,指示 JVM,这个变量是共享且不稳定的,每次使用它都到主存中进行读取。

  • 保证变量的可见性。
  • 禁止指令进行重排序优化。

    可见性 :当一个线程对共享变量进行了修改,那么另外的线程都是立即可以看到修改后的最新值。

image.png image.png

synchronized 关键字和 volatile 关键字的区别

synchronized 关键字和 volatile 关键字是两个互补的存在,而不是对立的存在!

  • volatile 关键字是线程同步的轻量级实现,所以 volatile性能肯定比synchronized关键字要好
  • volatile 关键字只能用于变量而 synchronized 关键字可以修饰方法以及代码块
  • volatile 关键字能保证数据的可见性,但不能保证数据的原子性。synchronized 关键字两者都能保证。
  • volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized 关键字解决的是多个线程之间访问资源的同步性。

    ThreadLocal

    为什么SimpleDateFormat线程不安全及解决方案:Calendar对象有一些设置、清空操作,多线程使数据不一致。
    如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的本地副本。
    TestSimpleDateFormat.java
    ThreadLocalExample.java