并发编程之线程基础
并发编程之线程基础
线程基础
线程概念
线程/进程区别
进程:加载上下文+执行程序+保存上下文;性能上的消耗;是资源分配的最小单位
线程:cpu执行+线程栈;属于执行任务的最小单位
线程组
ThreadGroup的提出是为了方便线程的管理,通过它可以批量设定一组线程的属性,比如setDaemon,设置未处理异常的处理方法,设置统一的安全策略等等;也可以通过线程组方便的获得线程的一些信息,尽量不要使用,会带来线程安全问题。
启动线程
启动线程的方式只有一种
new Thread().start();
创建线程的方式有三种
第一种方式:通过继承Thread类
Thread thread1 = new Thread01();thread1.start();
第二种方式:通过实现Runnable类
Thread thread2 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("Runnable-线程-"+Thread.currentThread().getName());}});thread2.start();
第三种方式:通过FutureTask类
FutureTask futureTask = new FutureTask(new Callable() {@Overridepublic Object call() throws Exception {System.out.println("Callable-线程-"+Thread.currentThread().getName());return null;}});Thread thread3 = new Thread(futureTask);thread3.start();
具体实例如下:
public class ThreadDemo1 {public static void main(String[] args) {test();}/*** 线程创建的三种方式*/private static void test() {//第一种方式:通过继承Thread类Thread thread1 = new Thread01();thread1.start();//第二种方式:通过实现Runnable类Thread thread2 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("Runnable-线程-"+Thread.currentThread().getName());}});thread2.start();//第三种方式:通过FutureTask类FutureTask futureTask = new FutureTask(new Callable() {@Overridepublic Object call() throws Exception {System.out.println("Callable-线程-"+Thread.currentThread().getName());return null;}});Thread thread3 = new Thread(futureTask);thread3.start();}
}class Thread01 extends Thread {@Overridepublic void run() {System.out.println("Thread01-线程-"+Thread.currentThread().getName());}
}
终止线程
stop
不建议使用thread.stop()方法,原因是:可能导致线程安全问题,破环一致性。
实例:
public static void test1() throws InterruptedException {Thread thread1 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (this){try {i ++;Thread.sleep(3000);j++;} catch (InterruptedException e) {e.printStackTrace();}}}});thread1.start();Thread.sleep(1000);thread1.stop();System.out.println("i="+i+" j="+j);//打印结果:i=1 j=0
}
interrupt
interrupt方法并不会中断线程,只是给线程打上标记
如果目标线程在调用 wait()、wait(long)方法、join()、join(long, int)、join(long, int)、sleep(long, int)或sleep(long, int)等方法后,处于WAITING、Timed Waiting状态时,该线程被调用interrupt方法后,线程的WAITING、Timed Waiting状态将被清除,并抛出InterruptedException异常。
park()\parkNanos方法执行后,线程也处于 WAITING、Timed Waiting,也会被唤醒,但是不会抛异常,且有很诡异的情况发生
如果目标线程是被I/O 或者NIO中的Channel所阻塞,同样,I/O操作会被中断或者返回特殊异常值。达到终止线程的目的。
如果以上条件都不满足,则会设置此线程的中断状态。
Thread.interrupted()//先返回当前线程的中断标志的值(true/false),然后将线程标志清除,更改为false
thread.currentThread().interrupt()//将线程设置中断标志,值为true
Thread.currentThread().isInterrupted()//判断线程标志
线程分类
线程分为守护线程、用户线程两种
守护线程:是指程序运行时,后台提供一种通信服务的线程,进程结束时,会杀死所有的守护线程。
用户线程:非守护线程的就是用户线程
注意:当没有用户线程运行时,进程结束
(1) thread.setDaemon(true)必须在thread.start()之前设置,否则会抛出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
(2)在Daemon线程中产生的新线程也是Daemon的。
(3)守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。
线程状态
线程的6种状态:New、Runnable、Blocked、Waiting、Timed Waiting、Terminated
1、New:尚未启动的线程的状态;
2、Runnable:准备就绪,可运行的线程状态,等待CPU调度;
3、Blocked:线程阻塞等待监视器释放锁的状态;
4、Waiting:线程等待的状态;
不带时间参数的方式:Object.wait、Thread.join、LockSupport.park
5、Timed Waiting:具有指定等待时间的线程等待状态;
带时间参数的方式:Object.wait(time)、Thread.sleep(time)、Thread.join(time)、 LockSupport.parkNanos(time)、LockSupport.parkUntil(time)
6、Terminated:终止线程的线程状态;
线程状态的关系图:
线程通讯机制
通信方式4种
通信方式有4种:
- 文件共享
- 网络共享
- 共享变量
- jdk提供的线程协调API
jdk提供的线程协调API
jdk提供的线程协作的API,如:
- suspend/resume
- wait/notify
- park/unpark
suspend/resume
作用:调用suspend挂起目标线程,通过resume可以恢复线程执行
被弃用的主要原因是,容易写出死锁的代码
实例代码:
- 死锁实例1
- 死锁实例2
wait/notify
wait方法导致当前线程等待,加入该对象的等待集合中,并且放弃当前持有的对象锁。
notify/notifyAll方法唤醒一个或所有正在等待这个对象锁的线程。
注意1**:**虽然会wait自动解锁,但是对顺序有要求, 如果在notify被调用之后,才开始wait方法的调用,线程会永远处于WAITING状态。
注意2**:**这些方法只能由同一对象锁的持有者线程调用,也就是写在同步块里面,否则会抛出IllegalMonitorStateException异常。
示例
死锁示例
park/unpark
线程调用park则等待“许可” ,unpark方法为指定线程提供“许可(permit)”
>调用unpark之后,再调用park,线程会直接运行
> 提前调用的unpark不叠加,连续多次调用unpark后,第一次调用park后会拿到“许可”直接
运行,后续调用会进入等待。
示例代码
死锁代码
总结
协作方式 | 机制 | 方式1(锁synchronized) | 方式2(先唤醒,再挂起) | 备注 |
---|---|---|---|---|
suspend/resume | 当某个线程的suspend()方法被调用时,当前线程被挂起,如果当前线程持有锁,它不会释放锁。即线程挂起时还持有锁 | 死锁 | 死锁 | 弃用 |
wait/notify | 当某个线程中执行了wait方法时,线程被放入该对象的等待集合中,并且释放当前持有的对象锁 | 不死锁 | 死锁 | 只能在sychronized语句块中使用,且调用wait、notify的对象与锁对象相同,否则会抛出IllegalMonitorStateException异常 |
park/unpark | 线程调用park则等待“许可” ,unpark方法为指定线程提供“许可(permit)”。 | 死锁 | 不死锁 |
伪唤醒
伪唤醒是指线程并非因为notify、notifyall、unpark等api调用而意外唤醒,是更底层原因导致的。
警告!!!
之前代码中用 if 语句来判断,是否进入等待状态,这样的做法是错误的!
官方建议应该在循环中检查等待条件,原因是处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。
伪唤醒实例
- 问题:两个线程对一个初始值为零的变量操作,实现一个线程加一,另一个线程减一,来十次。
- 问题:六个线程对一个初始值为零的变量操作,实现两个线程加一,另外两个线程减一,来十次。
public class Test {public static void main(String[] args) {final Number number = new Number();Thread thread1 = new Thread(new Runnable() {public void run() {for(int i = 1; i <= 10; ++i) {try {number.add();} catch (InterruptedException var3) {var3.printStackTrace();}}}}, "线程A");Thread thread2 = new Thread(new Runnable() {public void run() {for(int i = 1; i <= 10; ++i) {try {number.reduce();} catch (InterruptedException var3) {var3.printStackTrace();}}}}, "线程B");Thread thread3 = new Thread(new Runnable() {public void run() {for(int i = 1; i <= 10; ++i) {try {number.add();} catch (InterruptedException var3) {var3.printStackTrace();}}}}, "线程C");Thread thread4 = new Thread(new Runnable() {public void run() {for(int i = 1; i <= 10; ++i) {try {number.reduce();} catch (InterruptedException var3) {var3.printStackTrace();}}}}, "线程D");Thread thread5 = new Thread(new Runnable() {public void run() {for(int i = 1; i <= 10; ++i) {try {number.add();} catch (InterruptedException var3) {var3.printStackTrace();}}}}, "线程E");Thread thread6 = new Thread(new Runnable() {public void run() {for(int i = 1; i <= 10; ++i) {try {number.reduce();} catch (InterruptedException var3) {var3.printStackTrace();}}}}, "线程F");thread1.start();thread2.start();thread3.start();thread4.start();thread5.start();thread6.start();}
}class Number {// private volatile int number = 0;private int number = 0;public synchronized void add() throws InterruptedException {if (number != 0){System.out.println(Thread.currentThread().getName()+"---等待中");this.wait();}number++;System.out.println(Thread.currentThread().getName()+"---:"+number);//通知this.notifyAll();;}public synchronized void reduce() throws InterruptedException {if (number == 0){System.out.println(Thread.currentThread().getName()+"---等待中");this.wait();}number--;System.out.println(Thread.currentThread().getName()+"---:"+number);//通知this.notifyAll();;}}
//打印结果
线程B---等待中
线程E---:1
线程E---等待中
线程B---:0
线程B---等待中
线程F---等待中
线程C---:1
线程C---等待中
线程D---:0
线程D---等待中
线程A---:1
线程A---等待中
线程D---:0
线程D---等待中
线程C---:1
线程C---等待中
线程F---:0
线程F---等待中
线程B---:-1
线程B---:-2
线程B---:-3
线程B---:-4
线程B---:-5
分析
期望结果:一个线程加1,得到1,然后等待,另一个线程减一,得到0
实际结果:又出现小于0,和大于1的情况,如上实例
我们以小于0的情况分析
假设:
B抢到了锁,进入reduce,这时 number ==0 ,B 等待,释放了锁;
然后D抢到了锁,进入reduce,这时 number == 0,D 等待,释放了锁;
然后A抢到了锁,进入add,设置number == 0,所以A向下走,number++,并唤醒了B、D
这时候坏事了,B和D都是在if内等待的,if判断过了后是不会再判断的,这时B、D都会向下执行,number- -
执行了两次,这时number == -1。
B、D其中有一个就是被虚假唤醒,这时解决虚假唤醒的方法就是将if改为while,因为while会在线程被唤醒后再次判断条件是否满足
解决伪唤醒
这时解决虚假唤醒的方法就是将 if 改为 while,因为while会在线程被唤醒后再次判断条件是否满足
实例
public class Test {public static void main(String[] args) {final Number number = new Number();Thread thread1 = new Thread(new Runnable() {public void run() {for(int i = 1; i <= 10; ++i) {try {number.add();} catch (InterruptedException var3) {var3.printStackTrace();}}}}, "线程A");Thread thread2 = new Thread(new Runnable() {public void run() {for(int i = 1; i <= 10; ++i) {try {number.reduce();} catch (InterruptedException var3) {var3.printStackTrace();}}}}, "线程B");Thread thread3 = new Thread(new Runnable() {public void run() {for(int i = 1; i <= 10; ++i) {try {number.add();} catch (InterruptedException var3) {var3.printStackTrace();}}}}, "线程C");Thread thread4 = new Thread(new Runnable() {public void run() {for(int i = 1; i <= 10; ++i) {try {number.reduce();} catch (InterruptedException var3) {var3.printStackTrace();}}}}, "线程D");Thread thread5 = new Thread(new Runnable() {public void run() {for(int i = 1; i <= 10; ++i) {try {number.add();} catch (InterruptedException var3) {var3.printStackTrace();}}}}, "线程E");Thread thread6 = new Thread(new Runnable() {public void run() {for(int i = 1; i <= 10; ++i) {try {number.reduce();} catch (InterruptedException var3) {var3.printStackTrace();}}}}, "线程F");thread1.start();thread2.start();thread3.start();thread4.start();thread5.start();thread6.start();}
}class Number {// private volatile int number = 0;private int number = 0;public synchronized void add() throws InterruptedException {while (number != 0){System.out.println(Thread.currentThread().getName()+"---等待中");this.wait();}number++;System.out.println(Thread.currentThread().getName()+"---:"+number);//通知this.notifyAll();;}public synchronized void reduce() throws InterruptedException {while (number == 0){System.out.println(Thread.currentThread().getName()+"---等待中");this.wait();}number--;System.out.println(Thread.currentThread().getName()+"---:"+number);//通知this.notifyAll();;}}
ame()+“—等待中”);
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+“—:”+number);
//通知
this.notifyAll();;
}
public synchronized void reduce() throws InterruptedException {while (number == 0){System.out.println(Thread.currentThread().getName()+"---等待中");this.wait();}number--;System.out.println(Thread.currentThread().getName()+"---:"+number);//通知this.notifyAll();;
}
}