多线程并发开篇-线程相关
1.线程和进程
CPU执行指令,进程就是由多个指令组成的一整个流程。而线程粒度更小,一个进程中可以包含多个线程。
2.线程创建的方式
实际上都是Tread类的run方法。
归根结底是两种继承Tread类和实现Runnable接口。线程池和Callable接口属于封装的更好的,因此不算在两种之中。另外,不管是继承Tread类还是实现Runnable接口,大家都特别喜欢将这个类给匿名掉,因为这样的写法更加简洁。
继承Tread类
最常规方式
public class MyThread extends Thread { @Override public void run(){ System.out.println("MyThread running"); } }
然后主方法中要调用线程时:
MyThread myThread = new MyThread(); myTread.start();
匿名内部类。
也可以向Thread()中传参数,传一个的话,默认是线程的名字。
Thread thread = new Thread(){ @Override public void run(){ System.out.println("Thread Running"); } }; thread.start();
实现Runnable
常规写法
public class MyRunnable implements Runnable { @Override public void run(){ System.out.println("MyRunnable running"); } }
然后主方法中要调用线程时:
Thread thread = new Thread(new MyRunnable()); thread.start();
匿名内部类写法
常规写法有类名,这里的类是匿名的。
public class Demo1 { public static void main(String[] args) { Runnable myRunnable = new Runnable() { @Override public void run() { System.out.println("Runnable running"); } }; Thread thread = new Thread(myRunnable); thread.start(); } }
写法2:
public class Demo2 { public static void main(String[] args) { // 实现Runnable接口 Thread t2 = new Thread(new Runnable(){ @Override public void run() { System.out.println("实现线程"); } }); t2.start(); } }
Callable接口
直接上一个匿名内部类写法。
import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class Demo3 { public static void main(String[] args) { // 实现Callable的接口 ,这种方法可从线程中返回值 FutureTask ft = new FutureTask(new Callable(){ @Override public Object call() throws Exception { System.out.println("打印线程"); return null; } }); Thread t3 = new Thread(ft); t3.start(); } }
值得注意的是,线程开启使用start,如果不开启线程,那么调用run,就是主线程调用的。因此开启了新线程后,新线程工作了,就去进行打印工作。执行run方法。run方法就是某个线程要执行的内容,所以不开启新的线程,就是主线程来执行
3.线程的各种状态
6种
- NEW :线程新建但未启动
- RUNNABLE :调用Start,开启线程
- BLOCKED :被阻塞,有其他线程拿到锁了
- WAITING :等待
- TIMED_WAITING :计时等待
- TERMINATED:终止
其中,最容易混淆的是等待和阻塞。
- 阻塞:Lock 或者synchronize 关键字产生的状态
- 等待或者计时等待:有sleep、wait、join方法调用进入该状态。
阻塞时被动的,我想拿锁,但是没拿到,就得一直等。等待是主动的,当我在运行中,发现缺资源或者我自己主动想等一等,就会执行进入等待。
4.线程状态之间的转换
可以看到,wait是Object的方法,而join是Thread线程类的方法。
5.并发中,几种常见的方法辨析
sleep方法
该方法是线程类Tread类的方法,让调用的线程进入指定时间睡眠状态,使得当前线程进入阻塞状态。当线程处于上锁时,sleep()方法不会释放对象锁,即睡眠时也持有对象锁。只会让出CPU执行时间片,并不会释放同步资源锁。sleep()休眠时间满后,该线程不一定会立即执行,这是因为其他线程可能正在运行而起没有被调度为放弃执行,除非此线程具有更高的优先级。也就是说,睡醒了想执行就得排队。
public void run() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } }
wait方法及notify和notifyAll
- wait方法是Object类里的方法,当一个线程执行wait()方法时,它就进入到一个和该对象相关的等待池中(进入等待队列,也就是阻塞的一种,叫等待阻塞),同时释放对象锁,并让出CPU资源,待指定时间结束后返还得到对象锁。
- wait使用notify方法、notiftAll方法或者等待指定时间来唤醒当前等待池中的线程。也就是必须搭配使用
- wait方法、notify方法和notiftAll方法用于协调多线程对共享数据的存取,所以只能在同步方法或者同步块中使用,否则抛出IllegalMonitorStateException。
sleep和wait
- sleep和wait两个方法都会抛出InterruptedException。
- wait() 是 Object 的方法,而 sleep() 是 Thread 的静态方法;
- wait() 会释放锁,sleep() 不会。
补充说明join方法(Thread类)
在线程中调用另一个线程的 join() 方***将当前线程挂起,而不是忙等待,直到目标线程结束。也就是说,在A线程运行的时,调用B的join方***让A一直等到B线程运行结束。
补充说明yield方法(Thread类)
Java线程中的Thread.yield( )方法,译为线程让步。顾名思义,就是说当一个线程使用了这个方法之后,它就会把自己CPU执行的时间让掉,让自己或者其它的线程运行,注意是让自己或者其他线程运行,并不是单纯的让给其他线程。
举个例子:一帮朋友在排队上公交车,轮到Yield的时候,他突然说:我不想先上去了,咱们大家来竞赛上公交车。然后所有人就一块冲向公交车,有可能是其他人先上车了,也有可能是Yield先上车了。
6.线程的中断
stop方法
stop()方法在现在JDK中不推荐使用,原因是stop()方法过于暴力,强行把执行到一半的线程终止,可能会引起一些数据不一致的问题。因此是一个被淘汰的方法
interrupt方法
Thread.interrupt() 中断线程。但实际上只是给线程设置一个中断标志,线程仍会继续运行。
Thread,isInterrupted()判断是否被中断
Thread.interrupted()判断是否被中断,并清除当前中断状态
适用范围及注意事项
- interrupt中断机制并不是真正的将当前线程直接中断,而是一个中断标记的变化。简言之,stop就是让你直接停而不让用的,因此interrupt的出现也就不是直接停,而是到一个适当的时候,停下来。所以,该方法是设置标志位后,不断监听,需要线程自己响应这个中断。当然线程也可以不响应,比如正处于阻塞,等锁的线程,就不会听你的话,中断当前线程的状态。
- 一般使用中断的方法是sleep、wait、join以及并发包中的一些方法。而不太能响应这个中断的主要是IO操作,以及被阻塞的线程。
7.Java内存模型(JMM)
内存模型三大特性
1.原子性
2.有序性
编译器为了优化性能而改变程序中语句的先后顺序,导致有序性问题。(重排序)
- 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
- 指令级并行的重排序。处理器将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
- 内存系统的重排序。处理器使用缓存和读/写缓冲区,使得加载和存储操作看上去可能是在乱序执行。
可以运行重排序,但是是不影响有序性。
3.可见性
可见性指当一个线程修改了共享变量的值,其它线程能够立即得知这个修改。Java 内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值来实现可见性的。
主要有三种实现可见性的方式:
- volatile
- synchronized,对一个变量执行 unlock 操作之前,必须把变量值同步回主内存。
- final,被 final 关键字修饰的字段在构造器中一旦初始化完成,并且没有发生 this 逃逸(其它线程通过 this 引用访问到初始化了一半的对象),那么其它线程就能看见 final 字段的值。
8. 解决可见性的先行发生原则
1、程序次序规则。在一个线程内,书写在前面的代码先行发生于后面的。
2、Volatile变量规则。对一个volatile修饰的变量,两个线程。一个读一个写,写的先操作。
3、线程启动规则。Thread对象的start()方法先行发生于此线程的每一个动作。
4、线程终止规则。线程的所有操作都先行发生于对此线程的终止检测。
5、线程中断规则。对线程interrupt()方法的调用先行发生于被中断线程的代码所检测到的中断事件。
6、对象终止规则。一个对象的初始化完成(构造函数之行结束)先行发生于发的finilize()方法的开始。
7、传递性。A先行发生B,B先行发生C,那么,A先行发生C。
8、管程锁定规则。一个unlock操作先行发生于后面对同一个锁的lock操作。也就是先解锁才能获得锁。