javaSE--6线程

一、多线程

1.线程和进程

进程:是一个应用程序

线程:是一个进程中的执行场景/执行单元

一个进程可以启动多个线程

2.对于java程序

当在DOS命令窗口中输入:java HelloWorld回车后,辉县启动JVM,而JVM就是一个进程,

JVM会再启动一个主线程调用main方法,同时再启动一个垃圾回收线程负责看护,回收垃圾

现在的java程序中至少有两个线程并发,一个是垃圾回收线程,一个是执行main方法的主线程

3.进程和线程的关系

例如:

阿里巴巴:进程

​ 马云:阿里巴巴的一个线程

​ 阿里前台:阿里巴巴的一个线程

京东:进程

​ 强东:京东的一个线程

​ 妹妹:京东的一个线程

进程可以看做公司,线程可以看做员工

注意:

进程A和进程B的内存独立不共享

在java语言中,线程A和线程B,堆内存和方法区内存共享,但是栈内存独立,一个线程一个栈
在这里插入图片描述

4.多线程并发

假设启动10个线程,会有10个栈空间,每个栈之间互不干扰,各自执行

多线程机制,目的是为了提高程序的处理效率

使用多线程机制之后,main方法结束,程序也有可能结束吗?

​ 答:main方法结束之时主线程结束,主栈空了,其他栈(线程)可能还在压栈、弹栈

什么是真正的多线程并发?

​ t1线程执行t1,t2执行t2,二者不会互相影响

对于单核CPU来说,真的可以做到真正的多线程并发吗?(对于多喝CPU,真正的多线程并发是没问题的)

​ 单核CPU表示只有一个大脑:不能做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉

​ 对于单核CPU,在某一时间点上实际上只能处理一件事情,但由于CPU处理速度极快,多个线程之间频繁切换执行,给人的感觉是:多个事情同时处理
在这里插入图片描述

//除了垃圾回收线程外,只要一个主线程
public class ThreadTest01 {
    public static void main(String[] args) {
        System.out.println("main begin");
        m1();
        System.out.println("main over");
    }

    private static void m1(){
        System.out.println("m1 begin");
        m2();
        System.out.println("m1 over");
    }

    private static void m2(){
        System.out.println("m2 begin");
        m3();
        System.out.println("m2 over");
    }

    private static void m3(){
        System.out.println("m3 execute!");
    }
}

一个栈中,自上而下的顺序依次逐行执行

5.实现线程的两种方式

第一种方式:

编写一个类,直接继承java.lang.Thread,重写run方法

怎么创建线程对象?new

怎么启动线程?调用线程对象的start()方法

start()方法的作用:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码人去完成后,瞬间结束

public class ThreadTest02 {
    public static void main(String[] args) {
        //main方法在主线程中运行
        //新建一个分支线程对象
        MyThread myThread = new MyThread();

        //启动线程
        //myThread.run();//不会启动线程,不会分配新的分支栈(单线程)
        //这段代码的任务只是为了开启一个新的栈空间,只要心的栈空间开出来,start()方法结束,线程启动成功
        //启动成功的线程会自动调用run方法,且run方法在分支栈的栈底部(压栈)
        //run方法在分支栈的栈底部,main方法在主栈的栈底部,run和main是平级的
        myThread.start();

        for (int i = 0;i < 1000;i++){
            System.out.println("主线程--->" + i);
        }
    }
}
//定义线程类
class MyThread extends Thread{
    @Override
    public void run() {
        //编写程序,这段程序运行在分支栈中
        for (int i = 0;i < 1000;i++){
            System.out.println("分支线程--->" + i);
        }
    }
}

第二种方式

编写一个类,实现java.lang.Runnable接口,实现run方法

public class ThreadTest03 {
    public static void main(String[] args) {
        //创建一个可运行的对象
        //MyRunnable r = new MyRunnable();
        //将可运行的嗯对象封装成一个线程对象
        //Thread t = new Thread(r);

        //合并代码
        Thread t = new Thread(new MyRunnable());
        //启动线程
        t.start();

        for (int i = 0;i < 100;i++){
            System.out.println("主线程--->" + i);
        }
    }
}
//这不是一个线程类,是一个可运行的类,它不是一个线程
class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0;i < 100;i++){
            System.out.println("分支线程--->" + i);
        }
    }
}

在这里插入图片描述
注意:第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承其他的类,更加灵活

匿名内部类

public class ThreadTest04 {
    public static void main(String[] args) {
        //创建线程对象,采用匿名内部类的方式
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0;i < 100;i++){
                    System.out.println("分支线程--->" + i);
                }
            }
        });

        //启动线程
        t.start();

        for (int i = 0;i < 100;i++){
            System.out.println("main线程--->" + i);
        }

    }
}

6.线程的生命周期

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

新建状态

就绪状态

运行状态

阻塞状态

死亡状态

二、线程功能

1.获取线程名字

(1)获取当前线程对象

Thread t = Thread.cuttentThread();

返回值t就是当前线程

(2)获取线程对象的名字

String name = 线程对象.getName();

(3)修改线程对象的名字

线程对象.setName(“线程名字”);

(4)默认线程名字

当线程没有设置名字时,默认的名字的规律?

​ Thread-0、Thread-1…

public class ThreadTest05 {
    public static void main(String[] args) {

        //这个代码出现在main方法中,所以当前线程就是主线程
        Thread currentThread = Thread.currentThread();
        System.out.println(currentThread.getName());//main
        //创建线程对象
        MyThread2 t = new MyThread2();
        //设置线程名字
        t.setName("tttt");
        //获取线程名字
        String tName = t.getName();
        System.out.println(tName);//若不设置线程名字,默认为Thread-0

        MyThread2 t2 = new MyThread2();
        t.setName("t2");
        System.out.println(t2.getName());//Thread-1
        t2.start();

        //启动线程
        t.start();
    }
}
class MyThread2 extends Thread{
    public void run(){
        for (int i = 0;i <100;i++){
            //当t1线程执行run方法,则这个当前线程是t1
            //当t2线程执行run方法,则这个当前线程是t2
            Thread currentThread = Thread.currentThread();
            System.out.println("分支线程-->" + i);
        }
    }
}

2.线程的sleep方法

static void sleep(long millis)

​ 1)静态方法:Thread.sleep(1000);

​ 2)参数是毫秒

​ 3)作用:让当前进程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其他线程使用

​ 这行代码出现在A线程中,A线程就会进入休眠

​ 4)Thread.sleep()方法,可以做到这种效果:

​ 间隔待定的时间,去执行一端待定的代码,每隔多久执行一次

public class ThreadTest06 {
    public static void main(String[] args) {
        //让当前线程进入休眠,睡眠5秒
        //当前线程是主线程
        /*try {
            Thread.sleep(1000*5);
        }catch (InterruptedException e){
            e.printStackTrace();
        }

        //执行这里的代码
        System.out.println("hello world!");*/

        for (int i = 0;i < 10;i++){
            System.out.println(Thread.currentThread().getName() + "--->" + i);

            //睡眠1s
            try{
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

关于sleep的面试题

public class ThreadTest07 {
    public static void main(String[] args) {
      	//创建线程对象
        MyThhread3 t = new MyThhread3();
        t.setName("t");
        t.start();

        //调用sleep方法
        try{
            t.sleep(1000*5);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
      //5秒后输出
        System.out.println("hello world!");
    }
}
class MyThhread3 extends Thread{
    public void run(){
        for(int i = 0;i < 10000;i++){
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
}

问题:

​ t.sleep(1000*5);这行代码会让线程t进入休眠状态吗?

答案:不会,因为sleep是静态方法,执行时会转换成Thread.sleep(1000*5);只会让当前线程进入休眠

3.终止线程的休眠

注意:不是中断线程的执行,是终止线程的睡眠

t.interrupt();//干扰

这种中断睡眠的方式依靠了java的异常处理机制

public class ThreadTest08 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable2());
        t.setName("t");
        t.start();

        //希望5s后,t线程醒来
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //终止t线程的睡眠
        t.interrupt();//干扰
    }
}
class MyRunnable2 implements Runnable{

    //重点:run()当中的异常不能throws,只能try catch
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "---> begin");
        //睡眠一年
        try {
            Thread.sleep(1000*60*60*24*365);
        } catch (InterruptedException e) {
            //打印异常
            e.printStackTrace();
        }

        //1年之后才会执行这行代码
        System.out.println(Thread.currentThread().getName() + "---> end");

        //调用doOther
        //doOther();
    }
    /*//其他方法可以throws
    public void doOther() throws Exception{
    }*/
}

4.强行终止线程的执行

t.stop()方法已过时,缺点:容易丢失数据,此方法是直接讲线程杀死,先称没有保存的数据将会丢失

合理的终止一个线程的执行

public class ThreadTest10 {
    public static void main(String[] args) {
        MyRunnable4 r = new MyRunnable4();
        Thread t = new Thread(r);
        t.setName("t");
        t.start();

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //终止线程
        //想要什么时候终止t的执行,就把标记修改为false,即可结束
        r.run = false;
    }
}
class MyRunnable4 implements Runnable{

    //打一个布尔标记
    boolean run = true;

    @Override
    public void run() {
        for (int i = 0;i < 10;i++){
            if (run){
                System.out.println(Thread.currentThread().getName() + "--->" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else {
                //终止当前线程
                return;
            }
        }
    }
}

使用布尔标记法进行终止线程的执行

三、线程调度(了解)

1.常见的线程调度模型

抢占式调度模型

​ 哪个线程的优先级较高,抢到的CPU时间片的概率就高

​ java采用的是抢占式调度模型

均分式调度模型

​ 平均分配CPU时间片,每个线程占有的CPU时间片时间长度一样

​ 平均分配,一切平等

2.线程调度的方法

1)实例方法

void setPriority(int newPriority)//设置线程的优先级
int getPriority()//获取线程优先级

​ 最低优先级:1

​ 默认优先级:5

​ 最高优先级:10

public class ThreadTest11 {
    public static void main(String[] args) {

        //设置主线程的优先级为1
        Thread.currentThread().setPriority(1);

        /*System.out.println("最高优先级" + Thread.MAX_PRIORITY);
        System.out.println("最低优先级" + Thread.MIN_PRIORITY);
        System.out.println("默认优先级" + Thread.NORM_PRIORITY);*/

        //获取当前线程对象,获取当前线程的优先级
        Thread currentThread = Thread.currentThread();
        //System.out.println(currentThread.getName() + "线程的默认优先级是:" + currentThread.getPriority());

        Thread t = new Thread((new MyRunnable5()));
        t.setPriority(10);
        t.setName("t");
        t.start();

        //优先级较高的,只是抢到的CPU时间片相对多一些
        for (int i = 0;i < 10000;i++){
            System.out.println(Thread.currentThread().getName() +"--->" + i);
        }

    }
}
class MyRunnable5 implements Runnable{

    @Override
    public void run() {
        //获取线程优先级
        //System.out.println(Thread.currentThread().getName() + "线程的默认优先级:" + Thread.currentThread().getPriority());
        for (int i = 0;i < 10000;i++){
            System.out.println(Thread.currentThread().getName() +"--->" + i);
        }
    }
}

2)让位方法

static void yield()//静态让位方法

​ 暂停当前正在执行的线程对象,并执行其他线程

yield()方法不是阻塞方法,让当前线程让位,让给其他线程使用

​ yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”

注意:在回到就绪状态后,有可能会再次抢到

public class ThreadTest12 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable6());
        t.setName("t");
        t.start();

        for (int i = 1;i <= 1000;i++){
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
}
class MyRunnable6 implements Runnable{

    @Override
    public void run() {
        for (int i = 1;i <= 1000;i++){
            //每100个让位1次
            if (i%100 == 0){
                Thread.yield();//当前线程暂停一下,让给主线程
            }
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
}

3)线程合并

void join()
class MyThread1 extends Thread{
  public void soSome(){
    MyThread2 t = new MyThread2();
    t.join();//当前线程进入阻塞,t线程执行,直到t线程结束,当前线程才可以继续
  }
}
class MyThread2 extends Thread{
  
}
public class ThreadTest13 {
    public static void main(String[] args) {
        System.out.println("main begin");

        Thread t = new Thread(new MyRunnable7());
        t.setName("t");
        t.start();

        //合并线程
        try {
            t.join();//t合并到当前线程中,当前线程受阻塞,t线程执行直到结束
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("main over");

    }
}
class MyRunnable7 implements Runnable{

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

四、线程安全(重点)

关于多线程并发环境下,数据的安全问题

为什么是重点?

​ 以后在开发中,项目都是运行在服务器中,而服务器已经将线程的定义,线程对象的创建、线程的启动等,都已经实现完了,这些代码不需要自己编写

重点:需要知道,我们编写的程序需要放到一个多线程的环境下运行,更需要关注的是这些数据在多线程并发的环境下是否安全

1.线程不安全的条件

什么时候数据在多线程并发的环境下会存在安全问题?

​ 条件1:多线程并发

​ 条件2:有共享数据

​ 条件3:共享数据有修改行为

满足以上3个条件后,就会存在线程安全问题

2.怎么解决线程安全问题

当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全问题,

怎么解决这个问题? 线程排队执行(不能并发)—这种机制叫做:线程同步机制

线程同步就是线程排队了,线程排队了就会牺牲一部分效率,数据安全第一位,只有数据安全后,才可以谈效率,数据不安全,就不会有效率

3.同步编程模型和异步编程模型

异步编程模型

​ 线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,—其实就是:多线程并发(效率较高)

同步编程模型

​ 线程t1和线程t2,在线程t1执行时,必须等t2线程执行结束,或者在t2线程执行时,必须等待t1线程执行结束,两个线程之间发生了等待—线程排队(效率较低)

4.synchronized

线程同步机制的语法是:

​ synchronized(){

​ //线程同步代码块

}

​ synchronized后面小括号中传的数据相当关键

这个数据必须是多线程共享的数据,才能达到多线程排队

()中写什么?

那要看用户想让哪些线程同步,

​ 假设t1,t2,t3,t4,t5有5个线程,只希望t1,t2,t3排队,t4,t5不排队,怎么办?

​ 需要在()中写一个t1,t2,t3共享的对象,而这个对象对于t4,t5来说不是共享的

不一定this,只要是多线程共享的对象就可以

synchronized(this){}—这种写法最好

在java语言中,任何一个对象都有“一把锁”,其实这把锁就是标记(只是把它叫做锁)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在实例方法上也可以使用synchronized

优点:

​ 代码较少,较节俭

缺点:

​ synchronized出现在实例方法上,一定锁的是this,不能是其他对象,所以这种方式不灵活

​ synchronized出现在实例方法上,表示整个方法都需要同步,可能会无故扩大同步的范围,导致程序的执行效率降低

若共享的对象是this,且需要同步的代码块是整个方法体,建议使用这种方式

synchronized的三种写法

①同步代码块

灵活

synchronized(线程共享对象){

​ 同步代码块;

}

②在实例方法上使用

表示共享对象一定是this,且同步代码块是整个方法快

③在静态方法上使用(排它锁)

表示找类锁

类锁永远只要1把,就算创建了100个对象,类锁也只有1把

对象锁:1个对象1把锁,100个对象100把锁

类锁:100个对象,也可能只是1把类锁

synchronized面试题

package exam1;

//面试题1:doOther方法执行时需要等待doSome方法的结束吗?
    //不需要,因为doOther方法没有synchronized
public class Exam01 {
    public static void main(String[] args) throws InterruptedException {
        MyClass mc = new MyClass();

        Thread t1 = new MyThread(mc);
        Thread t2 = new MyThread(mc);

        t1.setName("t1");
        t2.setName("t2");

        t1.start();
        Thread.sleep(1000);//这个睡眠的作用是:为了保证t1线程先执行
        t2.start();
        
    }
}

//线程
class MyThread extends Thread{
    private MyClass mc;
    public MyThread(MyClass mc){
        this.mc = mc;
    }
    public void run(){
        if (Thread.currentThread().getName().equals("t1")){
            mc.doSome();
        }
        if (Thread.currentThread().getName().equals("t2")){
            mc.doOther();
        }
    }
}

class MyClass{
    public synchronized void doSome(){
        System.out.println("doSome begin");
        try {
            Thread.sleep(1000*10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over");
    }
    public void doOther(){
        System.out.println("doOther begin");
        System.out.println("doOther over");
    }
}
//面试题2:doOther方法执行时需要等待doSome方法的结束吗?
    //需要,因为是同一把锁
class MyClass{
    public synchronized void doSome(){
        System.out.println("doSome begin");
        try {
            Thread.sleep(1000*10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over");
    }
    public synchronized void doOther(){
        System.out.println("doOther begin");
        System.out.println("doOther over");
    }
}
//面试题3:doOther方法执行时需要等待doSome方法的结束吗?
    //不需要,因为MyClass对象是两个,两把锁
MyClass mc1 = new MyClass();
MyClass mc2 = new MyClass();

Thread t1 = new MyThread(mc1);
Thread t2 = new MyThread(mc2);
//面试题4:doOther方法执行时需要等待doSome方法的结束吗?
    //需要,因为静态方法是类锁,不管创建了几个对象,类锁只有1把
class MyClass{
    //synchronized出现在静态方法上是找类锁
    public synchronized static void doSome(){
        System.out.println("doSome begin");
        try {
            Thread.sleep(1000*10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over");
    }
    public synchronized static void doOther(){
        System.out.println("doOther begin");
        System.out.println("doOther over");
    }
}

5.线程安全问题(重要)

java中的三大变量:

​ 实例变量:在堆中(堆只有1个)

​ 静态变量:在方法区中(方法区只有1个)

​ 局部变量:在栈中

以上三大变量中,局部变量永远不会存在线程安全问题—因为局部变量不共享(一个线程一个栈)

局部变量+常量:不会有线程安全问题

成员变量:可能会有线程安全问题

如果使用局部变量的话:

​ 建议使用:StringBuilder—因为局部变量不存在线程安全问题,选择StringBuilder,StringBuffer效率较低

ArrayList是非线程安全的

Vector是线程安全的

HashMap HashSet是非线程安全的

Hashtable是线程安全的

6.扩大同步范围

synchronized(act){

​ act.withdraw(money);

}

这种方法会扩大同步范围,使得效率更低

五、死锁

在这里插入图片描述
死锁:不出现异常,也不出现错误,程序一直僵持在那里,这种错误最难调试

面试官要求会写死锁代码,只优惠些,才会在以后的开发中注意死锁问题,因为死锁很难调试

六、解决线程安全问题

并不是优先选择synchronized,因为synchronized会让程序的执行效率降低,用户体验不好,系统的用户吞吐量降低,用户体验差,在不得以的情况下再选择线程同步机制

第一种方案

​ 尽量使用局部变量代替“实例变量和静态变量”

第二种方案

如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了(对象不共享,就不会存在数据安全问题了)

第三种方案

如果不能使用局部变量,对象也不能创建多个,此时只能选择synchronized线程同步机制了

七、线程其他内容

1.守护线程

java语言中线程分为两大类:

​ 一类是:用户线程

​ 一类是:守护线程(后台线程)

其中具有代表性的是:垃圾回收线程(守护线程)

守护线程特点:

一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束

注意:主线程main方法是一个用户线程

守护线程用在什么地方?
在这里插入图片描述

public class ThreadTest14 {
    public static void main(String[] args) {
        Thread t = new BakDataThread();
        t.setName("备份数据的线程");

        //启动线程之前,将线程设置为守护线程
        t.setDaemon(true);

        t.start();

        //主线程:主线程是用户线程
        for (int i = 0;i < 10;i++){
            System.out.println(Thread.currentThread().getName() + "--->" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class BakDataThread extends Thread{
    public void run(){
        int i = 0;
        //即使是死循环,但由于该线程是守护者,当用户线程结束,守护线程自动终止
        while(true){
            System.out.println(Thread.currentThread().getName() + "--->" + (++i));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

2.定时器

作用:间隔特定的时间,执行特定的程序

定时器:java.util.Timer,可以直接拿来用(目前开发很少用)

目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class TimerTest {
    public static void main(String[] args) throws ParseException {
        //创建定时器对象
        Timer timer = new Timer();
        //守护线程的方式
        //Timer timer = new Timer(true);

        //指定定时任务
        //timer.schedule(定时任务,第一次执行时间,间隔多久执行一次);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date firstTime = sdf.parse("2020-12-23 22:39:20");
        timer.schedule(null,firstTime,1000*10);
    }
}
//编写一个定时任务类
//结社这是一个记录日志的定时任务
class LogTimerTask extends TimerTask{

    @Override
    public void run() {
        //编写需要执行的任务
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String strTime = sdf.format(new Date());
        System.out.println(strTime + ":成功完成了一次数据备份!");
    }
}

3.实现线程的第三种方式:实现Callable接口

这种方式实现的线程可以获取线程的返回值,之前的两种方式无法获取线程返回值,因为run方法返回void

思考:

​ 系统委派一个线程去执行一个任务,该线程执行完任务后,可能会有一个执行结果,我们怎么才能拿到这个执行结果?

实现Callable接口

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class ThreadTest15 {
    public static void main(String[] args) throws Exception{
        //第一步:创建一个“未来任务类”对象
        //参数非常重要,需要给一个Callable接口实现类对象
        FutureTask task = new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception {//call()方法就相当于run方法,只不过有返回值

                //线程执行一个任务,执行之后可能会有一个执行结果
                //模拟执行
                System.out.println("call method begin");
                Thread.sleep(1000*10);
                System.out.println("call method end!");
                int a = 100;
                int b = 200;

                return a+b;//自动装箱
            }
        });
        //创建线程对象
        Thread t = new Thread(task);

        //启动线程
        t.start();

        //这是main方法,主线程中
        //在主线程中,怎么获取t线程的返回结果?
        //get()方法的执行会导致“当前线程阻塞”
        Object obj = task.get();
        System.out.println("线程执行结果:" + obj);

        //main方法这里的陈旭要想执行必须等get()方法的结束
        //而get()方法可能需要很久,因为get()方法是为了拿另一个线程的执行结果,另一个线程执行是需要时间的
        System.out.println("hello world!");
    }
}

优点:可以获取线程的执行结果

缺点:效率较低,在获取t线程结果时,档期啊线程受阻塞

4.关于Object类中的wait和notify方法(生产者和消费者模式)

①这两个方法不是线程对象的方法,是Java中任何一个java对象都有的方法,因为这两个方式Object类中自带

②不是通过线程对象调用:t.wait()—错 t.notify()----错

wait()方法的作用?

Object o = new Object();
o.wait();//会让当前线程进入等待状态

表示:让正在o对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止
在这里插入图片描述

notify()方法的作用?

Object o = new Object();
o.notify();

表示:唤醒正在o对象上等待的线程

还有一个notifyAll()方法:唤醒o对象上处于等到的所有线程

wait方法和notify方法建立在synchronized线程同步的基础上

重点:

o.wait()方法会让正在o对象上活动的当前线程进入等待状态,并且释放之前占有的o对象的锁

o.notify()方法只会通知,不会释放之前占有的o对象的锁
在这里插入图片描述

什么是“生产者和消费者模式”?

​ 生产线程负责生产,消费线程负责消费

​ 生产线程和消费线程需要达到均衡

​ 这是一种业务需求,在这种特殊的情况下需要使用wait方法和notify方法

wait方法和notify方法不是线程对象的方法,是普通java对象都有的方法

wait方法和notify方法建立在线程同步的基础之上,因为多线程要同时操作一个仓库,有线程安全问题

生产者:
在这里插入图片描述
在这里插入图片描述

消费者:
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ava实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),可运行高分资源 Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现
C语言是一种广泛使用的编程语言,它具有高效、灵活、可移植性强等特点,被广泛应用于操作系统、嵌入式系统、数据库、编译器等领域的开发。C语言的基本语法包括变量、数据类型、运算符、控制结构(如if语句、循环语句等)、函数、指针等。下面详细介绍C语言的基本概念和语法。 1. 变量和数据类型 在C语言中,变量用于存储数据,数据类型用于定义变量的类型和范围。C语言支持多种数据类型,包括基本数据类型(如int、float、char等)和复合数据类型(如结构体、联合等)。 2. 运算符 C语言中常用的运算符包括算术运算符(如+、、、/等)、关系运算符(如==、!=、、=、<、<=等)、逻辑运算符(如&&、||、!等)。此外,还有位运算符(如&、|、^等)和指针运算符(如、等)。 3. 控制结构 C语言中常用的控制结构包括if语句、循环语句(如for、while等)和switch语句。通过这些控制结构,可以实现程序的分支、循环和多路选择等功能。 4. 函数 函数是C语言中用于封装代码的单元,可以实现代码的复用和模块化。C语言中定义函数使用关键字“void”或返回值类型(如int、float等),并通过“{”和“}”括起来的代码块来实现函数的功能。 5. 指针 指针是C语言中用于存储变量地址的变量。通过指针,可以实现对内存的间接访问和修改。C语言中定义指针使用星号()符号,指向数组、字符串和结构体等数据结构时,还需要注意数组名和字符串常量的特殊性质。 6. 数组和字符串 数组是C语言中用于存储同类型数据的结构,可以通过索引访问和修改数组中的元素。字符串是C语言中用于存储文本数据的特殊类型,通常以字符串常量的形式出现,用双引号("...")括起来,末尾自动添加'\0'字符。 7. 结构体和联合 结构体和联合是C语言中用于存储不同类型数据的复合数据类型。结构体由多个成员组成,每个成员可以是不同的数据类型;联合由多个变量组成,它们共用同一块内存空间。通过结构体和联合,可以实现数据的封装和抽象。 8. 文件操作 C语言中通过文件操作函数(如fopen、fclose、fread、fwrite等)实现对文件的读写操作。文件操作函数通常返回文件指针,用于表示打开的文件。通过文件指针,可以进行文件的定位、读写等操作。 总之,C语言是一种功能强大、灵活高效的编程语言,广泛应用于各种领域。掌握C语言的基本语法和数据结构,可以为编程学习和实践打下坚实的基础。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值