JavaSE之多线程

程序、进程、线程的理解

1.程序(program)

概念:是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码静态对象

2.进程(process)

概念:程序的一次执行过程,或是正在运行的一个程序

说明:进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域

3.线程(thread)

概念:进程可进一步细化为线程,是一个程序内部的一条执行路径

说明:线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小

在这里插入图片描述
在这里插入图片描述

每个线程,拥有自己独立的:栈、程序计数器(pc)

多个线程,共享同一个进程中的结构:方法区、堆

单核cpu和多核cpu的理解

​ 单核其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。
如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)
​ 一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc() 垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。

并行与并发

​ 并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事

​ 并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事

创建多线程的四种方式

1.继承Thread类的方式

start()作用:
1.启动当前线程
2.运行当前线程run()
注意:
3.**不能直接调用run()**来启动线程,这样只是单线程,不是多线程
4.再启动一个线程时,不能让已经start()的线程再执行,会报illegalThread异常,需要重新创建一个线程子类的对象

public class ThreadTest {
    public static void main(String[] args) {
        //3.创建Thread类的子类的对象
        MyThread thread = new MyThread();
        //4.通过此对象调用start()
        thread.start();//线程1
//        thread.run();
//        thread.start();
        MyThread thread1 = new MyThread();
        thread1.start();//线程3

        for (int i=0;i<100;i++){//线程2
            if(i%2==0){
                System.out.println(i+"***main***");
            }
        }
        new Thread(){//还可以用匿名子类匿名对象的方法来使用多线程
            @Override
            public void run() {
                super.run();
            }
        }.start();
    }
}
//1.创建一个继承于Thread类的子类
class MyThread extends Thread {
    //2.重写Thread类的run()-->将此线程执行的操作声明在run()中
    public void run(){
        for(int i=0;i<100;i++){
            if(i % 2 == 0){
                System.out.println(i);
            }
        }
    }
}

2.实现Runnable接口

//1.创建一个实现了 Runnable接口的类
class MyThread1 implements Runnable{
    @Override
//2.实现类去实现Runnable中的抽象方法:run()
    public void run() {
        for(int i=0;i<100;i++){
            if(i%2 ==0){
                System.out.println(i);
            }
        }
    }
}
public class ThreadTest1 {
    public static void main(String[] args) {
//        3.创建实现类的对象
        MyThread1 myThread1 = new MyThread1();
//        4.将此对象作为参数传递到 Thread 类的构造器中,创建Thread类的对象
        Thread thread = new Thread(myThread1);
        thread.setName("线程二");
//        5.通过Thread类对象来调用start()
//          5.1启动线程
//          5.2调用当前线程的run(),但由于是实现并不是继承,不是方式一那种调用重写的run()
//             通过查看源码可以知道 Thread的构造器形参是 Runnable类型的,
//             而步骤三创建的实现类的对象作为实参放进Thread构造器里,
//             当调用start()时会启动Thread类中的run方法,转而调用到形参的run(),也就是实现类的对象的run()
        thread.start();
    }
}

两种方式的对比

开发中:优先选择实现Runnable接口的方式
原因:
1.该方式没有类的单继承性的局限性
2.该方式更适合来处理多个线程有共享数据的情况(第一种方式想要共享数据需要声明static)

联系:public class Thread implements Runnable(第一种方式也同样实现了Runnable)
相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中

3.实现callable接口

如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?

  1. call()可以有返回值的。
  2. call()可以抛出异常,被外面的操作捕获,获取异常的信息
  3. Callable是支持泛型的
//1.创建一个实现Callable的实现类
class NumThread implements Callable{
    //2.实现call方法,将此线程需要执行的操作声明在call()中
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            if(i % 2 == 0){
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}

public class ThreadNew {
    public static void main(String[] args) {
        //3.创建Callable接口实现类的对象
        NumThread numThread = new NumThread();
        //4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
        FutureTask futureTask = new FutureTask(numThread);
        //5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
        new Thread(futureTask).start();

        try {
            //6.获取Callable中call方法的返回值
            //get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
            Object sum = futureTask.get();
            System.out.println("总和为:" + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

}

4.线程池

好处:

1.提高响应速度(减少了创建新线程的时间)

2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)

3.便于线程管理
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止

class NumberThread implements Runnable{

    @Override
    public void run() {
        for(int i = 0;i <= 100;i++){
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

class NumberThread1 implements Runnable{
    @Override
    public void run() {
        for(int i = 0;i <= 100;i++){
            if(i % 2 != 0){
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

public class ThreadPool {
    public static void main(String[] args) {
        //1. 提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;//向下转型
        //设置线程池的属性
//        System.out.println(service.getClass());
//        service1.setCorePoolSize(15);
//        service1.setKeepAliveTime();
        //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(new NumberThread());//适合适用于Runnable
        service.execute(new NumberThread1());//适合适用于Runnable

//        service.submit(Callable callable);//适合使用于Callable
        //3.关闭连接池
        service.shutdown();
    }

}

线程中的常用方法

* 1.start():启动当前线程,调用当前线程的run()
* 2.run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
* 3.currentThread():静态方法,返回执行当前代码的线程
* 4.getName():获取当前线程的名字
* 5.setName():设置当前线程的名字
*
* 6.yield():释放当前cpu的执行权,即使释放了,也不一定就执行其他线程
* 7.join():在线程a中调用线程b的join(),则a会阻塞,直到b执行完
* 8.stop():已过时,当执行此方法时,强制结束当前线程。
* 9.sleep(long millitime):让当前线程睡眠指定的millitime毫秒,即这段时间阻塞
* 10.xxx.isAlive():判断当前线程是否存活

线程的优先级

* 1. MAX_PRIORITY:10
* 2. MIN_PRIORITY:1
* 3. NORM_PRIORITY:5 -->默认优先级
* 4.如何获取和设置优先级
*    getPriority():获取线程的优先级
*    setPriority(int p):设置线程的优先级
*    说明:设置了高优先级的线程不一定比低优先级的线程先执行完,只是从概率上来说高的优先于低的执行

线程的生命周期

在这里插入图片描述

  1. 状态:五种

    方法:对应状态转换使用的方法

  2. 关注:状态的改变导致哪些方法的执行;主动调用某些方法引起状态的改变

  3. 线程的最终状态都是死亡

线程的同步机制

背景

​ 例子:创建三个窗口卖票,总票数为100张,使用实现Runnable接口的方式

  • 1.问题:卖票过程中,出现了重票、错票 -->出现了线程的安全问题
  • 2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票
  • 3.如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。

解决方法(利用同步机制)

方式一:同步代码块

synchronized(同步监视器){
        //需要被同步的代码
     }

说明:
1.操作共享数据的代码,即为需要被同步的代码。
–>不能包含代码多了,也不能包含代码少了(关键!)。
2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
3.同步监视器,俗称:锁。 任何一个类的对象 ,都可以充当锁。
要求:多个线程必须要共用 同一把锁
4.在实现Runnable接口创建多线程的方式中,我们可以考虑使用 this 充当同步监视器。(当前方法对应类对应的对象)
5.在继承Thread类创建多线程的方式中,慎用 this 充当同步监视器(因为继承的这种方式需要创建多个类的对象,而this无法指代是哪个对象),考虑使用当前类充当同步监视器。

方式二:同步方法

  1. 同步方法仍然涉及到同步监视器,只是不需要显式声明。
  2. 非静态的同步方法,同步监视器是:this
    静态的同步方法,同步监视器是:当前类本身
class Windows3 implements Runnable{
    private int ticket=100;
    @Override
    public void run() {
        while(true){//这个结构是无限循环,需要break来跳出循环
            show();
            if(ticket==0){
                break;
            }
        }
    }
  //同步方法,需要用synchronized修饰,同一时刻只允许一个线程进入处理,结束出来下一个线程才能进去
    public synchronized void show(){//同步监视器:this
        if(ticket >0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
            ticket--;
        }
    }
}
public class WindowTest3 {
    public static void main(String[] args) {
        Windows3 w = new Windows3();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

方式三:lock锁

class Window implements Runnable{    private int ticket = 100;    //1.实例化ReentrantLock    private ReentrantLock lock = new ReentrantLock();//关键    @Override    public void run() {        while(true){            try{                               lock.lock();//2.调用锁定方法lock()                if(ticket > 0){                    try {                        Thread.sleep(100);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket);                    ticket--;                }else{                    break;                }            }finally {                lock.unlock();//3.调用解锁方法:unlock()            }        }    }}public class LockTest {    public static void main(String[] args) {        Window w = new Window();        Thread t1 = new Thread(w);        Thread t2 = new Thread(w);        Thread t3 = new Thread(w);        t1.setName("窗口1");        t2.setName("窗口2");        t3.setName("窗口3");        t1.start();        t2.start();        t3.start();    }}
  1. 面试题:synchronized 与 Lock的异同?
    相同:二者都可以解决线程安全问题

    ​ 不同:

    ​ synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器(自动的)
    ​ Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())(手动的,更灵活)

  2. 优先使用顺序:
    Lock -->同步代码块(已经进入了方法体,分配了相应资源) --> 同步方法(在方法体之外)

面试题:如何解决线程安全问题?有几种方式,同步机制解决,四种

利弊

​ 同步的方式,解决了线程的安全问题。—好处

​ 操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。 —局限性

单例模式懒汉式的线程安全

public class BankTest {    private BankTest(){    }    private static BankTest instance = null;    public static BankTest getInstance(){        if(instance == null){//双重检查,第一层检查。若已经创建了对象,后面有多个线程再进来时便不需要再进入,直接return                        synchronized (BankTest.class){//第二层检查。第一次多个线程进来时对象还未创建,因此需要同步来开启线程安全                  if (instance ==null){                    instance = new BankTest();                }            }        }return instance;    }}

死锁

  1. 死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
  2. 说明:
    1)出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
    2)我们使用同步时,要避免出现死锁。
public class ThreadTest {    public static void main(String[] args) {        StringBuffer s1 = new StringBuffer();        StringBuffer s2 = new StringBuffer();        //线程一        new Thread(){            @Override            public void run() {                synchronized (s1){//起初先握住s1z                    s1.append("a");                    s2.append("1");                    try {                        Thread.sleep(100);	//在睡眠的这段时间里,移交运行权给到另一个线程,此时另一个线程在握住s2这把锁之后也睡眠了,运行权再次回到原线程,但下一个同步问题需要s2这把锁,但却因为另一个线程握住s2而并未松开导致出现死锁                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    synchronized (s2){                        s1.append("b");                        s2.append("2");                        System.out.println(s1);                        System.out.println(s2);                    }                }            }        }.start();        //线程二        new Thread(new Runnable() {            @Override            public void run() {                synchronized (s2){                    s1.append("c");                    s2.append("3");                    try {                        Thread.sleep(100);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    synchronized (s1){                        s1.append("d");                        s2.append("4");                        System.out.println(s1);                        System.out.println(s2);                    }                }            }        }).start();    }}

线程通信

涉及到的三个方法

wait()

​ 一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。(关键在于释放同步监视器)

notify()

​ 一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。

notifyAll()

​ 一旦执行此方法,就会唤醒所有被wait的线程。

说明

  1. 三个方法只能在 同步代码块或同步方法 中使用。(不能在lock()中使用)
  2. 三个方法的调用者必须是同步代码块或同步方法中的同步监视器。(是同步监视器调用的),否则,会出现IllegalMonitorStateException异常
  3. 三个方法是定义在java.lang.Object类中。(因为同步监视器可以是任何类的对象,因此这三个方法必须定义在object类中,才能让任何类的对象随便调用)

面试题:sleep() 和 wait()的异同?

​ 1.相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。

​ 2.不同点:
​ 1)两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
​ 2)调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中
​ 3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁(同步监视器),wait()会释放锁。
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值