Day04:异常处理(二) / 多线程基础
多线程
线程是什么?
一个线程是线程一个顺序执行流。
同类的多个线程共享一块内存空间和一组系统资源,线程本身有一个供程序执行时的栈堆。线程在切换时负荷小,因此,线程也被称为轻负荷进程。一个进程中可以包含多个线程。
线程什么时候使用?
线程通常用于在一个程序中需要同时完成多个任务的情况。我们可以将每个任务定义一个线程,使他们得以一同工作。
也可以用于在单一线程中可以完成,但是使用多线程可以更快的情况。
线程创建
继承Thread类
public class Thread implements Runnable
Thread 类实现了 Runnable 接口,它们之间具有多态关系。
其实,使用继承 Thread 类的方式实现多线程,最大的局限就是不支持多继承
public class XC extedns Thread{ }
实现 Runnable 接口
语法:
public class thread extends Object implements Runnale{
}
从 JDK 的 API 中可以发现,实质上 Thread 类实现了 Runnable 接口,其中的 run() 方法正是对 Runnable 接口中 run() 方法的具体实现。
继承 Thread 类的优缺点
当一个 run() 方法体现在继承 Thread 类中时,可以用 this 指向实际控制运行的 Thread 实例。因此,代码不需要使用以下控制语句:
Thread.currenThread().sleep();
不再使用上面的控制语句,而是可以简单地使用 Threadsleep() 方法,继承 Thread 类的方式使代码变得简单易读。
实现 Runnable 接口的优缺点
从面向对象的角度来看,Thread 类是一个虚拟处理机严格的封装,因此只有当处理机模型修改或扩展时,才应该继承该类。由于 Java 技术只允许单一继承,因此如果已经继承了 Thread 类,就不能再继承其他任何类,这会使用户只能采用实现 Runnable 接口的方式创建线程。
实例:
/** * 线程创建 * 2种方式: * 继承 Thread类 * 实现接口 Runnable * @author soft01 * */ //class Xc extends Thread{//创建线程所需要继承的的类 // public void run() {//run方法是覆盖父类方法 // for(int i=0;i<20;i++) { // System.out.println("111"); // } // } //} class Xc2 implements Runnable{//不是继承类,而改成了实现接口 public void run() {//run方法是覆盖父类方法 for(int i=0;i<20;i++) { System.out.println("111"); } } } public class One { public static void main(String[] args) { // Xc xc=new Xc(); // xc.run(); // xc.start(); /** * 谁调用start方法,程序就去自动调用run方法 * start 会单开启一个线程,而不是直接调用。 */ Xc2 xc2=new Xc2(); Thread a=new Thread(xc2); a.start(); for(int i=0;i<20;i++) { System.out.println("222"); } } }
对线程生命周期中的 7 种状态做说明。
- 出生状态:用户在创建线程时所处的状态,在用户使用该线程实例调用 start() 方法之前,线程都处于出生状态。
- 就绪状态:也称可执行状态,当用户调用 start() 方法之后,线程处于就绪状态。
- 运行状态:当线程得到系统资源后进入运行状态。
- 等待状态:当处于运行状态下的线程调用 Thread 类的 wait() 方法时,该线程就会进入等待状态。进入等待状态的线程必须调用 Thread 类的 notify() 方法才能被唤醒。notifyAll() 方法是将所有处于等待状态下的线程唤醒。
- 休眠状态:当线程调用 Thread 类中的 sleep() 方法时,则会进入休眠状态。
- 阻塞状态:如果一个线程在运行状态下发出输入/输出请求,该线程将进入阻塞状态,在其等待输入/输出结束时,线程进入就绪状态。对阻塞的线程来说,即使系统资源关闭,线程依然不能回到运行状态。
- 死亡状态:当线程的 run() 方法执行完毕,线程进入死亡状态。
提示:一旦线程进入可执行状态,它会在就绪状态与运行状态下辗转,同时也可能进入等待状态、休眠状态、阻塞状态或死亡状态。
线程优先级
线程的切换是由线程调度控制的,我们可以通过代码来干涉,但是我们可以通过提高线程的优先级来最大程度的改善线程获取时间片的几率。
线程的优先级被划分为10级,值分别为1-10,其中1最低,10最高。线程提供了3个常量来表示最低,最高,以及默认优先级:
Thread.MIN_PRIORITY,
Thread.MAX_PRIORITY,
Thread.NORM_PRIORITY
线程同步
synchronized关键字
多个线程并发读写同一个临界时会发生“线程并发安全问题”
比如:
多线程共享实例变量
多线程共享静态公共变量
若要解决线程安全问题,需要将异步的操作变为同步操作。
异步操作:多线程并发的操作,相当于各干各的。
同步操作:有先后顺序的操作,相当于你完成后我在干。
synchronized关键字是java中的同步锁
线程休眠
sleep()方法的作用是在指定的毫秒数内让当前正在执行的线程“休眠”(暂停)
实例:
//线程睡眠 public class Four { public static void main(String[] args) { Xc5 xc5=new Xc5(); Thread c=new Thread(xc5); c.start(); } } class Xc5 implements Runnable{ public void run() {//throws Exception for(int i=0;i<2;i++) { System.out.println(Thread.currentThread().getName()+""+i); try { Thread.sleep(1000);//毫秒 System.out.println("结束时间"+System.currentTimeMillis()); }catch(Exception e) { } } } } //class Yy implements Runnable{ // public void run() throws Exception{ // // } //} //class Xx extends Thread{ // public void run() throws Exception{ // // } //} /** * 用throws抛异常的时候,如果向主调处抛异常的方法是从父类继承的 * 或者从接口实现的。那么,覆盖父类方法或实现接口时,如果父类中的原方法 * 或接口中的原抽象方法没有抛异常,则子类覆盖父类的方法或实现接口的方法也不能抛异常 * 而出现这种情况只能用try...catch,大不了catch中什么都不写 */
线程让步
yiled()方法的作用是放弃当前CPU资源,将它让给其他的任务去占用CPU执行时间。
实例:
//线程让步 public class Five { public static void main(String[] args) { Xc6 xc6=new Xc6(); Thread a=new Thread(xc6); Thread b=new Thread(xc6); a.setName("线程一"); b.setName("线程二"); a.start(); b.start(); } } class Xc6 implements Runnable{ public void run() { for(int i=0;i<=30;i++) { System.out.println(Thread.currentThread().getName()+""+i); if(i%1==0) { Thread.yield(); } } } }
锁机制
Java提供了一种内置的锁机制来支持原子性:
同步代码块(synchronized关键字),同步代码块包含两部分:一个作为锁的对象的引用,一个作为由这个锁保护的代码块。
synchronized (同步监视器——锁对象引用){ //代码块 }
若方法所有代码都需要同步也可以给方法直接加锁。
每个Java对象都可以用做一个实现同步的锁,线程进入同步代码块之前会自动获得锁,并且在退出同步代码块时自动释放锁,而且无论是通过正常途径退出还是通过抛异常退出都一样,获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法。
选择合适的锁对象
使用synchroinzed需要对一个对象上锁以保证线程同步。那么这个锁对象应当注意:
多个需要同步的线程在访问该同步块时,看到的应该是同一个锁对象引用。否则达不到同步效果。
通常我们会使用this来作为锁对象。
选择合适的锁范围
在使用同步块时,应当尽量在允许的情况下减少同步范围,以提高并发的执行效率。
静态方法锁
方法加锁:
public synchronized void xxx(){ }
那么该方法锁的对象是类对象。每个类都有唯一的一个类对象。获取类对象的方式:类名.class。
静态方法与非静态方法同时声明了synchronized,他们之间是非互斥关系的。原因在于,静态方法锁的是类对象而非静态方法锁的是当前方法所属对象。
synchronized 不仅可以用到同步方法,也可以用到同步块。对于同步块,synchronized 获取的是参数中的对象锁。
synchronized(obj) { //代码 }
实例:
class CP implements Runnable{ public static int chepiao=100; public static String aa=new String("1");//字符串随意定义,定义在函数上边 //synchronized 作用是让它所管辖的代码部分,全部执行完否则全部不执行 public void run() {//synchronized修饰函数不需要字符串,相当于默认是this while(true) { synchronized (aa) {//即可修饰代码块,又可以修饰函数 if(chepiao>0) { System.out.println("第"+Thread.currentThread().getName()+"个车站正在卖第"+(101-chepiao)+"张"); --chepiao; } else { break; } } } } } public class CP01 { public static void main(String[] args) { CP cp01=new CP(); Thread cc=new Thread(cp01); CP cp02=new CP(); Thread dd=new Thread(cp02); cc.start(); dd.start(); } }