黑马程序员——java基础---多线程(一)
学习多线程之前的一些基本概念认知:
进程:进程是动态的、是一个正在执行中的程序。每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。
线程:线程依附于进程,可以理解为进程下的一个子执行路径,但没有进程线程无法单独执行。
两者间的区别:进程的运行需要给它分配独立的地址空间和系统资源等。不同进程的数据和状态都是完全独立的,所以不同进程之间的通信或转换很不易;而线程执则是,行过程中所需的系统资源由进程进行统一分配,所以同一进程的不同线程执行过程中是会相互影响的,而且线程本身的数据通常放于寄存器或者堆栈中,所以线程切换的难度要比进程切换小的多。
单线程:如果某个程序执行过程中,进程下只有唯一的一条线程,那么这便是单线程。
多线程:如果某个程序执行过程中,进程下有不同的线程在执行各自的任务,那么这就是多线程。
了解了以上概念,那么第一个问题就出现了。计算机为什么要使用多线程来执行任务呢?答案很简单,因为多线程单位时间内执行的任务量比单线程大,而且对计算机CPU而言,单线程无疑会造成CPU的极度浪费。
多线程在Java中的体现:
1、与其它大多数编程语言不同,Java本身内置支持多线程编程。
2、程序运行过程中,包含两条或两条以上并发运行的部分,即为多线程。每个单独部分都可称之为线程,每个线程都有独立的执行路径。
多任务处理主要有两种不同的类型:基于进程和基于线程。
基于进程:进程本质上是一个执行的程序。所以说基于进程就是指可以同时运行多个程序。例如:我们可以一边听音乐,一边在QQ上聊天。
基于线程:这种方式中,线程才是最小的执行单位。而线程依赖于进程。可以理解为一个程序可以同时执行多个任务。例如:我们打开一个QQ软件,可以同时和多个人聊天。
线程的一些状态以及不同状态间的转换方式:
Java中的线程的五种状态:
1、新建:用new关键字来创建Thread类或者其子类的对象。
2、就绪:线程创建后,通过别的线程调用它的start()方法,使该线程就进入就绪状态。
3、运行:就绪状态状态的线程获得了CPU的使用权,便会进入运行状态。
4、阻塞:运行状态的线程,因为某种原因,从而无法取得CPU使用权,便进入了阻塞状态。
5、消亡:处于运行状态的线程,执行完run()方法后,自动释放CPU是使用权,就变成了消亡状态。
不同状态间的转换:
一、创建并运行线程
通过new创建线程后,调用其start()方法,使其执行重写的run()方法,从而进入运行状态。运行中的的线程可以使用Thread类中的isAlive()方法来判别线程的运行状态。如果isAlive()返回true,线程便处于运行状态;当isAlive()返回false,线程可能处于等待状态,也可能处于停止状态。
二、挂起和唤醒线程
线程执行过程中,可以通过两个方法使线程暂时停止执行,它们分别是suspend()和sleep()。在使用suspend()挂起线程后,可以通过resume()方法来重新唤醒线程;而使用sleep()使线程进入休眠后,只能等待设定的时间过后,线程才会恢复成就绪状态。虽然suspend()和resume()可以很方便地使线程挂起和唤醒,但由于使用这两个方法可能会造成一些不可预料的事情发生,因此,这两个方法被标识为‘弃用’标记。所以新写的程序中,尽量不要使用这两个方法来操作线程。
三、终止线程的三种方法:
1、设置退出标志,使线程正常退出,即当run()方法完成后线程终止。
2、使用stop()方法强行终止线程。这个和上面两个方法一样,不推荐使用。
3、通过interrupt()方法中断线程。
Java中线程的创建:
一、继承Thread类
步骤:1、定义类继承Thread 2、复写Thread类的run()方法 3、创建新定义类的对象 4、调用start()方法启动线程
代码实现:
1 package come.file; 2 3 //1、定义TestThread类继承Thread类 4 class TestThread extends Thread{ 5 //2、复写Thread类的run()方法 6 public void run(){ 7 System.out.println("这是TestThread类"); 8 } 9 } 10 public class MyThread{ 11 public static void main(String[] args){ 12 //创建新定义的TestThread类的对象 13 TestThread t = new TestThread(); 14 //调用start()方法 15 t.start(); 16 } 17 }
二、实现Runnable接口
步骤:1、创建类实现Runnable接口 2、复写run()方法 3、调用new Thread(runnable)方式创建线程 4、调用start()方法启动线程
代码实现:
1 package come.file; 2 3 //1、创建类实现Runnable接口 4 class TestThread2 implements Runnable { 5 //2、复写run()方法 6 public void run(){ 7 System.out.println("这是TestThread2类"); 8 } 9 } 10 public class MyThread2 { 11 public static void main(String[] args) { 12 //3、调用new Thread(runnable)方式创建线程 13 Thread t2 = new Thread(new TestThread2()); 14 //4、调用start()方法启动线程 15 t2.start(); 16 } 17 }
注意:
1、在继承Thread的方式中,可以使用getName()方法,来获得当前线程的名字。而在实现Runnable方式中,却不可以使用this.getName(),因为Runnable接口没有这个方法,所以只能通过Thread的静态方法Thread.currentThread()来获取当前的Thread对象,再通过调用getName()方法,来取得当前线程的名字。
2、对于Java来说,run()方法并没有任何特别之处。像main()方法一样,它只为了让新线程知道需要调用的方法的名称。因此,在Runnable上或者Thread上调用run()方法是合法的,但并不启动新的线程。只有通过调用start()方法才会启动新线程。
通过对比两种方式,我们可以发现Runnable接口方式要优于继承Thread类方式。因为前者实现了接口后还可以继承其他的类,而后者如果有了本身的父类,想要在使用多线程运行某段代码会比较麻烦。Runnable接口这种方式,非常适合多个相同线程来处理同一份资源这种情况,可以更准确的体现面向对象这种思维模式。
多线程的运行会出现安全问题:
问题原因:当多条语句操作同一个线程共享数据时,一个线程对只执行了一部分,还没有执行完,另一个线程便参与了进来,这就会导致共享数据的错误。
解决办法:对多条操作共享数据的语句,只能让一个线程执行完,执行过程中其它的线程无法参与进来。
Java对于多线程的的安全问题提供了专业的解决方式——synchrozied
代码模型:obj相当于锁,持有锁的线程可以在同步中执行,没有锁的线程即使有cpu执行权,也无法进入同步中执行。
synchronized(obj){ //需要同步的代码 }
同步的前提:
1、必须有两个或两个以上的线程。
2、必须是多个线程使用同一个锁,必须保证同步中只有一个线程在运行。
好处:解决了多线程的安全问题。
弊端:多一重判断消耗资源。
如何找到需要同步的代码?
1、明确那些代码是多线程代码。
2、明确共享数据。
3、明确多线程运行代码那些事操作共享数据的。
同步函数中的this相当于锁,静态同步函数中的所属类的字节码对象相当于锁。
两种单例模式:
饿汉式代码:
1 class Single{ 2 private static final Single s = new Single(); 3 private single(){} 4 public static Single getInstance(){ 5 return s; 6 } 7 }
懒汉式代码:
1 class Single{ 2 private static Single s = null; 3 private Single(){ } 4 5 public static Single getInstance(){ 6 if(s==null){ 7 synchronized(Single.class){ 8 if(s==null) 9 s = new Single(); 10 } 11 } 12 return s; 13 } 14 }
当多线程中有两把锁,便有可能产生死锁问题,例如以下代码:
1 class TestRun implements Runnable{ 2 private boolean flag; 3 TestRun(boolean flag){ 4 this.flag = flag; 5 } 6 public void run(){ 7 if(flag){ 8 synchronized (Lock.lockA) { 9 System.out.println(new Thread().currentThread().getName()+"获得lockA"); 10 synchronized (Lock.lockB) { 11 System.out.println(new Thread().currentThread().getName()+"获得lockB"); 12 } 13 } 14 }else{ 15 synchronized (Lock.lockB) { 16 System.out.println(new Thread().currentThread().getName()+"获得lockB"); 17 synchronized (Lock.lockA) { 18 System.out.println(new Thread().currentThread().getName()+"获得lockA"); 19 } 20 } 21 } 22 } 23 } 24 class Lock{ 25 static Object lockA = new Object(); 26 static Object lockB = new Object(); 27 28 } 29 public class DeadLockDemo { 30 public static void main(String[] args) { 31 TestRun t1 = new TestRun(true); 32 TestRun t2 = new TestRun(false); 33 new Thread(t1).start(); 34 new Thread(t2).start(); 35 } 36 37 }
所以,在编写多线程代码时,应尽量避免死锁的产生。