> 文档中心 > 多线程开发与异常

多线程开发与异常

文章目录

  • 前言
  • 线程
    • 多线程的实现
      • Thread类
        • Thread常用方法
        • Thread的构造器
        • 优缺点
      • Runnable接口
        • 优缺点
    • 线程同步机制
      • volatile
      • 同步锁
      • 同步方法
      • lock锁
    • 线程池
  • 异常
    • 声明异常throws
    • 捕获异常try…catch finally 代码块
    • 自定义异常

前言

提示:本文来自老师授课内容,已获得老师同意在此当做本人学习笔记记录:
线程(thread)是一个程序内部的一条执行路径。
我们之前启动程序执行后,main方法的执行其实就是一条单独的执行路径。

程序中如果只有一条执行路径,那么这个程序就是单线程的程序。
多线程是指从软硬件上实现多条执行流程的技术
消息通信、淘宝、京东系统等都离不开多线程技术。


提示:以下是本篇文章正文内容,下面案例可供参考

多线程

并发:在一个时间段出现的,可能不是同时发生
并行:同时发生
进程:一个任务创建、运行、消亡
线程:进程的一个单元,一个进程可以有多个线程。

多线程的实现

多线程实现方式有2种:Thread类、Runnable接口

Thread类

① 定义一个子类,重写run方法
② 实例化子类
③ 对子类对象执行start方法来启动线程
(直接调用run方法会当成普通方法执行,此时相当于还是单线程执行。只有调用start方法才是启动一个新的线程执行。)

public class Test {public static void main(String[] args)  {MyThread myThread = new MyThread();//2.实例化子类,创建Thread的子类对象myThread.start();  //3.对子类对象执行start方法来启动线程for(int i =0; i<10; i++) {System.out.println("主线程"+i);} }}class MyThread extends Thread{ //1.自定义一个子类,重写run方法@Overridepublic void run(){for(int i =0; i<10; i++) {System.out.println("子线程"+getName()+i); //getName()返回线程名称}}}

Java是通过java.lang.Thread 类来代表线程的。
按照面向对象的思想,Thread类应该提供了实现多线程的方式。

Thread常用方法

String getName​()  //获取当前线程的名称,默认线程名称是Thread-索引void setName​(String name)  //将此线程的名称更改为指定的名称,通过构造器也可以设置线程名称public static Thread currentThread():  //返回对当前正在执行的线程对象的引用public static void sleep(long time)  //让当前线程休眠指定的时间后再继续执行,单位为毫秒(Thread类的线程休眠方法)public void run()  //线程任务方法public void start()  //线程启动方法

注意:
1、此方法是Thread类的静态方法,可以直接使用Thread类调用。
2、这个方法是在哪个线程执行中调用的,就会得到哪个线程对象。

Thread的构造器

public Thread(String name)  //可以为当前线程指定名称public Thread(Runnable target)  //封装Runnable对象交给线程对象public Thread(Runnable target,String name)  //封装Runnable对象成为线程对象,并指定线程名称

优缺点

优点:编码简单
缺点:存在单继承的局限性,线程类继承Thread后,不能继承其他类,不便于扩展。不能返回线程执行结果

Runnable接口

① 声明一个Runnable接口实现类,重写run方法
② 实例化,把实例化对象作为线程的目标进行创建
③ 执行start方法来启动线程

public class Test {public static void main(String[] args)  {MyThread myThread = new MyThread();//2.实例化Thread t = new Thread(myThread);//2.把实例化对象myThread作为Thread的target进行创建t.start();//启动线程for(int i =0; i<10; i++) {System.out.println("主线程"+i);}//主线程和子线程在抢夺资源,所以每次运行结果都不一样 }}//1.声明一个Runnable接口实现类,重写run方法class MyThread implements Runnable{@Overridepublic void run() {for(int i =0; i<10; i++) {System.out.println("子线程"+Thread.currentThread().getName()+i); //getName()返回线程名称}}}

优缺点

优点:线程任务类只是实现了Runnale接口,可以继续继承类和实现接口,扩展性强
缺点:编程多一层对象包装,如果线程有执行结果是不可以直接返回的

线程同步机制

模拟电影院卖100张票

public class Test {public static void main(String[] args)  {MyThread myThread = new MyThread();Thread t1 = new Thread(myThread);//线程1Thread t2 = new Thread(myThread);//线程2t1.start();t2.start(); }}class MyThread implements Runnable{private int num =100;//定义一个共享票源@Overridepublic void run() {while(num > 0) {//还有票try {Thread.sleep(50);//为了模拟买票操作耗时System.out.println(Thread.currentThread().getName()+"正在卖"+num+"票");//获取当前线程名 num--;} catch (InterruptedException e) {e.printStackTrace();} }}}

线程安全问题是由全局变量及静态变量引起的,若多线程同时执行写操作(单纯读操作一般是线程安全的),线程会去抢夺cpu资源完成操作,造成线程不安全。

同步机制:
1.volatile
2.同步锁
3.同步方法
4. CAS
5. Lock锁

synchronized(同步锁){需要同步操作的代码:1锁对象可以是任意类型。2多个线程对象要使用同一把锁。}public synchronized void method(){可能会产生线程安全问题的代码}class Ticket implements  Runnable{    Lock lock = new ReentrantLock();    private int num = 100;//定一个多线程共享的票源    @Override    public void run() { while (true){  //上锁     lock.lock();     if (num>0){...//代码省略     }     //释放锁     lock.unlock();     }    }}

volatile

假如说线程1修改了data变量的值为1,然后将这个修改写入自己的本地工作内存。那么此时,线程1的工作内存里的data值为1。然而,主内存里的data值还是为0!线程2的工作内存里的data值还是0啊?!

作用:

1、一旦data变量定义的时候前面加了volatile来修饰的话,那么线程1只要修改data变量的值,就会在修改完自己本地工作内存的data变量值之后,强制将这个data变量最新的值刷回主内存,必须让主内存里的data变量值立马变成最新的值!
2、如果此时别的线程的工作内存中有这个data变量的本地缓存,也就是一个变量副本的话,那么会强制让其他线程的工作内存中的data变量缓存直接失效过期,不允许再次读取和使用了!
3、如果线程2在代码运行过程中再次需要读取data变量的值,他就必须重新从主内存中加载data变量最新的值!那么不就可以读取到data = 1这个最新的值了!

同步锁

多线程开发与异常

public class Test {public static void main(String[] args)  {MyThread myThread = new MyThread();Thread t1 = new Thread(myThread);//线程1Thread t2 = new Thread(myThread);//线程2t1.start();t2.start(); }}class MyThread implements Runnable{private int num =100;//定义一个共享票源Object obj = new Object();@Overridepublic void run() {while(true) {//窗口永远开启synchronized(obj) {//同步锁if(num > 0) {try {Thread.sleep(50);//为了模拟买票操作耗时System.out.println(Thread.currentThread().getName()+"正在卖"+num+"票");//获取当前线程名 num--;} catch (InterruptedException e) {e.printStackTrace();}}} }}}

同步方法

public class Test {public static void main(String[] args)  {MyThread myThread = new MyThread();Thread t1 = new Thread(myThread);//线程1Thread t2 = new Thread(myThread);//线程2t1.start();t2.start(); }}class MyThread implements Runnable{private int num =100;//定义一个共享票源Object obj = new Object();@Overridepublic void run() {while(true) {//窗口永远开启method(); }}public synchronized void method(){ //同步方法if(num > 0) {try {Thread.sleep(50);//为了模拟买票操作耗时System.out.println(Thread.currentThread().getName()+"正在卖"+num+"票");//获取当前线程名 num--;} catch (InterruptedException e) {e.printStackTrace();}}}}

### CAS(Compare and Set)

lock锁

使用ReentrantLock实现同步, lock()方法上锁,unlock()方法释放锁

import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class Test {  public static void main(String[] args)  {    MyThread myThread = new MyThread();    Thread t1 = new Thread(myThread);//线程1    Thread t2 = new Thread(myThread);//线程2    t1.start();    t2.start();   }  }class MyThread implements Runnable{  private int num =100;//定义一个共享票源  Lock lock = new ReentrantLock();  //声明一个lock对象  @Override  public void run() {    while(true) {//还有票    lock.lock();   //上锁    if(num > 0) {    try {     Thread.sleep(50);//为了模拟买票操作耗时     System.out.println(Thread.currentThread().getName()+"正在卖"+num+"票");//获取当前线程名      num--;   } catch (InterruptedException e) {     e.printStackTrace();   }    }    lock.unlock(); //释放锁    }  }}

线程池

问题:如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。

线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

好处

1.降低资源消耗,如果线程池无空闲,则需等待其他任务执行完毕后归还线程。

2.提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。

3.提高线程的可管理性,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;/*1.创建线程池对象2.实现Runnable接口子类3.使用线程池4.关闭线程池*/public class Test {  public static void main(String[] args)  {    //1.创建线程池对象  ExecutorService service = Executors.newFixedThreadPool(2);  //包含2个线程对象  MyThread m = new MyThread();  //2.实现Runnable接口子类//  Thread t = new Thread(m);//  t.start();  service.submit(m);//3.使用线程池  service.submit(m);//submit执行之后程序并没有关闭,是因为线程池控制了线程的关闭  service.submit(m);//  service.shutdown();//4.关闭线程池 // service.submit(m); //关闭之后再执行则报异常   }  }class MyThread implements Runnable{@Overridepublic void run() { System.out.println("售票员开始卖票"); try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}//为了模拟买票操作耗时 System.out.println(Thread.currentThread().getName()+"正在卖票");//获取当前线程名 System.out.println("线程结束,回到线程池");}}

异常

java.lang.Throwable编译的时候不能运行

1.Error绝症

2.Exception小病

声明异常throws

//1.声明在方法上 2.异常必须是Exception及子类  3.必须处理声明的异常,不处理会交给jvm声明public class Test {public static void main(String[] args) throws Exception{read("D:\\a.txt"); }private static void read(String path) throws FileNotFoundException{if(path != "D:\\a.txt") {throw new FileNotFoundException("文件找不到异常");     //throw用在方法内,用来抛出一个编译异常对象}} }

捕获异常try…catch finally 代码块

public static void main(String[] args) {try { //try里编写了可能发生异常的代码throw new FileNotFoundException("文件找不到异常");} catch (FileNotFoundException e) {//catch捕获异常,对异常处理 System.out.println("对异常处理"+e);e.printStackTrace();}finally { System.out.println("不管怎么执行,finally都会执行");} System.out.println("over"); }

自定义异常

public class Test {public static void main(String[] args) throws Exception {throw new A("自定义异常"); }}class A extends Exception{public A() {}public A(String message) {super(message);}}