Java多线程总结

用多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现。说这个话其实只有一半对,因为反应“多角色”的程序代码,最起码每个角色要给他一个线程吧,否则连实际场景都无法模拟,当然也没法说能用单线程来实现:比如最常见的“生产者,消费者模型”。

多线程:指的是这个程序(一个进程)运行时产生了不止一个线程
并行与并发:

  • 并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
  • 并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。

单线程会按照代码的调用顺序进行执行
多线程中,main()方法和MyThread类的run()方法可以同时运行.

继承Thread类创建多线程


public class Thread1 {
    public static void main(String []args){
        MyThread myThread = new MyThread();
        myThread.start();
        while(true){
            System.out.println("Main函数运行");
        }
    }
}
class MyThread extends Thread{
    public void run(){
        while(true){
            System.out.println("Thread函数运行");
        }
    }
}
//结果是Main函数运行和Thread函数运行交替打印.




实现Runnable接口创建多线程

java中,如果一个类继承某个父类就无法再继承Thread类,构造方法Thread(Runnable target),提供Runnable接口,只有一个run()方法…


public class Thread1 {
    public static void main(String []args){
        MyThread myThread = new MyThread(); //创建MyThread的实例对象
        Thread thread = new Thread(myThread);   //创建线程对象
        thread.start(); //开启线程
        while(true){
            System.out.println("Main函数运行");
        }
    }
}
class MyThread implements Runnable{
    public void run(){
        while(true){
            System.out.println("Thread函数运行");
        }
    }
}



两种实现多线程方法的对比分析

public class Thread1 {
    public static void main(String []args){
        //MyThread myThread = new MyThread(); //创建MyThread的实例对象
        new MyThread().start();   //创建线程对象
        new MyThread().start();   //创建线程对象
        new MyThread().start();   //创建线程对象
        new MyThread().start();   //创建线程对象

    }
}
class MyThread extends Thread{
    private int tickets = 100;
    public void run(){
        while(true){
            if (tickets>0){
                Thread th = Thread.currentThread(); //获取当前线程.
                String th_name = th.getName();  //获取当前线程的名字..
                System.out.println(th_name + " 正在发售第 : " + tickets-- + "张票");
            }
        }
    }
}

运行结果:
在这里插入图片描述

从运行结果可以看出,每张票被打印了4次,出现这种现象的原因是四个线程没有共享100张票.
用户创建的第一个线程默认的名字为Thread-0,第二个默认为Thread-1,希望指定名称,可以调用setName(String
name)
一般来说,不同的线程是在独立地处理各自的资源,如果希望共用资源,可以调用Runnable,可以使用Thread(Runnabletarget,String name)设置名称. 这里以售票厅为例,多个售票窗口共同享用票数资源


public class Thread1 {
    public static void main(String []args){
        MyThread myThread = new MyThread(); //创建MyThread的实例对象
        new Thread(myThread,"A").start();   //创建线程对象
        new Thread(myThread,"B").start();   //创建线程对象
        new Thread(myThread,"C").start();   //创建线程对象
        new Thread(myThread,"D").start();   //创建线程对象

    }
}
class MyThread implements Runnable{
    private int tickets = 100;
    public void run(){
        while(true){
            if (tickets>0){
                Thread th = Thread.currentThread(); //获取当前线程.
                String th_name = th.getName();  //获取当前线程的名字..
                System.out.println(th_name + " 正在发售第 : " + tickets-- + "张票");
            }
        }
    }
}

实现Runnable接口相对继承Thread有如下好处.

  • 1.适合多个相同代码的线程去处理一个资源的情况,把线程同程序代码,数据有效分离,体现面对对象的设计思想.
  • 2.避免单继承带来的局限性.


线程的生命周期和状态转移.

当Thread对象创建完成时,线程的生命周期便开始了。当run()方法中代码正常执行完毕或者线程抛出一个未捕获的异常(Exception)或者错误(Error),线程的生命周期结束.
整个生命周期分成5个阶段,
1.新建状态(New)
2.就绪状态(Runnable)
3.运行状态(Running)
4.阻塞状态(Blocked)
5.死亡状态(Terminated)

1.新建状态: 创建当前对象,但是不能运行,仅仅时分配了内存
2.就绪状态调用start()方法后,线程进入就绪状态(称可运行状态),具备运行条件,需要等待系统调用以获得CPU使用权.
3.运行状态开始执行run()方法中的线程体。不可能一直处于运行状态,当使用完系统分配时间后,会自动返回就绪状态
4.阻塞状态执行耗时的输入/输出操作时,放弃对CPU使用权,进入阻塞状态

  • 试图获取某个对象同步锁,进入阻塞状态,当获取到其他线程持有的锁之后,会返回就绪状态.
  • 线程调用阻塞式的IO方法,进入阻塞状态,等IO方法返回,会返回就绪状态.
  • 调用某个对象的wait()方法,进入阻塞状态,使用notify()方法唤醒线程.
  • 调用sleep()方法时,进入阻塞状态,等线程睡眠时间到达,自动进入就绪状态
  • 调用另一个线程join()方法,需等新加入的线程结束后才能进入就绪状态.

如图所示
在这里插入图片描述



多线程同步

线程安全,售票案例中,极有可能碰到"意外"情况,如一张票被打印多次,或者出现票数为0甚至负数,这些都是线程安全问题.

//以下是没有使用同步锁,多个线程处理不同的对象.
public class Thread1 {
    public static void main(String []args){
        MyThread myThread = new MyThread(); //创建MyThread的实例对象
        new Thread(myThread,"A").start();   //创建线程对象
        new Thread(myThread,"B").start();   //创建线程对象
        new Thread(myThread,"C").start();   //创建线程对象
        new Thread(myThread,"D").start();   //创建线程对象

    }
}
class MyThread implements Runnable{
    private int tickets = 100;
    public void run(){
        while(tickets>0){
            try{
                Thread.sleep(10);   //线程休眠10ms
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                Thread th = Thread.currentThread(); //获取当前线程.
                String th_name = th.getName();  //获取当前线程的名字..
                System.out.println(th_name + " 正在发售第 : " + tickets-- + "张票");
            }
        }
    }
}

运行结果:
在这里插入图片描述
可以看出,出现了-1.-2张票,这是不应该有的情况.因为在售票程序中做了判断,只有当票号大于0才会进行售票。之所以出现负数的票号是因为多线程售票出现了安全问题.
解决方法如下:

多线程同步-限制某资源在同一时刻只能被一个线程访问.

public class Thread1 {
    public static void main(String []args){
        MyThread myThread = new MyThread(); //创建MyThread的实例对象
        new Thread(myThread,"A").start();   //创建线程对象
        new Thread(myThread,"B").start();   //创建线程对象
        new Thread(myThread,"C").start();   //创建线程对象
        new Thread(myThread,"D").start();   //创建线程对象

    }
}
class MyThread implements Runnable{
    private int tickets = 30;
    Object lock = new Object(); //定义任意一个对象,用作同步代码块的锁.
    //默认情况下锁的标志位为1,线程执行同步代码块,同时将锁对象的标志位置为0.当一个线程执行到这段同步
    //代码的时候,锁对象标志位为0,新线程发生阻塞,等待锁执行完,新线程才能执行同步代码块的代码.
    public void run(){
        while(true){
            synchronized (lock){
                try{
                    Thread.sleep(20);   //线程休眠10ms
                }catch (Exception e) {
                    e.printStackTrace();
                }
                if (tickets>0){
                    Thread th = Thread.currentThread(); //获取当前线程.
                    String th_name = th.getName();  //获取当前线程的名字..
                    System.out.println(th_name + " 正在发售第 : " + tickets-- + "张票");
                }else   break;
            }
        }
    }
}

运行结果下图:
在这里插入图片描述
可以看出,解决了不同线程共同处理同一资源对象.

方法前面同样可以使用synchronized关键字修饰,被修饰的方法称为同步方法,实现和同步代码块一样的功能.
synchronized 返回值类型 方法名 ([参数一,…])同步方法的锁时调用该方法的对象,也就是this指的对象.静态方法的锁是类所在的class对象



多线程通信.

Object类提供wait(),notify(),notifyAll()方法解决线程间通信问题.调用者都应该是同步锁的对象,否则抛出IllegalMonitorStateException异常

wait():使当前线程放弃同步锁进入等待,直到其他线程进入此同步锁,调用notify()方法唤醒该线程
notify():唤醒同步锁等待的第一个调用wait()方法的线程.

class Storage{
    private int [] cells = new int[10];
    private int inPos,outPos;   //存入时,取出时下标
    private int count ;
    public synchronized void put(int num){
        try{
            while(count == cells.length){
                this.wait();    //如果放入数据等于cells长度,等待
            }
            cells[inPos] = num;
            System.out.println("从cells["+inPos+"]放入数据"+cells[inPos]);
            inPos++;
            if (inPos==cells.length)    inPos=0;    //当在cells[9]放完数据后再从cells[0]开始
            count ++;	//每放一个数据count+1
            this.notify();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    public synchronized void get(){
        try{
            while(count == 0){
                this.wait();    //如果count为0,此线程等待.
            }
            int data = cells[outPos];
            System.out.println("从cells["+outPos+"]取出数据"+data);
            cells[outPos] = 0;  //取完,当前位置数据为0
            outPos++;
            if (outPos==cells.length)   outPos = 0;
            count--;	//取出一个元素count-1
            this.notify();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
class Input implements Runnable{
    private Storage st;
    private int num;
    Input(Storage st){
        this.st = st;
    }
    public void run(){
        while(true){
            st.put(num++);
            if (num==1000)  break;	//只看前1000个
        }	
    }
}
class Output implements Runnable{
    private Storage st;
    Output(Storage st){
        this.st = st;
    }
    public void run(){
        while(true){
            st.get();
        }
    }
}
public class Thread1{
    public static void main(String []args){
        Storage st = new Storage();	//创建数据存储类对象
        Input input = new Input(st);
        Output output = new Output(st);
        new Thread(input).start();开启新线程
        new Thread(output).start();
    }
}

运行结果:
在这里插入图片描述

提醒一下大家:main方法其实也是一个线程。在java中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。
在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个jVM实习在就是在操作系统中启动了一个进程。

由于笔者水平有限,所以有不足之处还请提出,大家互相学习.

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值