java之多线程二
1.线程常用方法
1.1线程等待join
有时,我们需要等待⼀个线程完成它的⼯作后,才能进⾏⾃⼰的下⼀步⼯作。例如,李四只有等 张三下班了他才上班,这时我们需要⼀个⽅法明确等待线程的结束。
1.join:等待某个线程执行完之后,再执行后续的代码。
**
* 关于join示例
*/
public class ThreadByJoin {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
//1.张三开始上班
System.out.println("1.张三开始上班");
//2.张三正在上班
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//3.张三下班
System.out.println("3.张三下班");
});
t1.start();
t1.join();
Thread t2=new Thread(()->{
//1.李四开始上班
System.out.println("1.李四开始上班");
//2.李四正在上班
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//3.李四下班
System.out.println("3.李四下班");
});
t2.start();
}
}
join()相比于isAlive()方法的优势:
(1)写法更优雅
(2)运行时所用的资源更少。
join方法如果没有设置参数会无限等待下去。
2.带参数的join(n)方法,等待线程结束,最多等待n毫秒。
1.2线程终止
李四一旦进入到工作状态,它就会按照行动指南上的步骤去工作,不完成是不会结束的,有时我们需要增加一些机制,例如老板突然来电话说对方是骗子,需要赶紧停止转账,涉及到线程的终止。
自定义标识符
/**
* 使用自定义标识符终止线程
*/
public class ThreadInterrupt{
//1.声明一个自定义标识符
private volatile static boolean flag=false;
public static void main(String[] args) throws InterruptedException {
Thread thread=new Thread(()->{
while(!flag){
System.out.println("正在转账...");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("啊?差点误了大事!");
});
thread.start();
Thread.sleep(3000);
//终止线程
System.out.println("有内鬼,终止交易");
flag=true;
}
}
使用interrput()终止线程
1.interrupt()需要配合Thread.interrupted()或者Thread.currentThread().isInterrupted()一块使用,从而实现线程的终止。
Thread内部包含了一个boolean类型的变量作为线程是否被中断的标记。
2.使用thread对象的interrupted()方法通知线程结束。
/**
* 使用interrupt终止线程
*/
public class ThreadInterrupt2 {
public static void main(String[] args) throws InterruptedException {
Thread thread=new Thread(()->{
while(!Thread.interrupted()){
//while(!Thread.currentThread().isInterrupted()){
System.out.println("正在转账...");
}
System.out.println("啊?险些误了大事!");
});
thread.start();
Thread.sleep(500);
//终止线程
thread.interrupt();
System.out.println("有内鬼终止交易!");
}
}
3.使用Thread.currentThread().isInterrupted()方法。
4.isInterrupted和interrupted的区别:
(1)interrupted属于静态方法,所有程序都可以直接调用的全局方法;而isInterrupted属于某个实例的方法。
(2)interrupted在使用完之后会重置中断标识符,而isinterrputed不会重置中断标识符。
5.线程在接收到中断指令之后,立即中断了线程,相比于上一种自定义中断标识符的方法来说,它能更及时的响应中断线程指令。
使用stop终止线程
1.stop方法虽然可以终止线程,但是它已经是不建议使用的废弃方法,观察源码可以发现:
从上面的图片可以看出,stop方法是被@Deprecated修饰的不建议使用的过期方法,并且在注释的第一句话就说明了stop方法非安全的方法。在最新版本java中,此方法已经被直接移除了,所以强烈不建议使用。
总结
1.自定义中断标识符的停止方法,,此方法的缺点是不能及时响应中断请求;
2.使用interrupt中断线程方法,此方法是发送一个中断信号给线程,它可以及时响应中断,也是最推荐使用的方法。
3.stop方法,它虽然可以终止线程,但是方法已经是过期的,不建议使用。
1.3yield让出执行权
/**
* yield方法演示(让出CPU的执行权)
*/
public class ThreadYield {
public static void main(String[] args) {
Thread t1=new Thread(()->{
//得打当前线程
Thread cThread=Thread.currentThread();
for (int i = 0; i <100 ; i++) {
//让出CPU执行权
Thread.yield();
System.out.println("执行了线程:"+cThread.getName());
}
},"张三");
t1.start();
//创建并动线程
new Thread(()->{
Thread cThread=Thread.currentThread();
for (int i = 0; i < 100; i++) {
System.out.println("执行线程:"+cThread.getName());
}
},"李四").start();
}
}
yield方法会出让CPU的执行权,让线程调度器重新调度线程,但还是有一定的几率再一次调用到让出CPU的线程上的,这一次他就会执行线程的方法了,因为yield已经被执行过
1.4获得当前的线程
public class ThreadDemo {
public static void main(String[] args) {
//得到当前线程
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
}
}
2.在主线程中创建两个子线程,每个子线程中产生一个随机数,最终等待子线程执行完之后,在主线程累计两个子线程的结果。
/**
* 在主线程中创建两个子线程,每个子线程中产生一个随机数,最终等待子线程执行完之后
* 在主线程累计两个子线程的结果
* 实现思路:1.使用普通线程,把线程的随机数的值存起来
* 实现思路:2.使用有返回值的线程调用
*/
public class ThreadDemo14 {
static int num1=0;
static int num2=0;
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
num1=new Random().nextInt(10);
});
t1.start();
Thread t2=new Thread(()->{
num2=new Random().nextInt(10);
});
t2.start();
t1.join();
t2.join();
System.out.println("最终的结果:"+(num1+num2));
}
}
1.5休眠当前线程
1.5.1使用sleep休眠
1.传毫秒
public class ThreadSleep {
public static void main(String[] args) throws InterruptedException {
Thread thread=new Thread(()->{
try {
Thread.sleep(60*60*1000);
} catch (InterruptedException e) {
// e.printStackTrace();
System.out.println("我接受到了终止的通知");
}
});
thread.start();
Thread.sleep(1000);
System.out.println("终止子线程thread");
thread.interrupt();
}
}
1.5.2使用TimeUnit休眠
2.线程状态
2.1所有线程状态
1.打印线程的所有状态
public class ThreadState {
public static void main(String[] args) {
//打印所有的线程状态
printState();
}
private static void printState() {
for(Thread.State item:Thread.State.values()) {
System.out.println(item);
}
}
}
线程状态有6种:
(1)NEW:新建状态,当线程被创建,但是未启动(start方法)之前的状态。
(2)RUNNABLE:运行状态
(3)BLOCKED:阻塞状态(如果遇到锁,线程就会变为阻塞状态,等待另一个线程释放锁)
(4)WAITING:等待状态(没有明确的等待时间,无限期等待下去)
(5)TIMED_WAITING:等待状态(有时间限制的等待状态)
(6)TERMINATED:销毁状态,当线程结束完成之后就会变成此状态。
2.2线程状态转变
线程状态转变:
import java.util.concurrent.TimeUnit;
public class ThreadState {
public static void main(String[] args) throws InterruptedException {
//打印所有的线程状态
//printState();
Thread t1=new Thread(()->{
System.out.println("当前线程状态2:"+Thread.currentThread().getState());
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("当前线程的状态1:"+t1.getState());
t1.start();
//让主线程休眠1s
Thread.sleep(1000);
System.out.println("当前线程的状态3:"+t1.getState());
//等待子线程执行完
t1.join();
System.out.println("打印线程状态4:"+t1.getState());
}
private static void printState() {
for(Thread.State item:Thread.State.values()) {
System.out.println(item);
}
}
}
3.线程安全问题
1.概念:线程不安全指的是程序在多线程的执行结果不符合预期。
2.单线程:
单线程代码是顺序执行,最后计算的结果为0是正确的。
public class ThreadDemo16 {
static class Counter{
//变量
private int number=0;
//循环次数
private int MAX_COUNT=0;
public Counter(int i){
this.MAX_COUNT=MAX_COUNT;
}
//++方法
public void incr(){
for (int i = 0; i <MAX_COUNT ; i++) {
number++;
}
}
//--方法
public void decr(){
for (int i = 0; i <MAX_COUNT ; i++) {
number--;
}
}
public int getNumber(){
return number;
}
public static void main(String[] args) {
Counter counter=new Counter(1000000);
counter.incr();
counter.decr();
System.out.println("最终结果:"+counter.getNumber());
}
}
}
2.多线程:
利用多线程对同一个变量进行修改产生了错误,计算结果是错误的。
public class ThreadDemo16 {
static class Counter{
//变量
private int number=0;
//循环次数
private int MAX_COUNT=0;
public Counter(int MAX_COUNT){
this.MAX_COUNT=MAX_COUNT;
}
//++方法
public void incr(){
for (int i = 0; i <MAX_COUNT ; i++) {
number++;
}
}
//--方法
public void decr(){
for (int i = 0; i <MAX_COUNT ; i++) {
number--;
}
}
public int getNumber(){
return number;
}
public static void main(String[] args) throws InterruptedException {
Counter counter=new Counter(1000000);
Thread t1=new Thread(()->{
//++操作
counter.incr();
});
Thread t2=new Thread(()->{
//--操作
counter.decr();
});
//启动线程
t1.start();
t2.start();
//等待两个线程执行完
t1.join();
t2.join();
System.out.println("最终结果:"+counter.getNumber());
}
}
}
线程不安全因素
1.抢占式执行(狼多肉少)
多线程会抢占CPU资源,会造成线程不安全。
2.多个线程修改同一个变量
public class ThreadDemo16 {
static class Counter{
//变量
private int number=0;
//循环次数
private int MAX_COUNT=0;
public Counter(int MAX_COUNT){
this.MAX_COUNT=MAX_COUNT;
}
//++方法
public void incr(){
for (int i = 0; i <MAX_COUNT ; i++) {
number++;
}
}
//--方法
public void decr(){
for (int i = 0; i <MAX_COUNT ; i++) {
number--;
}
}
public int getNumber(){
return number;
}
public static void main(String[] args) throws InterruptedException {
Counter counter=new Counter(1000000);
Thread t1=new Thread(()->{
//++操作
counter.incr();
});
Thread t2=new Thread(()->{
//--操作
counter.decr();
});
//启动线程
t1.start();
t2.start();
//等待两个线程执行完
t1.join();
t2.join();
System.out.println("最终结果:"+counter.getNumber());
}
}
}
3.操作是非原子性操作
(1)什么是原子性?
执行代码时要一次性执行完,不能停顿。
(2)⼀条 java 语句不⼀定是原⼦的,也不⼀定只是⼀条指令。
比如:上面的temp++操作(非原子性)
(一)从内存查询temp当前值
(二)进行数据修改操作temp+1
(三)刷新temp的最新值
(3)不保证原子性会给线程带来什么问题?
如果一个线程正在对一个变量操作,中途其他线程插入进来了,如果这个操作被打断,结果就可能是错误的。
上面的情况正确答案应该是0,而上面结果为1,造成了线程的不安全性。
4.内存可见性问题
()可见性指,一个线程对共享变量值的修改,能够及时的被其他线程看到。
(2)java内存模型:
java虚拟机规范中定义了java内存模型。目的是屏蔽掉各种硬件和操作系统的内存访问差异,以实现让java程序在各种平台下都能达到一致的并发效果。
由于每个线程有自己的工作内存,这些工作内存中的内容相当于同一个共享变量的"副本",此时修改线程1的工作内存中的值,线程2的工作内存不一定会及时变化。JMM带来的问题是会导致线程非安全问题的发生。
(3)上面的那个计算例子:
正确的结果应该为0,由于内存可见性问题结果错误为1.
5.指令重排序
编译器优化的本质是调整代码的执行顺序,在单线程下没问题,但在多线程下容易出现混乱,从而导致线程安全问题。