0


多线程--知识概念

多线程

1.什么是进程?

进程是计算机中的程序关于某数据集合上的一次运行活动,是系统资源分配和调度的基本单位,是操作系统结构的基础。每个程序有一个独立的进程,进程之间可以相互独立存在。

是一个正在执行中的程序,每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。

2.什么是线程?

线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。就是进程中的一个独立控制单元,线程在控制着进程的执行。一个进程中至少有一个线程。

3.进程和线程之间有什么区别?

进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。(进程是资源分配的最小单位)

线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位)

4.多线程

4.1、什么是多线程?

多线程是指同一个时间内多个顺序流在执行。即一个进程中不只有一个线程。

4.2、为什么要用多线程?

  • 为了能更好了利用CPU的资源,如果只有一个线程,则第二个线程必须等第一个任务结束后才能进行,如果使用多线程在主程序执行任务的同时可以执行其他任务,而不需要等待;
  • 进程之间不能进行共享数据,但是线程可以;
  • 系统创建进程需要为该进程重新分配系统资源,创建线程的代价比较小;
  • Java语言内置了多线程功能支持,简化了Java多线程编程。

4.3、线程的生命周期

  • 新建:从新建一个线程对象到程序start()这个线程之间的状态,都是新建线程;
  • 就绪:线程对象调用start()方法后,就处于就绪状态,等到JVM里的线程调度器的调度;
  • 运行:就绪状态下的线程得到CPU资源后就可以执行run(),此时的线程就处于运行状态,运行状态的线程可以变为就绪、阻塞及死亡三种状态;
  • 等待/阻塞/睡眠:在一个线程执行了sleep(睡眠)、suspend(挂起)等方法后悔失去所占有的资源,从而进入阻塞状态,在睡眠结束后重新进入就绪状态;
  • 终止:run()方法完成后或发生其他终止条件就会切换到终止状态。

4.4、用java.lang.Thread类实现多线程

如何用代码实现多线程,我们先通过继承Thread类来实现

public class ThreadTest{
    public static void main(String[] args) {
        //创建两个线程
        ThreadDemo t1=new ThreadDemo("thread1");
        ThreadDemo t2=new ThreadDemo("thread2");
        //start()启动
        t1.start();
        t2.start();
        //主线程
        for (int i = 0; i < 5; i++) {
            System.out.println("main:run"+i);
        }
    }
}
//继承Thread类
class ThreadDemo extends Thread{
    //设置线程的名称
    ThreadDemo(String name){
        super(name);
    }
    //重写run方法
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(this.getName()+":run"+i);
        }
    }
}

运行结果如下

我们可以看出是主线程先运行玩,然后再到t1和t2线程争抢CPU,t1和t2争抢CPU的概率是相同的。


用Runnable接口实现

public class ThreadTes2 {
    public static void main(String[] args) {
        RunTest rt = new RunTest();
        //建立线程对象
        Thread t1 = new Thread(rt);
        Thread t2 = new Thread(rt);
        //开启线程并调用run方法。
        t1.start();
        t2.start();
    }
}
​
//用接口实现Runnable
class RunTest implements Runnable{
    private int tick = 10;
    //覆盖Runnable接口中的run方法,并将线程要运行的代码放在该run方法中。
    public void run(){
        while (true) {
            if(tick > 0){
                System.out.println(Thread.currentThread().getName() + "..." + tick--);
            }
        }
    }
}

运行的结果如下

我们可以看到t1线程和t2线程谁抢占到了处理机谁就运行程序,最后出来的结果可能t1线程运行的多也可能t2线程运行的多。


通过Callable和Future创建线程

public class ThreadTest3 {
    public static void main(String[] args) {
        CallableTest ct = new CallableTest();                   //创建对象
        FutureTask<Integer> ft=new FutureTask<Integer>(ct);         //使用FutureTask包装CallableTest对象
        for (int i = 0; i < 100; i++) {
            //输出主线程
            System.out.println(Thread.currentThread().getName()+"主线程的i为:"+i);
            if(i==30){
                Thread td=new Thread(ft,"子线程");
                td.start();
            }
        }
        try{
            System.out.println("子线程的返回值为:"+ft.get());
        }catch (InterruptedException e){
            e.printStackTrace();
        }catch (ExecutionException e){
            e.printStackTrace();
        }
    }
}
class CallableTest implements Callable<Integer>{
​
    @Override
    public Integer call() throws Exception {
        int i=0;
        for (; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"..."+i);
        }
        return i;
    }
}
​

结果如下:

我们可以看到,我们设定的代码是主进程在i为30时开启子线程,但是子线程是在i=43时才运行,并且是与主线程交叉进行,说明子线程和主线程在争夺CPU资源。

4.5、三种方法的优缺点

继承Thread方法:线程代码存放在Thread子类的run()方法中。

优势:编写简单,可直接用this.getname()获取当前线程。

劣势:已经继承了Thread类,无法再继承其他类。


实现Runnable接口:线程代码存放在Runnable接口的run()方法中。

优势:避免了单继承的局限性,多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。

劣势:比较复杂,访问线程必须使用Thread.currentThreaad()方法,无返回值。


实现Callable:

优势:有返回值,避免了单继承的局限性,多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。

劣势:比较复杂,访问线程必须使用Thread.currentThreaad()方法,无返回值。

4.6、线程状态管理

1、线程睡眠---sleep

线程睡眠的原因:线程执行速度太快,或需要强制执行到下一个线程

线程睡眠的方法(两个):sleep(long millis)在指定的毫秒数内让正在执行的线程休眠。

            sleep(long millis,int nanos)在指定的毫秒数加指定的纳秒数内让正在执行的线程休眠。

sleep代码:

public class ThreadSleepTest {
    public static void main(String[] args) {
        CountDown countDown=new CountDown();
        Thread t1 = new Thread(countDown);
        t1.start();
    }
}
​
class CountDown  implements Runnable{
    int time = 10;
​
    @Override
    public void run() {
        while (true) {
            if(time>=0){
                System.out.println(Thread.currentThread().getName() + ":" + time--);
                try {
                    Thread.sleep(1000);       //睡眠时间为1秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

每隔一秒就会打印一次,结果如下:

扩展:Java线程调度是Java多线程的核心,只有良好的调度,才能充分发挥系统的性能,提高程序的执行效率。但是不管程序员怎么编写调度,只能最大限度的影响线程执行的次序,而不能做到精准控制。因为使用sleep方法之后,线程是进入阻塞状态的,只有当睡眠的时间结束,才会重新进入到就绪状态,而就绪状态进入到运行状态,是由系统控制的,我们不可能精准的去干涉它,所以如果调用Thread.sleep(1000)使得线程睡眠1秒,可能结果会大于1秒。

2、线程让步---yield

线程让步和sleep方法类似,也是Thread类中的一个静态方法,可以让正在执行的进程停止,但是yield不会进入阻塞状态,而是直接进入就绪状态。

代码测试:

public class ThreadYieldTest {
    public static void main(String[] args) {
        YieldTest yieldTest=new YieldTest();
        Thread t1 = new Thread(yieldTest,"张三吃完还剩");
        Thread t2 = new Thread(yieldTest,"李四吃完还剩");
        Thread t3 = new Thread(yieldTest,"王五吃完还剩");
        t1.start();
        t2.start();
        t3.start();
    }
​
}
​
class YieldTest implements Runnable{
​
    @Override
    public void run() {
        int count = 20;
        while(true){
            if(count > 0) {
                System.out.println(Thread.currentThread().getName() + count-- + "个瓜");
                if (count % 2 == 0) {
                    Thread.yield();        //线程让步
                }
            }
        }
    }
}

3、线程合并---join

当一个线程执行join方法后,其他的线程必须等待这个线程完成后才能执行。

join方法可以用来临时加入线程执行。

代码如下:

public class ThreadJoinTest {
    public static void main(String[] args) throws InterruptedException {
        JoinTest jt=new JoinTest();
        Thread t1 =new Thread(jt,"张三吃完剩下");
        Thread t2 =new Thread(jt,"李四吃完剩下");
        Thread t3 =new Thread(jt,"王五吃完剩下");
​
        t1.start();
        t1.join();
        t2.start();
        t3.start();
​
    }
}
​
class JoinTest implements Runnable{
    int count = 20;
    @Override
    public void run() {
        while(true)
        {
            if(count>0){
                System.out.println(Thread.currentThread().getName()+count--+"个瓜");
            }
        }
    }
}

4、停止线程

原stop方法因为有缺陷已经停用了,那么我们该如何控制停止线程呢?其实只需要让run()方法中的循环停止就行,我们可以设置一个布尔型的值来判断什么时候该停止,具体代码如下

public class ThreadStopTest {
    public static void main(String[] args) {
        int num = 0;
        Stoptest st=new Stoptest();
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(st);
        //设置主线程执行50次,执行结束之后停止线程
        while (true) {
            if(num++ == 50){
                st.flagchange();
                break;
                            }
            System.out.println(Thread.currentThread().getName() + "..." + num);
                    }
​
    }
}
​
class Stoptest implements Runnable{
    private boolean flag = true;
    @Override
    public void run() {
        while(flag){
            System.out.println(Thread.currentThread().getName()+"stop run");
        }
    }
    public void flagchange(){
        flag=false;
    }
}

5、设置优先级

每个线程执行时都会有一个优先级的属性,通过查看源码我们可以看到优先级的级别如下

/**
     * The minimum priority that a thread can have.
     */
public final static int MIN_PRIORITY = 1;
​
/**
     * The default priority that is assigned to a thread.
     */
public final static int NORM_PRIORITY = 5;
​
/**
     * The maximum priority that a thread can have.
     */
public final static int MAX_PRIORITY = 10;

简单的理解,就是优先级高的先执行,优先级低的后执行,如果在创建过程中没有设置优先级,则默认为NORM_PRIORTY。

6、线程同步与锁

为什么要进行线程同步?

在java中允许多线程并发执行,当多个线程同时操作一个可共享资源时(如对其进行CURD操作),则会导致数据不准确,而且相互之间产生冲突。所以加入同步锁来避免一个线程在还没有完成操作之前就被其他线程调用,从而保证了变量的唯一性和准确性。

不同步会出现的问题?

用下面的代码演示

public class SynTest {
    public static void main(String[] args) {
        MySyn  syn = new MySyn();
        Thread t1 = new Thread(syn,"线程1输出:");
        Thread t2 = new Thread(syn,"线程2输出:");
        Thread t3 = new Thread(syn,"线程3输出:");
        t1.start();
        t2.start();
        t3.start();
    }
}
​
class MySyn  implements Runnable{
    int count = 10;
    @Override
    public void run() {
        while(true){
            if(count>0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+" "+count--);
            }
        }
    }
}

查看结果,会发现发生了数据冲突,甚至最后还会输出-1

同步方法1:

同步函数:就是用synchronize关键字修饰的方法。因为每个java对象都有一个内置锁,当用synchronize关键字修饰方法时内置锁会保护整个方法,而在调用该方法之前,要先获得内置锁,否则就会处于阻塞状态。

代码如下

public class SynTest {
    public static void main(String[] args) {
        MySyn  syn = new MySyn();
        Thread t1 = new Thread(syn,"线程1输出:");
        Thread t2 = new Thread(syn,"线程2输出:");
        Thread t3 = new Thread(syn,"线程3输出:");
        t1.start();
        t2.start();
        t3.start();
    }
}
​
class MySyn  implements Runnable{
    int count = 10;
    @Override
    synchronized public void run() {
        while(true){
            if(count>0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+" "+count--);
            }
        }
    }
}

同步方法2:

同步代码块:就是拥有synchronize关键字修饰的语句块,被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。

代码如下

public class SynTest {
    public static void main(String[] args) {
        MySyn  syn = new MySyn();
        Thread t1 = new Thread(syn,"线程1输出:");
        Thread t2 = new Thread(syn,"线程2输出:");
        Thread t3 = new Thread(syn,"线程3输出:");
        t1.start();
        t2.start();
        t3.start();
    }
}
​
class MySyn  implements Runnable{
    int count = 10;
    @Override
     public void run() {
        while(true) {
            synchronized (this) {
                if (count > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " " + count--);
                }
            }
        }
    }
}

结果如下

标签: java 面试

本文转载自: https://blog.csdn.net/m0_56837353/article/details/125546098
版权归原作者 不想敲代码想玩 所有, 如有侵权,请联系我们删除。

“多线程--知识概念”的评论:

还没有评论