线程

发布时间 2023-08-15 10:53:35作者: 蝶凌天

线程

一、什么是线程?

在讲线程之前需要先知道什么是进程?。

进程: 是指内存中运行的应用程序(App:例如 QQ,微信,stream等),每一个进程都存在一个独立的内存空间,而每一应用程序都可以同时运行多个线程。

如下图:

线程: 线程是进程中的一个执行单元,负责当前进程中程序的执行,每个进程至少有一条线程,进程是可以有多个线程的,这种用用多个线程的,被称为多线程程序。

1、创建线程类

创建有两种

1.1、extends Thread

运行结果:

1.2、implements Runnable

这里new Thread的原因是:Bunnable里面没有start方法,只能通过 new Thread这个方法来实现开启多线程。(这只是其中的一种方法)

运行结果:

1.3、总结

(1)线程开启后运行的结果是随机的,与平时代码运行的结果不同

(2)通过运行结果我们可以看出,代码的运行是一段一段的运行,好像在my和‘ 欸嘿2‘之间来回跳转,而不是如同我们所认知的同时进行。由此可知,多线程是一条单线程根据相同的时间在不同的运行线上进行快速跳转以此来实现多线程。

(3)在java中一直都是单继承,多实现 ,因此Runnable作为线程,更适合资源共享,同时也容易实现解耦操作。

2、获取和设置线程名称

2.1、获取线程ID和线程名称

// 创建一个类继承Thread  线程类
public class MyThread  extends Thread{
 // 调用 Thread的 run 方法,这里重写run方法
 //  这里使用循环遍历的目的是为了能看清多线成的结果
    @Override
    public void run() {

        for (int i = 0; i < 20; i++) {
              // Thread.currentThread().getName() 获取当前线程的名称  --- 可作用在任何地方
              // this.getName() 获取当前线程的名称  ---该方法只能用在Thread子类下
            System.out.println(this.getName()+"欸嘿==="+i);
        }
    }
}

运行结果:

2.2、修改线程名称

1、调用线程对象的setName()方法

2、使用线程子类的构造方法赋值

public class My {

    public static void main(String[] args) throws InterruptedException {
       // 创建 Mythread线程类
        MyThread myThread = new MyThread();

      //  setName 对线程的命名
        myThread.setName("测试是否命名成功 yes");
        // start 开启线程的方法
        myThread.start();

        for (int i = 0; i < 20; i++) {
            System.out.println("myThread==="+i);

        }
    }
}
// 创建一个类继承Thread  线程类
public class MyThread  extends Thread{
 // 调用 Thread的 run 方法,这里重写run方法
 //  这里使用循环遍历的目的是为了能看清多线成的结果
    @Override
    public void run() {

        for (int i = 0; i < 20; i++) {
              // Thread.currentThread().getName() 获取当前线程的名称  --- 可作用在任何地方
              // this.getName() 获取当前线程的名称  ---该方法只能用在Thread子类下
            System.out.println(this.getName()+"欸嘿==="+i);
        }
    }
}

运行结果:

二、线程的常用方法

1、设置优先级 setPriority()

同优先级别的线程,采取的策略就是先到先服务,使用时间片策略。
如果优先级别高,被CPU调度的概率就高。级别从1到10,默认的级别是5。

实例: MyThread2:欸嘿2 MyThread:欸嘿

public class My {

    public static void main(String[] args) {
//        // 创建 Mythread线程类
        MyThread myThread = new MyThread();

        //  实现Runnable 的 线程类
        MyThread2 myThread2 = new MyThread2();
        //  Thread() ,括号里面填写的是实Runnable的线程类所创建的线程类 如上
        Thread thread = new Thread(myThread2);
//      开启线程

//        // start 开启线程的方法
        myThread.start();
        thread.start();
        // 设置优先级
        myThread.setPriority(1);
        thread.setPriority(10);
//        for (int i = 0; i < 10; i++) {
//            System.out.println("my2==="+i);
//        }
//        for (int i = 0; i < 10; i++) {
//            System.out.println("my1==="+i);
//        }
    }
}

运行结果:

这个方法仅仅是提高被调用的概率 因此也会出现如下结果:

2、线程插队方法 join()

谁调用这个方法,就会进入堵塞的状态,必须等待这个方法执行完才会往下执行

public class My {

    public static void main(String[] args) throws InterruptedException {
//        // 创建 Mythread线程类
        MyThread myThread = new MyThread();

        //  实现Runnable 的 线程类
        MyThread2 myThread2 = new MyThread2();
        //  Thread() ,括号里面填写的是实Runnable的线程类所创建的线程类 如上
        Thread thread = new Thread(myThread2);
//      开启线程

//        // start 开启线程的方法
        myThread.start();
        // join()方法 插队法
        myThread.join();

        thread.start();
    }
}

运行结果:

由结果可知:添加前运行结果为随机的。添加这个方法后给“诶嘿”后,执行完“欸嘿”后才执行”欸嘿“2

3、线程休眠方法 sleep(参数millis)

使正在执行的线程暂停,将CPU转让给别的线程,从而进入休眠等待状态。

public class MyThread  extends Thread{
 // 调用 Thread的 run 方法,这里重写run方法
 //  这里使用循环遍历的目的是为了能看清多线成的结果
    @Override
    public void run() {
        try {
            // 这里设置 休眠时间为1000毫秒
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        for (int i = 0; i < 10; i++) {
            System.out.println("欸嘿==="+i);

        }
    }
}

运行结果:

以上结果: 因为设置了休眠时间sleep 因此才会出这种情况。当休眠时间结束后就会出现一下结果。

4、设置伴随线程 setDaemon() (保镖)

将某一个子线程设置为主线程的伴随线程后,主线程停止后,子线程也会伴随着停止。

public class My {

    public static void main(String[] args) throws InterruptedException {
       // 创建 Mythread线程类
        MyThread myThread = new MyThread();
        //  实现Runnable 的 线程类
        MyThread2 myThread2 = new MyThread2();
        //  Thread() ,括号里面填写的是实Runnable的线程类所创建的线程类 如上
//        Thread thread = new Thread(myThread2);
        myThread.setDaemon(true);
      // start 开启线程的方法
        myThread.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("myThread==="+i);
            break;

        }
//        thread.start();
    }
}
// 创建一个类继承Thread  线程类
public class MyThread  extends Thread{
 // 调用 Thread的 run 方法,这里重写run方法
 //  这里使用循环遍历的目的是为了能看清多线成的结果
    @Override
    public void run() {

        for (int i = 0; i < 10; i++) {
            System.out.println("欸嘿==="+i);

        }
    }
}

运行结果:

结论: 虽然设置的myThread关闭了,但是欸嘿依旧处于运行状态,这是因为setDaemon() 虽然关闭了,但子线程依旧是运行了以一次相同的时间后才会随着主线程关闭而关闭。

5、 yield()

yield ()的作用是让步,它可以让当前线程由"运行状态"进入到"就绪状态",从而让其它具有相同优先等级的等待线程获取执行权.但是也有可能是当前线程又进入到"运行状态"继续运行!

// 创建一个类继承Thread  线程类
public class MyThread  extends Thread{
 // 调用 Thread的 run 方法,这里重写run方法
 //  这里使用循环遍历的目的是为了能看清多线成的结果
    @Override
    public void run() {

        for (int i = 0; i < 20; i++) {
            // 开启 yield()
            Thread.yield();
            System.out.println("欸嘿==="+i);
        }
    }
}

运行结果:

(1)让出CPU资源使mythread运行

(2)让出CPU资源后又再次占用

三、线程安全

public class My {

    public static void main(String[] args) throws InterruptedException {
       // 创建 Mythread线程类
        MyThread2 myThread2 = new MyThread2();
        new Thread( myThread2,"窗口1").start();
        new Thread( myThread2,"窗口2").start();
    }
}
// 创建一个类实现 Runnable    线程类
public class MyThread2 implements Runnable {
 // 调用 Runnable run 方法,这里重写run方法
 //  这里使用循环遍历的目的是为了能看清多线成的结果

    private int ticket = 100;
    @Override
    public void run(){
        while(ticket>0){
            System.out.println(Thread.currentThread().getName() + "售卖第" + ticket + "张票");
            ticket--;
        }
    }
}

运行结果:

由结果可知,窗口1都销售到第93张了,但是窗口2却卖到了第94张.

售票出现了问题,而这就是线称安全问题

如何解决线程安全问题?

要想解决线程俺去那问题,就需要优先考虑Java并发的三大基本特性:原子性,可见性,有序性.

原子性

原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,即不被中断操作,要不全部执行完成

可见性

当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值

有序性

程序执行的顺序按照代码的先后顺序执行,在多线程编程时就得考虑这个问题。


第一种自动锁: synchronized

public class My {

    public static void main(String[] args) throws InterruptedException {
       // 创建 Mythread线程类
        MyThread2 myThread2 = new MyThread2();
        new Thread( myThread2,"窗口1").start();
        new Thread( myThread2,"窗口2").start();


    }
}

public class MyThread2 implements Runnable {
 // 调用 Runnable run 方法,这里重写run方法
 //  这里使用循环遍历的目的是为了能看清多线成的结果

    private int ticket = 100;
    @Override
    public void run(){
        while(true){
            // 自动锁 () ()括号里输入的共同的值
            synchronized (this){
                if(ticket>0){
                    ticket--;
                    System.out.println(Thread.currentThread().getName() + "还剩" + ticket + "张票");
                }else {
                    break;
                }
            }
        }
    }
}

运行结果:


使窗口1直接卖完

第二种手动锁: Lock

// 创建一个类实现 Runnable    线程类
public class MyThread2 implements Runnable {
    // 调用 Runnable run 方法,这里重写run方法
    //  这里使用循环遍历的目的是为了能看清多线成的结果

    private int ticket = 100;
    // 创建手动锁
    private Lock l = new ReentrantLock();//创建手动锁对象

    @Override
    public void run() {
        while (true) {
            // 自动锁 () ()括号里输入的共同的值
//            synchronized (this) {
//                if(ticket>0){
//                    ticket--;
//                    System.out.println(Thread.currentThread().getName() + "还剩" + ticket + "张票");
//                }else {
//                    break;
//                }
//            }
                l.lock();
                if (ticket > 0) {
                    ticket--;
                    System.out.println(Thread.currentThread().getName() + "还剩" + ticket + "张票");
                } else {
                    break;
                }
            }

        }
    }

运行结果:
)

使窗口2直接卖完