码迷,mamicode.com
首页 > 编程语言 > 详细

Java多线程

时间:2021-05-24 01:03:30      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:下载文件   不同   返回   线程的生命周期   row   特殊情况   服务器   定义   notifyall   

多线程的概述:即同时做多件事情;一个服务器可以让多个人同时访问。

进程的概述:在一个操作系统中,每个独立执行的程序都可称之为一个进程,也就是“正在运行的程序”。

技术图片

在以上图示中,在一个程序中多个线程执行图,看似同时进行,其实是由CPU调度,CPU的运行速度很快,所以看起来像是同时执行的。

在Java中提供了实现多线程的两种方式:一种是继承java.lang包下的Thread类,覆写Thread类的run()方法,在run()方法中实现运行在线程上的代码;另一种是实现java.lang.Runnable接口,同样是在run()方法中实现运行在线程上的代码。

继承Thread类

  • 子类继承Thread类具有多线程能力。
  • 启动线程:创建子类对象.start()。
  • 具有oop单继承的局限性。

分析一下单线程和多线程的运行流程:

技术图片

可以看出,单线程执行时会按照顺序一步步执行,而多线程,在main()方法和run()方法都可以同时运行。

线程开启不一定执行,由CPU调度执行。

public class TestThreadP1 extends Thread{		                //继承Thread类
    @Override
    public void run() {							//重写run()方法
        //run方法线程体:
        for (int i = 0; i < 20; i++) {
            System.out.println("run方法线程体" + i);
        }
    }

    public static void main(String[] args) {
        //创建一个线程对象:
        TestThreadP1 thread = new TestThreadP1();
        //调用start()方法,开启线程:
        thread.start();

        //主方法main线程:
        for (int i = 0; i < 2000; i++) {
            System.out.println("主方法main线程" + i);
        }
    }
}

注意:通过继承Thread类实现了多线程,但是这种方式有一定的局限性。因为Java中只支持单继承,一个类一旦继承了某个父类就无法再继承Thread类。

实现Runnable接口

  • 实现接口Runnable具有多线程能力,重写run()方法
  • 启动线程:出入目标对象+Thread对象.start()
  • 可以避免oop单继承的局限性,方便同一个对象被多个线程使用

与Thread不同,实现Runnable接口,可以解决Java单继承的问题,可以实现多线程,也可以继承于其他的类。

public class TestRunnable implements Runnable {		//实现Runnable接口
    @Override
    //run方法实现体:
    public void run() {
        for (int i = 0; i < 2000; i++) {
            System.out.println("Runnable 执行!!!" + i);
        }
    }
    public static void main(String[] args) {
        //创建runnable接口的实现类:
        TestRunnable test = new TestRunnable();
        //创建线程对象,通过线程对象来开启线程:
        Thread thread = new Thread(test);
        //开启线程:
        thread.start();

        for (int i = 0; i < 20; i++) {
            System.out.println("main 执行!!!" + i);
        }
    }
}

实现Callable接口

当多个线程同时操作同一个资源的情况下,线程不安全,会发生数据紊乱的情况。所以这时候,可以使用实现Callable接口来实现多线程;其可以抛出异常和定义返回值;这部分如果时初学者可以了解一下!到后期还是挺重要的。

  • 重写call()方法,实现接口Callable具有多线程能力
  • 创建执行服务(添加线程) ExecutorService
  • 提交执行线程 (Future)
  • 关闭线程 (shutdownNow)
  • 可以定义返回值,可以抛出异常。
public class TestCallable implements Callable<Boolean> {	//实现Callable接口
    private String url;
    private String name;

    public TestCallable(String url, String name) {
        this.url = url;
        this.name = name;
    }
    @Override
    public Boolean call(){
        WebDownload2 webdown = new WebDownload2();
        webdown.downloader(url,name);
        System.out.println("下载文件:" + name);
        return true;      //返回值
    }
}
class WebDownload2{
    public void downloader(String url,String name){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        //创建目标对象:
        TestCallable t1 = new TestCallable("链接","名字");
        TestCallable t2 = new TestCallable("链接","名字");
        TestCallable t3 = new TestCallable("链接","名字");
        //创建执行服务:
        ExecutorService ser = Executors.newFixedThreadPool(3);
        //提交执行:
        Future<Boolean> r1 = ser.submit(t1);
        Future<Boolean> r2 = ser.submit(t2);
        Future<Boolean> r3 = ser.submit(t3);
        //关闭Callable服务
        ser.shutdownNow();				
    }
}

Lambda表达式

Java中Lambda表达式,虽然说时一种语法糖,但是在多线程中的作用还是非常大的。但是只有一点,如果想要使用Lambda表达式,这个方法必须时函数式接口,不然无法使用Lambda表达式。Lambda表达式的使用让定义函数式接口里的方法更加方便。

函数式接口的定义:

  • 任何接口,如果只包含唯一一个抽象方法,那么就是一个函数式接口。
  • 对于函数式接口,可以通过lambda表达式来创建该接口的对象。
public class TestLambda {
    public static void main(String[] args) {
        Test test = new Testc();
        test.run(100,"小明");

        //Lambda表达式:
        test = (a,b) -> {       //多个参数可以去掉参数类型,但是必须去掉括号
            System.out.println(b + "run6--->" + a + "步");
        };
        test.run(10000,"小麦");
    }
}
interface Test{
    void run(int a, String b);
}
class Testc implements Test{
    @Override
    public void run(int a, String b) {
        System.out.println(b + "run1--->" + a + "步");
    }
}

静态代理模式

静态代理模式,即一个真实对象通过一个代理对象来实现一些具体的方法;一个对象,可以通过例外一个对象来实现某些功能。

  • 一个对象,可以通过例外一个对象来实现某些功能;
  • 真实对象和代理对象都要实现同一个接口;
public class StaticProxy {
    public static void main(String[] args) {
      /*  residenter rd = null;
        rd = ()->{
            System.out.println("resident is succeed! == Lambda");
        };
        rd.resident();*/

      new Thread( () -> {
          System.out.println("this is lambda!--error");
      }).start();

      residentc res = new residentc(new You());
      res.resident();
    }
}

//定义接口:
interface residenter{
    void resident();
}
//定义实现类:(真实对象)	实现residenter接口
class You implements residenter{
    @Override
    public void resident() {
        System.out.println("resident is succeed!");
    }
}
//定义静态代理:(代理对象)	实现residenter接口
class residentc implements residenter{
    private residenter target;

    public residentc(residenter target) {
        this.target = target;
    }
    @Override
    public void resident() {
        before();
        this.target.resident();
        atfer();
    }
    private void before() {
        System.out.println("暂无此用户!");
    }
    private void atfer() {
        System.out.println("用户注册成功!");
    }
}    

线程的生命周期

在Java中,任何对象都有生命周期,线程也不例外,它也有自己的生命周期;线程的生命周期一共包含了五大状态。

技术图片

线程的状态:(五大状态)

  • 新建状态:创建一个线程对象后,该线程对象就处于新建状态。
  • 就绪状态:当线程对象调用了start()方法后,该线程就进入就绪状态;等待CPU调度。
  • 运行状态:如果处于就绪状态的线程获得了CPU的使用权,并开始执行run()方法中的线程执行体,则该线 程处于运行状态。
  • 阻塞状态:一个正在执行的线程在某些特殊情况下,如被人为挂起或执行耗时的输入/输出操作时,会让出CPU的使用权并暂时中止自己的执行,进入阻塞状态。
  • 死亡状态:当线程调用stop()方法或run()方法正常执行完毕后,或者线程抛出一个未捕获的异常(Exception)、错误(Error),线程就进入死亡状态。一旦进入死亡状态,线程将不再拥有运行的资格,也不能再转换到其他状态。

线程状态观察:对于线程生命周期的观察。

public class TestState {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
                System.out.println("****");
        });

        //观察状态:NEW
        Thread.State state = thread.getState();
        System.out.println(state);

        //观察状态:RUNNABLE
        thread.start();
        state = thread.getState();
        System.out.println(state);
        
        //只要线程不终止,就会一直输出:
        while (state != Thread.State.TERMINATED){
            try {
                Thread.sleep(100);
                state = thread.getState();
                System.out.println(state);		//TIMED_WAITING
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //观察状态:TERMINATED
        state = thread.getState();
        System.out.println(state);
    }
}

对于线程的生命周期,我们只要能够理解线程阻塞的概念、原因就基本没什么问题了。

线程关闭

对于线程中的线程关闭,线程不建议使用自带的stop()和destory()方法,此方法已经被废弃使用了,建议自己定义一个Boolean值,来控制线程的关闭,当这个变量置为false,则终止线程的运行。

public class TestStop implements Runnable {
    private boolean flag = true;
    private String name;

    public TestStop(String name) {
        super();
        this.name = name;
    }
    public void  stop(){     //定义终止线程的方法
        flag = false;
    }
    @Override
    public void run() {
        int s = 1;
        while (flag){
            System.out.println( name + "输出线程~~~~~" + s++);
        }
    }
    public static void main(String[] args) {
        TestStop test = new TestStop("线程A:");
        new Thread(test).start();       //开启线程
        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程开启:" + i);
        }
        test.stop();            //调用终止线程的方法,停止线程
        System.out.println("线程已停止。");
    }
}

线程休眠:sleep()

暂停线程执行常用的方法有sleep();该方法可以让当前正在执行的线程暂停一段时间,进入休眠等待状态。当前线程调用sleep(long millis)方法后,可以让正在运行的线程进入阻塞状态,直到休眠时间满了,进入就绪状态。

  • sleep(时间)指定当前线程阻塞的毫秒数;
  • sleep存在异常interruptedException;
  • sleep时间到达后线程进入就绪状态;
  • sleep可以模拟网络延迟,倒计时等;
  • 每一个对象都有一个锁,sleep不会释放锁;
  • 可以放大问题的发生性;
//模拟倒计时:
public class TestSleep{
    public static void main(String[] args) {
        TestSleep testSleep = new TestSleep();
        testSleep.sleep();
    }
    public void sleep(){
        int num = 10;
        while (true){
            try {
                Thread.sleep(1000);
                System.out.println("this is sleep" + num--);
                if (num < 0){
                    System.out.println("stop!");
                    break;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

线程礼让:yield()

暂停线程执行常用的方法有yield()方法;该方法和sleep()方法有点相似,都可以让当前正在运行的线程暂停,区别在于yield()方法不会阻塞该线程,它只是将线程转换成就绪状态,让系统的调度器重新调度一次。即可以让正在运行的线程直接进入就绪状态,让出CPU的使用权。

  • 让当前的线程暂停,但是不阻塞;
  • 将线程从运行状态转为就绪状态;
  • 礼让不是百分百成功的,只是提高优先度的成功几率,是否成功取决于CPU;
//测试礼让线程:
public class TestYield {
    public static void main(String[] args) {
        MyYield my = new MyYield();
        new Thread(my,"a:").start();
        new Thread(my,"b:").start();
    }
}
class MyYield implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "线程启动");
        Thread.yield();  //线程礼让
        System.out.println(Thread.currentThread().getName() + "线程关闭");
    }
}

合并线程:Join()

当在某个线程中调用其他线程的join()方法时,调用的线程将被阻塞,直到被join()方法加入的线程执行完成后它才会继续运行。 线程A在运行期间,可以调用线程B的join()方法,让线程B和线程A联合。这样,线程A就必须等待线程B执行完毕后,才能继续执行。

  • Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞;
  • 类似于生活中的插队;
//测试Join方法:
public class TestJoin implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            System.out.println("准备执行Join方法---" + i);
        }
    }
    public static void main(String[] args) {
        TestJoin testJoin = new TestJoin();
        Thread thread = new Thread(testJoin);
        thread.start();
        for (int i = 0; i < 1000; i++) {
            if (i == 200){
                try {
                    thread.join();      //穿插:在200时插入线程
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else{
                System.out.println("主程序输出---" + i);
            }
        }
    }
}

线程的优先级

在应用程序中,如果要对线程进行调度,最直接的方式就是设置线程的优先级。值得的注意的是,优先级越高的线程获得CPU调度的机会越大,而优先级越低的线程获得CPU调度的机会越小。并不是优先级高就一定先调度。

线程的优先级:优先级使用数字表示,范围为1~10。

  • MIN_PRIORITY: 表示线程最低优先级,值为1;
  • NORM_PRIORITY: 表示线程普通优先级,值为5;
  • MAX_PRIORITY: 表示线程最高优先级,值为10;

下面是获取线程信息的一些基本方法:

技术图片

//测试线程的优先级:
public class TestPriority {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName() + "--->" +
                Thread.currentThread().getPriority());
        MyPriority myPriority = new MyPriority();
        Thread t1 = new Thread(myPriority,"a");
        Thread t2 = new Thread(myPriority,"b");
        Thread t3 = new Thread(myPriority,"c");

        //设置优先级:int(1-10)	优先级越高越大几率被提 前调度
        t1.start();
        t2.setPriority(5);
        t2.start();
        t3.setPriority(10);
        t3.start();
    }
}
class MyPriority implements Runnable{
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "--->" +
                Thread.currentThread().getPriority());
    }
}

守护线程(daemon)

守护线程:将线程设置为守护线程后,只要用户线程执行完毕,守护线程也会随之结束。setDaemon()

  • 线程分为用户线程和守护线程;
  • 虚拟机必须确保用户线程执行完毕;
  • 虚拟机不用等待守护线程执行完毕;
  • 例如:后台记录操作日志,监控内存,垃圾回收等。。。。
//测试线程守护:
public class TestDaemon {
    public static void main(String[] args) {
        God god = new God();
        Thread thread = new Thread(god);
        //开启守护线程 setDaemon()--->目前进入无限循环
        thread.setDaemon(true); //默认为false,表示用户线程。
        thread.start();

        You you = new You();
        //开启用户线程,只要用户线程结束,守护线程也会随之结束:
        new Thread(you).start();
    }
}
class God implements Runnable{
    //定义一个守护线程,让其一直循环输出:
    @Override
    public void run() {
        while (true){
            System.out.println("This is Daemon!");
        }
    }
}
class You implements Runnable{
    //定义一个用户线程,该线程达到一定结果就会结束:
    @Override
    public void run() {
        for (int i = 0; i <= 36500; i++) {
            System.out.println("This is You Pro!");
        }
    }
}

线程同步与 Lock锁

在Java中,多个线程访问一个对象,会发生错误。确保不会出现这种错误,处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象。 这时候,我们就需要用到“线程同步”。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕后,下一个线程再使用。

当多个线程使用同一个共享资源时,可以将处理共享资源的代码放在一个用synchronized修的方法中或一个使用synchronized关键字来修饰的代码块中,这个代码块被称作同步代码块。

线程同步:(synchronized)形成条件---队列+锁-->保证线程的安全性

  • synchronized 方法
public  synchronized  void accessVal();

被synchronized修饰的方法在某一时刻只允许一个线程访问,访问该方法的其他线程都会发生阻塞,直到当前线程访问完毕后,其他线程才有机会执行该方法。

  • synchronized块
synchronized(lock)
   { 
   //允许访问控制的代码 
   }

上面的代码中,lock是一个锁对象,它是同步代码块的关键。当某一个线程执行同步代码块时,其它线程将无法执行当前同步代码块,会发生阻塞,等当前线程执行完同步代码块后,所有的线程开始抢夺线程的执行权,抢到执行权的线程将进入同步代码块,执行其中的代码。

//线程同步的例子
//两个人在银行取同一个账户里面的钱:
public class UnsafeBank {
    public static void main(String[] args) {
        //账户:
        Account account = new Account(150,"Cent");
        //第一个取款的人:
        Drawingmoney you = new Drawingmoney(account,50,"You");
        //第二个取款的人:
        Drawingmoney she = new Drawingmoney(account,100,"She");
        you.start();
        she.start();
    }
}
class Account{
     int money; //余额
     String name;    //卡名

    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}
class Drawingmoney extends Thread{
    Account account;        //账户
    int Dmoney;     //取款额度
    int Nmoney;     //手里额度

   public  Drawingmoney(Account account, int Dmoney, String name){
       super(name);
       this.account = account;
       this.Dmoney = Dmoney;
   }
   //取钱
    //synchronized 默认锁的是this.  它本身。
    @Override
    public  void run() {
       //synchronized代码块:
       synchronized (account){
           //判断卡里有没有钱:
           if (account.money - Dmoney < 0){
               System.out.println(this.getName() + "余额不够,无法取款!");
               return;
           }
           try {
               Thread.sleep(100);  //放大问题的发生性
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           //卡内余额 = 余额 - 取款金额
           account.money = account.money - Dmoney;
           //手里金额 = 当前金额 + 取款金额
           Nmoney = Nmoney + Dmoney;

           System.out.println(account.name + "余额为:"  + account.money);
           System.out.println(this.getName() + "手里金额为:" + Nmoney);
       }
    }
}

死锁: 多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。因此, 某一个同步块需要同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。

解决方法: 死锁是由于“同步块需要同时持有多个对象锁造成”的,要解决这个问题,就是:同一个代码块,不要同时持有两个对象锁。

//死锁:多个线程互相拥有对方需要的资源,然后形成僵持;
public class SynLock {
    public static void main(String[] args) {
        //两个对象使用同个资源,造成死锁
        Dohomework d1 = new Dohomework(0,"学生1");
        Dohomework d2 = new Dohomework(0,"学生2");
        d1.start();
        d2.start();
    }
}
class Pen{
}
class Note{

}
class Dohomework extends Thread{
    //需要的资源只有一份,所以使用static
    static Pen pen = new Pen();
    static Note note = new Note();

     int choice;  //选择
     String student;  //人物

   Dohomework(int choice, String student){
       this.choice = choice;
       this.student = student;
   }
    @Override
    public void run() {
        try {
            dohomewhork();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //互相持有对方的锁,需要拿到对方的资源:
   private void dohomewhork() throws InterruptedException {
        if (choice == 0 ){
            synchronized(pen){
                System.out.println(this.student + "获得:pen的锁");
                Thread.sleep(1000);
//               synchronized (note){      //同步中出现两个锁,会造成死锁
//                   System.out.println(this.student + "再获得:note的锁");
//               }
            }
            //把另外一个同步锁放在第一个同步锁外面就不会造成死锁:
            synchronized (note){
                System.out.println(this.student + "再获得:note的锁");
            }
        }else {
            synchronized (note){
                System.out.println(this.student + "获得:note的锁");
                Thread.sleep(2000);
//                synchronized (pen){     //同步中出现两个锁,会造成死锁
//                    System.out.println(this.student + "再获得:pen的锁");
//                }
            }
            synchronized (pen){
                System.out.println(this.student + "再获得:pen的锁");
            }
        }
    }
}

线程并发协作(生产者/消费者模式)

多线程环境下,我们经常需要多个线程的并发和协作;生产者/消费者模式: 生产者------缓冲区-------消费者

  • 生产者:负责生产数据的模块(这里模块可能是:方法、对象、线程、进程)。
  • 消费者:负责处理数据的模块(这里模块可能是:方法、对象、线程、进程)。
  • 缓冲区:生产者将生产好的数据放入“缓冲区”,消费者从“缓冲区”拿要处理的数据。

该模式的好处:

  • 有了缓冲区以后,生产者线程只需要往缓冲区里面放置数据,而不需要管消费者消费的情况;同样,消费者只需要从缓冲区拿数据处理即可,也不需要管生产者生产的情况。
  • 生产者不需要和消费者直接打交道,实现了“生产者线程”和“消费者线程”的分离。
  • 生产者生产数据慢时,缓冲区仍有数据,不影响消费者消费;消费者处理数据慢时,生产者仍然可以继续往缓冲区里面放置数据 。

线程并发协作(也叫线程通信),通常用于生产者/消费者模式,常用方法如下:

(都只能在同步方法或者同步代码块中使用,否则会抛出异常。)

技术图片

//测试:生产消费者模型---->利用缓冲区
//生产者,消费者,产品,缓冲区
public class TestPC {
    public static void main(String[] args) {
        SynContainer container = new SynContainer();
        new Productor(container).start();
        new Comsumer(container).start();
    }
}
//生产者:
class Productor extends Thread{
    SynContainer container;
    public Productor(SynContainer container) {
        this.container = container;
    }
//生产:
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            container.push(new Pencil(i));
            System.out.println("生产了ID为:" + i + "的铅笔");
        }
    }
}
//消费者:
class Comsumer extends Thread{
    SynContainer container;
    public Comsumer(SynContainer container) {
        this.container = container;
    }
//消费:
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            System.out.println("消费了ID为--->" + container.pop().id + "的铅笔");
        }
    }
}

//产品:
class Pencil{
    int id;
    public Pencil(int id) {
        this.id = id;
    }
}
//缓冲区:
class SynContainer{
    //需要一个容器大小:
    Pencil[] pencils = new Pencil[10];
    //定义一个计数器:
    int count = 0;

    //生产:
    public synchronized void push(Pencil pencil){
        while (count == pencils.length){
            //产品已满,同之消费者消费,生产者等待。。。。
            try {
                this.wait();    //让生产者等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果没有满就需要丢入产品:
        pencils[count] = pencil;
        count++;
        this.notifyAll();   //解除等待。可以消费了
    }

    //消费:
    public synchronized Pencil pop(){
        while (count == 0){
            //产品已空,通知生产者生产,消费者等待。。。。
            try {
                this.wait();    //消费者等待。
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果可以消费:
        count--;
        Pencil pencil = pencils[count];
        this.notifyAll();       //解除等待,通知生产者生产
        return pencil;
    }
}

到这里,多线程的基本知识都总结的差不多了,如果想要进一步了解多线程,可以学习JUC编程。

Java多线程

标签:下载文件   不同   返回   线程的生命周期   row   特殊情况   服务器   定义   notifyall   

原文地址:https://www.cnblogs.com/vxzx/p/14743296.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!