1、进程、线程、多线程
1.1.什么是进程?
电脑中时会有很多单独运行的程序,每个程序有一个独立的进程,而进程之间是相互独立存在的。比如下图中的QQ、酷狗播放器、电脑管家等等。
1.2.什么是线程?
进程想要执行任务就需要依赖线程。换句话说,就是进程中的最小执行单位就是线程,并且一个进程中至少有一个线程。
那什么是多线程?提到多线程这里要说两个概念,就是串行和并行,搞清楚这个,我们才能更好地理解多线程。
所谓串行,其实是相对于单条线程来执行多个任务来说的,我们就拿下载文件来举个例子:当我们下载多个文件时,在串行中它是按照一定的顺序去进行下载的,也就是说,必须等下载完A之后才能开始下载B,它们在时间上是不可能发生重叠的。
并行:下载多个文件,开启多条线程,多个文件同时进行下载,这里是严格意义上的,在同一时刻发生的,并行在时间上是重叠的。
了解了这两个概念之后,我们再来说说什么是多线程。举个例子,我们打开腾讯管家,腾讯管家本身就是一个程序,也就是说它就是一个进程,它里面有很多的功能,我们可以看下图,能查杀病毒、清理垃圾、电脑加速等众多功能。
1.3.多线程
按照单线程来说,无论你想要清理垃圾、还是要病毒查杀,那么你必须先做完其中的一件事,才能做下一件事,这里面是有一个执行顺序的。
如果是多线程的话,我们其实在清理垃圾的时候,还可以进行查杀病毒、电脑加速等等其他的操作,这个是严格意义上的同一时刻发生的,没有执行上的先后顺序。
以上就是,一个进程运行时产生了多个线程。
2、什么是线程安全?
当多个线程访问某个方法时,不管你通过怎样的调用方式、或者说这些线程如何交替地执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类是线程安全的。
既然是线程安全问题,那么毫无疑问,所有的隐患都是在多个线程访问的情况下产生的,也就是我们要确保在多条线程访问的时候,我们的程序还能按照我们预期的行为去执行,我们看一下下面的代码。
1 Integer count = 0; 2 public void getCount() { 3 count ++; 4 System.out.println(count); 5 }
很简单的一段代码,下面我们就来统计一下这个方法的访问次数,多个线程同时访问会不会出现什么问题,我开启的3条线程,每个线程循环10次,得到以下结果:
我们可以看到,这里出现了两个26,出现这种情况显然表明这个方法根本就不是线程安全的,出现这种问题的原因有很多。
最常见的一种,就是我们A线程在进入方法后,拿到了count的值,刚把这个值读取出来,还没有改变count的值的时候,结果线程B也进来的,那么导致线程A和线程B拿到的count值是一样的。
那么由此我们可以了解到,这确实不是一个线程安全的类,因为他们都需要操作这个共享的变量。其实要对线程安全问题给出一个明确的定义,还是蛮复杂的,我们根据我们这个程序来总结下什么是线程安全。
当多个线程访问某个方法时,不管你通过怎样的调用方式、或者说这些线程如何交替地执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类是线程安全的。
搞清楚了什么是线程安全,接下来我们看看Java中确保线程安全最常用的两种方式。先来看段代码。
1 public void threadMethod(int j) { 2 3 int i = 1; 4 5 j = j + i; 6 }
大家觉得这段代码是线程安全的吗?
毫无疑问,它绝对是线程安全的,我们来分析一下,为什么它是线程安全的?
我们可以看到这段代码是没有任何状态的,就是说我们这段代码,不包含任何的作用域,也没有去引用其他类中的域进行引用,它所执行的作用范围与执行结果只存在它这条线程的局部变量中,并且只能由正在执行的线程进行访问。当前线程的访问,不会对另一个访问同一个方法的线程造成任何的影响。
两个线程同时访问这个方法,因为没有共享的数据,所以他们之间的行为,并不会影响其他线程的操作和结果,所以说无状态的对象,也是线程安全的
添加一个状态呢?
如果我们给这段代码添加一个状态,添加一个count,来记录这个方法并命中的次数,每请求一次count+1,那么这个时候这个线程还是安全的吗?
1 public class ThreadDemo { 2 3 int count = 0; // 记录方法的命中次数 4 5 public void threadMethod(int j) { 6 7 count++ ; 8 9 int i = 1; 10 11 j = j + i; 12 } 13 }
明显已经不是了,单线程运行起来确实是没有任何问题的,但是当出现多条线程并发访问这个方法的时候,问题就出现了,我们先来分析下count+1这个操作。
进入这个方法之后首先要读取count的值,然后修改count的值,最后才把这把值赋值给count,总共包含了三步过程:“读取”一>“修改”一>“赋值”,既然这个过程是分步的,那么我们先来看下面这张图,看看你能不能看出问题:
可以发现,count的值并不是正确的结果,当线程A读取到count的值,但是还没有进行修改的时候,线程B已经进来了,然后线程B读取到的还是count为1的值,正因为如此所以我们的count值已经出现了偏差,那么这样的程序放在我们的代码中,是存在很多的隐患的。
3.实现多线程方式
1.继承Thread类
2.实现Runnable接口
3.实现Callable接口,重写call()方法。
3.1.继承Thread类
- 定义一个类MyThread继承Thread类
- 在MyThread类中重写run()方法
- 创建MyThread类的对象
- 启动线程
3.2.实现Runnable接口
- 避免了Java单继承的局限性
- 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现
3.3.实现Callable接口,重写call()方法
public class Demo7 { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub ExecutorService threadPool = Executors.newSingleThreadExecutor(); //启动线程 Future<String> future = threadPool.submit(new myCallable()); try{ System.out.println("waiting the thread to finish,,,"); //使用Future监视目标线程调用call()方法的情况,当前的线程会一直阻塞,直到call()方法结束返回结果。 System.out.println("获取监听线程返回值: "+future.get()); }catch(Exception e){ e.printStackTrace(); } } } /** * callable 比 runnable 强大的地方是:可以返回结果值 * @return String * @others:接口是 Executor 框架的功能类; * */ class myCallable implements Callable<String> { @Override public String call() throws Exception { System.out.println("callable body..."); Thread.sleep(2000); return "Hello World!!"; } }
4、如何确保线程安全?
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
4.1.synchronized
synchronized关键字,就是用来控制线程同步的,保证我们的线程在多线程环境下,不被多个线程同时执行,确保我们数据的完整性,使用方法一般是加在方法上。
public class ThreadDemo { int count = 0; // 记录方法的命中次数 public synchronized void threadMethod(int j) { count++ ; int i = 1; j = j + i; } }
同步代码块:
1 /** 2 * 多线程卖票:synchronized 3 */ 4 public class Demo10 { 5 public static void main(String[] args) { 6 ticket t = new ticket(); 7 8 Thread t1 = new Thread(t, "线程1"); 9 Thread t2 = new Thread(t, "线程2"); 10 Thread t3 = new Thread(t, "线程3"); 11 12 t1.start(); 13 t2.start(); 14 t3.start(); 15 16 } 17 18 static class ticket implements Runnable { 19 private int ticket = 10; 20 //创建锁 21 Object lock = new Object(); 22 23 @Override 24 public void run() { 25 String name = Thread.currentThread().getName(); 26 while (true) { 27 //同步代码块 28 synchronized (lock){ 29 try { 30 sell(name); 31 } catch (InterruptedException e) { 32 e.printStackTrace(); 33 } 34 if (ticket < 0) { 35 break; 36 } 37 } 38 } 39 } 40 41 private void sell(String name) throws InterruptedException { 42 Thread.sleep(1000); 43 if (ticket > 0) { 44 System.out.println(name + "——" + ticket); 45 ticket--; 46 } 47 } 48 } 49 }
同步方法:
1 public class Demo9 { 2 public static void main(String[] args) { 3 ticket t = new ticket(); 4 5 Thread t1 = new Thread(t, "线程1"); 6 Thread t2 = new Thread(t, "线程2"); 7 Thread t3 = new Thread(t, "线程3"); 8 9 t1.start(); 10 t2.start(); 11 t3.start(); 12 } 13 14 static class ticket implements Runnable{ 15 private int ticket =10; 16 Lock lock = new ReentrantLock(); 17 18 @Override 19 public void run() { 20 String name = Thread.currentThread().getName(); 21 while(true){ 22 try { 23 if ("线程1".equals(name)){ 24 synchronized (lock){//线程1获得lock锁 25 sell(name); 26 } 27 }else{ 28 sell(name); 29 } 30 } catch (InterruptedException e) { 31 e.printStackTrace(); 32 } 33 if (ticket < 0){ 34 break; 35 } 36 37 } 38 } 39 //synchronized:同步方法 40 private synchronized void sell(String name) throws InterruptedException {//线程2获取到this锁 41 Thread.sleep(1000); 42 synchronized (lock){ 43 if (ticket >0){ 44 System.out.println(name +"——"+ ticket); 45 ticket --; 46 } 47 } 48 } 49 } 50 }
这样就可以确保我们的线程同步了,同时这里需要注意一个大家平时忽略的问题,首先synchronized锁的是括号里的对象,而不是代码,其次,对于非静态的synchronized方法,锁的是对象本身也就是this。
当synchronized锁住一个对象之后,别的线程如果想要获取锁对象,那么就必须等这个线程执行完释放锁对象之后才可以,否则一直处于等待状态。
注意点:虽然加synchronized关键字,可以让我们的线程变得安全,但是我们在用的时候,也要注意缩小synchronized的使用范围,如果随意使用时很影响程序的性能,别的对象想拿到锁,结果你没用锁还一直把锁占用,这样就有点浪费资源。
4.2.lock
先来说说它跟synchronized有什么区别吧,Lock是在Java1.6被引入进来的,Lock的引入让锁有了可操作性,什么意思?就是我们在需要的时候去手动的获取锁和释放锁,甚至我们还可以中断获取以及超时获取的同步特性,但是从使用上说Lock明显没有synchronized使用起来方便快捷。我们先来看下一般是如何使用的:
1 private Lock lock = new ReentrantLock(); // ReentrantLock是Lock的子类 2 3 private void method(Thread thread){ 4 lock.lock(); // 获取锁对象 5 try { 6 System.out.println("线程名:"+thread.getName() + "获得了锁"); 7 // Thread.sleep(2000); 8 }catch(Exception e){ 9 e.printStackTrace(); 10 } finally { 11 System.out.println("线程名:"+thread.getName() + "释放了锁"); 12 lock.unlock(); // 释放锁对象 13 } 14 }
进入方法我们首先要获取到锁,然后去执行我们业务代码,这里跟synchronized不同的是,Lock获取的所对象需要我们亲自去进行释放,为了防止我们代码出现异常,所以我们的释放锁操作放在finally中,因为finally中的代码无论如何都是会执行的。
写个主方法,开启两个线程测试一下我们的程序是否正常:
1 public static void main(String[] args) { 2 LockTest lockTest = new LockTest(); 3 4 // 线程1 5 Thread t1 = new Thread(new Runnable() { 6 7 @Override 8 public void run() { 9 // Thread.currentThread() 返回当前线程的引用 10 lockTest.method(Thread.currentThread()); 11 } 12 }, "t1"); 13 14 // 线程2 15 Thread t2 = new Thread(new Runnable() { 16 17 @Override 18 public void run() { 19 lockTest.method(Thread.currentThread()); 20 } 21 }, "t2"); 22 23 t1.start(); 24 t2.start(); 25 }
结果
可以看出我们的执行,是没有任何问题的。
卖票案例:
1 /** 2 * 多线程卖票 3 */ 4 public class Demo8 { 5 public static void main(String[] args) { 6 ticket t = new ticket(); 7 8 Thread t1 = new Thread(t, "线程1"); 9 Thread t2 = new Thread(t, "线程2"); 10 Thread t3 = new Thread(t, "线程3"); 11 12 t1.start(); 13 t2.start(); 14 t3.start(); 15 16 } 17 18 static class ticket implements Runnable{ 19 ReentrantLock lock = new ReentrantLock(); 20 private int ticket =10; 21 22 @Override 23 public void run() { 24 String name = Thread.currentThread().getName(); 25 while(true){ 26 //加锁 27 lock.lock(); 28 try { 29 sell(name); 30 } catch (InterruptedException e) { 31 e.printStackTrace(); 32 } 33 if (ticket < 0){ 34 break; 35 } 36 //释放锁 37 lock.unlock(); 38 } 39 } 40 41 private void sell(String name) throws InterruptedException { 42 Thread.sleep(1000); 43 if (ticket >0){ 44 System.out.println(name +"——"+ ticket); 45 ticket --; 46 } 47 } 48 } 49 }
4.3.死锁
多线程死锁:同步中嵌套同步,导致锁无法释放。
死锁解决办法:不要在同步中嵌套同步
1 ** 2 * 死锁 3 */ 4 public class Demo9 { 5 public static void main(String[] args) { 6 ticket t = new ticket(); 7 8 Thread t1 = new Thread(t, "线程1"); 9 Thread t2 = new Thread(t, "线程2"); 10 Thread t3 = new Thread(t, "线程3"); 11 12 t1.start(); 13 t2.start(); 14 t3.start(); 15 } 16 17 static class ticket implements Runnable{ 18 private int ticket =10; 19 Lock lock = new ReentrantLock(); 20 21 @Override 22 public void run() { 23 String name = Thread.currentThread().getName(); 24 while(true){ 25 try { 26 if ("线程1".equals(name)){ 27 synchronized (lock){//线程1获得lock锁 28 sell(name); 29 } 30 }else{ 31 sell(name); 32 } 33 } catch (InterruptedException e) { 34 e.printStackTrace(); 35 } 36 if (ticket < 0){ 37 break; 38 } 39 40 } 41 } 42 //synchronized:同步方法 43 private synchronized void sell(String name) throws InterruptedException {//线程2获取到this锁 44 Thread.sleep(1000); 45 synchronized (lock){ 46 if (ticket >0){ 47 System.out.println(name +"——"+ ticket); 48 ticket --; 49 } 50 } 51 } 52 } 53 }
5.线程的生命周期与状态
5.1.多线程的几种状态
- 新建状态(New):
用new语句创建的线程处于新建状态,此时它和其他Java对象一样,仅仅在堆区中被分配了内存。 - 就绪状态(Runnable):
当一个线程对象创建后,其他线程调用它的start()方法,该线程就进入就绪状态,Java虚拟机会为它创建方法调用栈和程序计数器。处于这个状态的线程位于可运行池中,等待获得CPU的使用权。 - 运行状态(Running):
处于这个状态的线程占用CPU,执行程序代码。只有处于就绪状态的线程才有机会转到运行状态。 - 阻塞状态(Blocked):
阻塞状态是指线程因为某些原因放弃CPU,暂时停止运行。当线程处于阻塞状态时,Java虚拟机不会给线程分配CPU。直到线程重新进入就绪状态,它才有机会转到运行状态。- 阻塞状态可分为以下3种:
- 位于对象等待池中的阻塞状态(Blocked in object’s wait pool):
当线程处于运行状态时,如果执行了某个对象的wait()方法,Java虚拟机就会把线程放到这个对象的等待池中,这涉及到“线程通信”的内容。 - 位于对象锁池中的阻塞状态(Blocked in object’s lock pool):
当线程处于运行状态时,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他线程占用,Java虚拟机就会把这个线程放到这个对象的锁池中,这涉及到“线程同步”的内容。 - 其他阻塞状态(Otherwise Blocked):
当前线程执行了sleep()方法,或者调用了其他线程的join()方法,或者发出了I/O请求时,就会进入这个状态。
- 位于对象等待池中的阻塞状态(Blocked in object’s wait pool):
- 阻塞状态可分为以下3种:
- 死亡状态(Dead):
当线程退出run()方法时,就进入死亡状态,该线程结束生命周期。
查看Thread源码,能够看到java的线程有六种状态:
NEW:线程刚被创建,但是并未启动。
RUNNABLE(可运行):
线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。
BLOCKED(锁阻塞):
当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
WAITING(无限等待):
一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
TIMED_WAITING(计时等待):
同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep、Object.wait。
TERMINATED(被终止):
因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。
5.2.线程控制
jion方法:
1 public class Demo12 { 2 public static void main(String[] args) { 3 ThreadJoin tj1 = new ThreadJoin(); 4 ThreadJoin tj2 = new ThreadJoin(); 5 ThreadJoin tj3 = new ThreadJoin(); 6 tj1.setName("康熙"); 7 tj2.setName("四阿哥"); 8 tj3.setName("八阿哥"); 9 tj1.start(); 10 try { 11 //等线程tj1执行完成之后,其余线程方可执行 12 tj1.join(); 13 } catch (InterruptedException e) { 14 e.printStackTrace(); 15 } 16 tj2.start(); 17 tj3.start(); 18 } 19 public static class ThreadJoin extends Thread { 20 @Override 21 public void run() { 22 for (int i = 0; i < 5; i++) { 23 System.out.println(getName() + ":" + i); 24 } 25 } 26 } 27 }
setDemon方法:
1 public class Demo2 { 2 public static void main(String[] args) { 3 Thread t1 = new Thread(new demo()); 4 //Thread t2 = new Thread(new demo()); 5 6 //设置t2为守护线程 7 //t2.setDaemon(true); 8 //设置t1为守护线程 9 t1.setDaemon(true); 10 t1.start(); 11 //t2.start(); 12 } 13 static class demo extends Thread{ 14 public void run(){ 15 String name = Thread.currentThread().getName(); 16 for (int i = 0;i <4;i++){ 17 try { 18 Thread.sleep(1000); 19 } catch (InterruptedException e) { 20 e.printStackTrace(); 21 } 22 System.out.println(name +"的内容是"+i); 23 } 24 } 25 } 26 }
5.3.wait()、notify()
wait()、notify()、notifyAll()是三个定义在Object类里的方法,可以用来控制线程的状态状态。
wait 方法会使持有该对象的线程把该对象的控制权交出去,然后处于等待状态。
notify 方法会通知某个正在等待这个对象的控制权的线程继续运行。
notifyAll 方法会通知所有正在等待这个对象的控制权的线程继续运行。
注意:一定要在线程同步中使用,并且是同一个锁的资源
1 /** 2 * wait和notify方法例子,一个人进站出站: 3 */ 4 public class Demo13 { 5 public static void main(String[] args) { 6 State state = new State(); 7 InThread inThread = new InThread(state); 8 OutThread outThread = new OutThread(state); 9 Thread in = new Thread(inThread); 10 Thread out = new Thread(outThread); 11 in.start(); 12 out.start(); 13 } 14 15 // 控制状态 16 static class State { 17 //状态标识 18 public String flag = "车站内"; 19 } 20 21 static class InThread implements Runnable { 22 private State state; 23 public InThread(State state) { 24 this.state = state; 25 } 26 public void run() { 27 while (true) { 28 synchronized (state) { 29 if ("车站内".equals(state.flag)) { 30 try { 31 // 如果在车站内,就不用进站,等待,释放锁 32 state.wait(); 33 } catch (Exception e) { 34 } 35 } 36 37 } 38 System.out.println("进站"); 39 state.flag = "车站内"; 40 // 唤醒state等待的线程 41 state.notify(); 42 } 43 } 44 } 45 46 static class OutThread implements Runnable { 47 private State state; 48 49 public OutThread(State state) { 50 this.state = state; 51 } 52 public void run() { 53 while (true) { 54 synchronized (state) { 55 if ("车站外".equals(state.flag)) { 56 try { 57 //就不用出站了,等待,释放锁 58 state.wait(); 59 } catch (Exception e) { 60 } 61 } 62 System.out.println("出站"); 63 state.flag = "车站外"; 64 // 唤醒state等待的线程 65 state.notify(); 66 } 67 } 68 } 69 } 70 }
5.4.wait与sleep区别
- 对于sleep()方法,首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的
- sleep 控状态依然保持者,当指定的时间到了又会自动恢复运行状态。wait()是把控制权交出去,然后进入等待此对象的等待锁定池处于等待状态,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
- 在调用sleep()方法的过程中,线程不会释放对象锁。而当调用wait()方法的时候,线程会放弃对象锁 。
5.5.线程停止
结束线程有以下三种方法:
(1)设置退出标志,使线程正常退出。
(2)使用interrupt()方法中断线程。
(3)使用stop方法强行终止线程(不推荐使用Thread.stop, 这种终止线程运行的方法已经被废弃,使用它们是极端不安全的!)
(1)使用退出标志
一般run()方法执行完,线程就会正常结束,然而,常常有些线程是伺服线程。它们需要长时间的运行,只有在外部某些条件满足的情况下,才能关闭这些线程。使用一个变量来控制循环,例如:最直接的方法就是设一个boolean类型的标志,并通过设置这个标志为true或false来控制while循环是否退出,代码示例:
1 public class Demo8Exit { 2 3 public static boolean exit = true; 4 5 public static void main(String[] args) throws InterruptedException { 6 Thread t = new Thread(new Runnable() { 7 public void run() { 8 while (exit) { 9 try { 10 System.out.println("线程执行!"); 11 Thread.sleep(100l); 12 } catch (InterruptedException e) { 13 e.printStackTrace(); 14 } 15 } 16 } 17 }); 18 t.start(); 19 20 Thread.sleep(1000l); 21 exit = false; 22 System.out.println("退出标识位设置成功"); 23 } 24 }
(2)使用interrupt()方法中断线程
1 public class Demo9Interrupt { 2 3 public static boolean exit = true; 4 5 public static void main(String[] args) throws InterruptedException { 6 Thread t = new Thread(new Runnable() { 7 public void run() { 8 while (exit) { 9 try { 10 System.out.println("线程执行!"); 11 12 //判断线程的中断标志来退出循环 13 if (Thread.currentThread().isInterrupted()) { 14 break; 15 } 16 17 Thread.sleep(100l); 18 } catch (InterruptedException e) { 19 e.printStackTrace(); 20 //线程处于阻塞状态,当调用线程的interrupt()方法时, 21 //会抛出InterruptException异常,跳出循环 22 break; 23 } 24 } 25 } 26 }); 27 t.start(); 28 29 Thread.sleep(1000l); 30 //中断线程 31 t.interrupt(); 32 System.out.println("线程中断了"); 33 } 34 }
6.线程优先级
6.1 优先级priority
现今操作系统基本采用分时的形式调度运行的线程,线程分配得到时间片的多少决定了线程使用处理器资源的多少,也对应了线程优先级这个概念。在JAVA线程中,通过一个int priority来控制优先级,范围为1-10,其中10最高,默认值为5。
1 public class Demo10Priorityt { 2 3 public static void main(String[] args) { 4 PrioritytThread prioritytThread = new PrioritytThread(); 5 6 // 如果8核CPU处理3线程,无论优先级高低,每个线程都是单独一个CPU执行,就无法体现优先级 7 // 开启10个线程,让8个CPU处理,这里线程就需要竞争CPU资源,优先级高的能分配更多的CPU资源 8 for (int i = 0; i < 3; i++) { 9 Thread t = new Thread(prioritytThread, "线程" + i); 10 if (i == 1) { 11 t.setPriority(4); 12 } 13 if (i == 2) { 14 t.setPriority(8); 15 } 16 t.setDaemon(true); 17 t.start(); 18 } 19 20 try { 21 Thread.sleep(1000l); 22 } catch (InterruptedException e) { 23 e.printStackTrace(); 24 } 25 26 System.out.println("线程1总计:" + PrioritytThread.count1); 27 System.out.println("线程2总计:" + PrioritytThread.count2); 28 } 29 30 static class PrioritytThread implements Runnable { 31 public static Integer count1 = 0; 32 public static Integer count2 = 0; 33 34 public void run() { 35 while (true) { 36 if ("线程1".equals(Thread.currentThread().getName())) { 37 count1++; 38 } 39 if ("线程2".equals(Thread.currentThread().getName())) { 40 count2++; 41 } 42 if (Thread.currentThread().isInterrupted()) { 43 break; 44 } 45 } 46 } 47 } 48 }
6.2.join()方法
两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。6.3.yield方法
6.3.yield方法
yield()的目的是让具有相同优先级的线程之间能够适当的轮换执行。但是,实际中无法保证yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。
7.线程的几个主要概念
在多线程编程时,你需要了解以下几个概念:
- 线程同步
- 线程间通信
- 线程死锁
- 线程控制:挂起、停止和恢复
8.多线程并发的3个特性
多线程并发开发中,要知道什么是多线程的原子性,可见性和有序性,以避免相关的问题产生。
8.1 原子性
原子性: 即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行
一个很经典的例子就是银行账户转账问题:
比如从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。1000元之后,操作突然中止。这样就会导致账户A虽然减去了1000元,但是账户B没有收到这个转过来的1000元。
所以这2个操作必须要具备原子性才能保证不出现一些意外的问题。
8.2 可见性
可见性: 当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即
看得到修改的值
举个简单的例子,看下面这段代码:
//线程1执行的代码 int i = 0; i = 10; //线程2执行的代码 j = i;
当线程1执行 int i = 0 这句时, i 的初始值0加载到内存中,然后再执行 i =10 ,那么在内存中 i 的值变为10了。
如果当线程1执行到 int i = 0 这句时,此时线程2执行 j = i,它读取 i 的值并加载到内存中,注意此时内存当中i的值是0,那么就会使得 j 的值也为0,而不是10。
这就是可见性问题,线程1对变量 i 修改了之后,线程2没有立即看到线程1修改的值
8.3.有序性
有序性: 程序执行的顺序按照代码的先后顺序执行
要想并发程序正确地执行,必须要保证原子性、可见性以及有序性。只要有一个没有被保证,就有可能会导致程序运行不正确。