继承Thread类
// 创建线程方式一:继承Thread类,重写run方法,调用start开启线程
// 总结:线程开启不一定立即执行,由CPU调度执行
实现Runnable
操作同一个资源的问题
发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱
Callable(了解)
模拟龟兔赛跑
赛道race实例只有一个,多个线程共享 胜利者: 静态变量,多个线程共同访问 步数step(计数器i)是栈内局部变量,每个线程独享
静态代理模式
new Thread(new Runnable() {
@Override
public void run() {
// 执行体
}
}).start();
Thread也实现了Runnable接口。Thread代理了Runnable,Thread.start()内部会调用Runnable接口的run()
Lamda表达式
函数式接口
接口——普通实现——静态成员内部类——局部内部类——匿名内部类——Lamda表达式简化
停止线程
- 建议线程正常停止(run方法退出)——> 利用次数,不建议死循环
- 建议使用标志位(就是while里面的变量true或false)
- 不要使用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的持行权
礼让、插队、状态、优先级
守护线程
- 虚拟机不用等待用户线程执行完毕,只要用户线程执行完毕,则守护线程也会结束。
如后台记录操作日志、监控内存、垃圾回收等待thread.setDaemon(_true_)
true为用户线程,默认false守护线程
线程同步
线程同步和不安全案例
- 每个新new的Runnable实现对象都持有一把锁
- 核心就是:锁共同操作的对象
方法的synchronized锁的是所属的对象实例(同步方法的同步监视器就是this,或者说是class对象) ```java public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account(100, "建设银行");
Drawing youDrawing = new Drawing(account, 50, "you");
Drawing wifeDrawing = new Drawing(account, 100, "wife");
new Thread(youDrawing).start();
new Thread(wifeDrawing).start();
} }
// 因为不涉及操作多个对象,所以继承Thread class Drawing extends Thread {
final Account account;
@Override
public void run() {
// 其实核心就是:锁共同操作的对象,这里you和wife是两个不同的实例,共同操作的是account
synchronized (account) {
// ...
}
}
}
Lock锁
ReentrantLock可重入锁实现了Lock,和Synchronized差不多,是显式的。
线程协作
wait/notify生产者与消费者
管程法 WithContainer.java
信号灯法WithSignal.java
补充
volatile
缓存:计算机 上一层都是下一层的缓存。
- 内存:主存(即共享内存)。
- 本地内存(比如机器的寄存器)
- CPU高速缓存。
常规 ——> 加了volatile的变量,指示 JVM,这个变量是共享且不稳定的,每次使用它都到主存中进行读取。
- 保证变量的可见性。
- 禁止指令进行重排序优化。
可见性 :当一个线程对共享变量进行了修改,那么另外的线程都是立即可以看到修改后的最新值。
synchronized 关键字和 volatile 关键字的区别
synchronized 关键字和 volatile 关键字是两个互补的存在,而不是对立的存在!
- volatile 关键字是线程同步的轻量级实现,所以 volatile性能肯定比synchronized关键字要好 。
- volatile 关键字只能用于变量而 synchronized 关键字可以修饰方法以及代码块 。
- volatile 关键字能保证数据的可见性,但不能保证数据的原子性。synchronized 关键字两者都能保证。
- volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized 关键字解决的是多个线程之间访问资源的同步性。
ThreadLocal
为什么SimpleDateFormat线程不安全及解决方案:Calendar对象有一些设置、清空操作,多线程使数据不一致。
如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的本地副本。
TestSimpleDateFormat.java
ThreadLocalExample.java