多线程的知识点

 

目录

一、什么是线程?进程?多线程?

 1、程序和进程

2、线程与多线程

3、多线程的实现

三、与线程的相关概念

1、线程的生命周期

1)新生状态(new)

2)就绪状态(runnable)

3)运行状态(running)

4)阻塞状态(block)

5)死亡(dead)状态

2、优先级与Thread字段(了解一下)

四、线程的其他方法

1、setName()与getName()

2.sleep()

怎么终止睡眠线程或者唤醒睡眠线程?

3、yield()

4、join()

5、wait()与notify()

五、线程的安全问题

1、线程同步与互斥

2、synchronized关键字


一、什么是线程?进程?多线程?

 1、程序和进程

程序只是一组指令的有序集合,是一个静态的实体。它本身没有任何运行的含义。

进程是一个动态的实体,它有自己的生命,它因创建而产生,因调度而运行,因资源或事件而被处于等待状态, 是正在计算机中执行的程序。它反映了一个程序在一定的数据集上运行的全部动态过程。一个程序一般只有一个进程,这个进程死了,程序也就结束了

进程和程序并不是一一对应的,一个程序可能有多个进程,一个进程却只对应一个程序

2、线程与多线程

线程是进程内一个相对独立的、可调度的执行单元,又称为轻量级进程。线程必须拥有父进程。在java中,我们操作的都是对象,线程也不例外。是对象就有类,线程类就是Thread。

main方法结束只代表主线程结束了,其他线程可能还在执行。

下面理解以下并行与并发的概念:

并行:并排行驶,相当两个人并排走。也就是说,两个程序各走各的,互不干涉。对于多核的CPU电脑,可能真的有4个进程并发执行,多核的CPU才能做到并行。

并发:程序是交替执行的,即,同一时间只能执行一个程序,到了一个时间点后就换另一个程序执行。单核的CPU不能真正多线程并发,(即,单核的CPU可以做到并发)但可以做到给人一种“多线程并发”的感觉,在某一个时间上实际上只能处理一件事情,CPU处理速度很快,多个线程之间频繁切换执行,

多线程程序指的是一个程序包含多个执行流,它是实现并发机制色有效手段

多线程堆内存和方法区内存共享,但栈内存独立,一个线程一个栈。多线程并发指的是每个线程和每个线程之间互不干扰,各自执行各自的,这样能提高程序的处理效率。

3、多线程的实现

java支持多线程机制,并且已经实现了

java的多线程是一个类,这个类可以对它的线程进行控制,可以确定那个线程的优先级高,哪个线程应该执行,线程的执行由拥有这个线程的一个或多个类来控制。

java中实现线程的方式有两种:

1)编写一个类,直接继承jThread,重写run方法。所有从java.long.Thread类派生的直接子类或间接子类均为线程

package com.xiancheng.one;
​
public class Thread01 extends Thread{
    //1.构建Thread子类对象,重写其中的run方法,
    String threadId;
​
    public Thread01(String threadId) {
        this.threadId = threadId;
    }
​
    @Override
    public void run() {
        System.out.println("Thread start:"+this.threadId);
        for(int i=0;i<6;i++) {
            System.out.println("i="+ (i+1)+"\t");
            System.out.println("Thread stopped:"+this.threadId);
        }
    }
}
package com.xiancheng.one;
​
import com.xiancheng.four.Thread01;
​
public class ThreadTest01 {
    public static void main(String[] args) {
        System.out.println("Staring ThreadTest01");
        Thread01 t1 = new Thread01("thread1");//创建线程对象
        t1.start();//线程对象调用start()方法启动该线程
        Thread01 t2 = new Thread01("thread2");
        t2.start();
        Thread01 t3 = new Thread01("thread3");
        t3.start();
        System.out.println("ThreadTest01 is done");
    }
}

更细致的理解

package com.xiancheng.four;
​
public class Thread04 extends Thread {
    @Override
    public void run() {
        //编写程序,这段程序运行在分支线程中(分支栈)
        for (int i=0; i<5; i++) {
            System.out.println("分支线程————>" + i);
        }
    }
}
package com.xiancheng.one;
​
import com.xiancheng.four.Thread04;
​
public class ThreadTest04 {
    public static void main(String[] args) {
        //这里是main方法,这里的代码属于主线程,在主栈中运行
        //新建一个分支线程对象
        Thread04 thread04 = new Thread04();
        //启动线程
        //start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了
        //启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)
        //run方法在分支栈的底部,main方法在主栈的底部,run和main是平级的
        thread04.start();
        //这里的代码还是运行在主线程中
        for (int i=0; i<5; i++) {
            System.out.println("主线程————>" + i);
        }
    }
}
/*
每次的输出结果随机,是主线程与分线程在抢着输出在控制台
*/

 

 

2)编写一个类,实现Runable接口,创建对象,调用start()方法

package com.xiancheng.one;
​
public class ThreadTest05 {
    public static void main(String[] args) {
        //创建一个可运行的对象
        Thread05 thread05 = new Thread05();
        //将可运行的对象封装成一个线程对象
        Thread t = new Thread(thread05);
        /*也用接口的方式创建对象:Thread t = new Thread(new Thread05);
        参考源码:
        public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
        }
         */
        
        //启动线程
        t.start();
        for (int i=0; i<5; i++) {
            System.out.println("主线程————>" + i);
        }
    }
}
//这并不是一个可运行的线程,而是一个可运行的类,它还不是一个线程
class Thread05 implements Runnable {
    @Override
    public void run() {
        for (int i=0; i<5; i++) {
            System.out.println("分支线程————>" + i);
        }
    }
}

三、与线程的相关概念

1、线程的生命周期

每个java程序都有一个主线程,即main方法对应的线程,要实现多线程,必须在主线程中创建一个新的线程,线程从新生到死亡的状态变化过程称为线程的生命周期。每个线程都要经历:

1)新生状态(new)

创建Thread实例后,这个Thread对象就处于新生状态,此时线程处于被创建还未开始执行的一个特殊状态,仅仅是一个空线程对象,它还没有分配到系统资源,因此只能启动或者终止它,其他操作会引发异常

2)就绪状态(runnable)

为了得到实际的线程,为线程创建属于与他的内存资源,需要使用start()方法启动线程,这样,线程就进入了就绪状态。此时线程具备了运行的条件,但还没有获得CPU时间片,因此进入就绪队列中排队。一旦获得CPU时间片,线程就会进入运行状态并自动调用自己的run方法。至于线程什么时候被选中运行,取决于优先级和进入就绪状队列的时间。优先级相同的线程则采用“先来先服务”的调度原则。

3)运行状态(running)

线程一旦获得CPU时间片,就开始执行run()方法中线程执行体,线程进入运行状态

如果线程不能够在一个时间片内执行完毕,就会被中断,有运行状态回到就绪状态,进入到线程就绪队列中排队等待下一次被调度执行

如果优先级高的程序进入就绪状态,它会抢占正在执行的优先级低的线程的CPU,优先级高的线程会进入运行状态,优先级低的线程会回到就绪状态

另外,运行状态的线程可以调用yield()方法主动放弃执行,转入到就绪状态。但是它仍可能被选中执行。对于yeild()方法,只给相同优先级的线程可执行的机会。

4)阻塞状态(block)

阻塞状态其实睡眠、资源阻塞、等待这三种状态的结合体。这三种状态有一个共同点:线程是活的,但是当前缺乏运行它的条件、等条件满足了,就返回到就绪状态。当运行的线程遇到如下情况会进入阻塞状态:

1‘睡眠:线程调用sleep()方法睡眠一段时间

2‘资源阻塞:线程在等待一种资源,比如等待某个IO流的完成,或试图获得一个同步锁,但同步锁正被其他线程持有

3’等待:调用wait()方法是线程挂起,直到线程得到了notify()或notify()消息

当正在执行的线程被阻塞后,会让出CPU,其他就绪线程就可以获得执行的机会

5)死亡(dead)状态

如果线程的run()方法执行完毕,线程正常结束;或者线程执行过程中抛出一个未捕获异常或错误,线程异常结束。结束后,线程处于死亡状态。

一旦线程死亡,就不能复生。如果再死亡状态的线程对象上再次调用start()方法,则会出现运行时异常IllegalThreadException。

 

 

2、优先级与Thread字段(了解一下)

线程调度器是JVM的一部分,JVM通常将java线程直接映射为本地操作系统上的本机线程。可运行池放了runnable状态的线程,由线程调度器决定合适运行哪个线程,用户无法控制线程调度器,也无法指定哪个线程运行。(注意,在设计多线程应用程序是不能依赖线程的优先级。线优先级调度没有保证,只能作为一种提高效率的手段)常见的线程调度模型:

一个是抢占是调度模型:哪个线程的优先级比较高,抢到的CPU时间片的概率就高一些/多一些,java采用的就是抢占式调度模型

一个是均分式调度模型:平均分配CPU时间片,每个线程栈有的CPU时间片长度一样,平均分配,一切平等。有些编程语言就采用这种模型。

java提供的与线程调度有关的方法:

实例方法:

void setPriority(int nePriority)设置线程的优先级

int getPriority()获取线程的优先级

void join()合并线程

Thread最低优先级1,默认优先级是5,最高优先级是10,优先级高的获取CPU时间片可能会多一些(但也完全是,大概率是)。

不同平台对优先级的支持不同,为了更好的实现跨平台特性,通常是使用Thread类的以下3个静态常量设定线程优先级:Thread.MIN_PRIORITY(1)、Thread.NORM_PRIORITY(5)、Thread.MAX_PRIORITY(10)

package com.xiancheng.one;
​
public class ThreadTest05 {
    public static void main(String[] args) {
        /*
        System.out.println("最低优先级"+Thread.MIN_PRIORITY);
        System.out.println("最高优先级"+Thread.MAX_PRIORITY);
        System.out.println("默认优先级"+Thread.NORM_PRIORITY);
        //获取当前线程的优先级
        Thread t1 = Thread.currentThread();
        System.out.println(t1.getName()+"线程的默认优先级"+t1.getPriority());
​
        最低优先级1
        最高优先级10
        默认优先级5
        main线程的默认优先级5
        */
​
        Thread t2 = new Thread(new Thread05());
        t2.getName();
        t2.start();
​
        //优先级高一些,抢的CPU时间片相对多一些
        //但只是大概率
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "  " + i);
        }
​
    }
}
​
class Thread05 implements Runnable {
​
    @Override
    public void run() {
//        System.out.println(Thread.currentThread().getName()+"Thread05默认优先级"+Thread.currentThread().getPriority());
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "  " + i);
        }
    }
}

static void yield()暂停当前正在执行的线程对象,并执行其他线程。让位方法,yield()方法不是阻塞方法,让当前线程让位,让给其他线程使用,yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”

四、线程的其他方法

1、setName()与getName()

获取线程对象的名字:String t = 线程对象.getName 修改线程对象名字:线程对象.setName("线程名字") 默认格式

获取当前线程对象:(源码中currentThread方法)

package com.xiancheng.four;
​
public class Thread06 extends Thread {
    @Override
    public void run() {
        Thread t = Thread.currentThread();
        //t指的是当前线程对象,当t1执行run方法时,那么t就指t1
        //当t2执行run方法时,t就指t2
        System.out.println(t.getName());
        for (int i=0; i<5; i++) {
​
            System.out.println("分支线程——>" + i);
        }
    }
}
​
​
package com.xiancheng.one;
​
import com.xiancheng.four.Thread06;
​
public class ThreadTest06 {
    public static void main(String[] args) {
        //c指的就是当前线程对象,这段代码出现在main方法中,
        // 所以当前这个线程就是主线程
        Thread c = Thread.currentThread();
        System.out.println(c.getName());
​
        Thread06 t1 = new Thread06();
        //设置/修改线程的名字
//        thread06.setName("ttt");
        t1.setName("ii");//setName修改名字或设置
        //String name1 = t1.getName() ;//否则默认Thread-i模式
        System.out.println(t1.getName());
​
        Thread06 t2 = new Thread06();
        System.out.println(t2.getName());
        t1.start();
        t2.start();
        /*获取线程对象的名字:String t = 线程对象.getName
        * 修改线程对象名字:线程对象.setName("线程名字")
        * 默认格式
        * 获取当前线程对象:(源码中currentThread方法)
        * Thread t = Thread.currentThread()*/
    }
}

2.sleep()

static void sleep(long millis) 
使当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,取决于系统计时器和调度器的精度和准确性。 millis参数是毫秒
    
    public static native void sleep(long millis) throws InterruptedException;
/*
IllegalArgumentException -如果millis的值是负数
InterruptedException -如果任何线程中断了当前线程。当抛出此异常时,清除当前线程的中断状态。
*/

它是静态方法Thread.sleep()

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

package com.xiancheng.four;

public class Thread07 {
    public static void main(String[] args) {
        //让当前线程进入休眠,当前线程是主线程
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //五秒执行这里的代码
        System.out.println("hello");
    }
}

怎么终止睡眠线程或者唤醒睡眠线程?

有两种方式,一种是依赖异常机制,利用interrupt()方法,将睡眠线程终止(杀死),然后抛出异常:

package com.xiancheng.one;

public class ThreadTest05 {
    public static void main(String[] args) {
        Thread t = new Thread(new Thread05());
        t.setName("t");
        t.start();
        //希望9秒后t线程醒来

        try {
            Thread.sleep(1000*9);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //中断t线程的睡眠,这种中断睡眠的方式依靠了java的异常处理机制
        t.interrupt();//干扰
    }
}

class Thread05 implements Runnable {
    //run方法中的异常不能try只能try catch
    //因为run方法的父类没有抛出更多的异常,子类不能必父类抛出更多的异常
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"--->begin");
        try {
            Thread.sleep(1000*60);//此线程会睡1分钟
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

二是做一个布尔标识

package com.xiancheng.one;

public class ThreadTest05 {
    public static void main(String[] args) {
        Thread05 r = new Thread05();
        Thread t = new Thread(r);
        t.setName("t");
        t.start();
        //模拟五秒

        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //终止线程,想要唤醒线程的话,就将其改为false
        r.run =false;
    }
}

class Thread05 implements Runnable {
    //做义工布尔标志
    boolean run = true;

    @Override
    public void run() {
        if(run) {
            System.out.println(Thread.currentThread().getName()+"--->begin");
            try {
                Thread.sleep(1000*2);//此线程会睡1分钟
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("2秒");
        } else {
            //终止当前线程
            return;
        }

    }
}

3、yield()

此方法也是Thread类中的静态方法,作用是暂时终止正在执行的线程对象的运行,若存在其他同优先级线程,则随机调用下一个同优先级线程。若不存在其他同优先级线程,则这个被中断的线程就绪,这个方法可以保证CPU不会空闲,为sleep()方法则有可能浪费CPU时间,如所有合作线程都在等待,而CPU什么也不做

static void yield()暂停当前正在执行的线程对象,并执行其他线程。让位方法,yield()方法不是阻塞方法,让当前线程让位,让给其他线程使用,yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”

package com.xiancheng.one;

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

        Thread t2 = new Thread(new Thread05());
        t2.setName("t2");
        t2.start();

        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "  " + i);
        }

    }
}

class Thread05 implements Runnable {

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

4、join()

它会令当前线程“加入到”调用join()方法的线程的尾部

final void join()throws InterruptedException

package com.xiancheng.one;

public class ThreadTest05 {
    public static void main(String[] args) {
        System.out.println("main begin");
        Thread t2 = new Thread(new Thread05());
        t2.setName("t2");
        t2.start();

        //合并线程
        try {
            t2.join();
            //会抛异常
            //t合并到当前线程中,当前线程受阻,t线程执行直到结束,当前线程就是main线程
            //有了这段代码,就会先输出t线程的内容,再输出main线程的内容,否则反之
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        for (int i = 0; i < 15; i++) {
            System.out.println(Thread.currentThread().getName() + "  " + i);
        }
        System.out.println("main over");

    }
}

class Thread05 implements Runnable {

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

它会让当前线程“加入到”调用join()方法的尾部

5、wait()与notify()

这两个方法是在Object类中声明的方法,wait()方法使当前线程进入等待状态,直到被另一线程唤醒。notify()方法把线程状态的变化通知并唤醒另一等待线程,使之进入runnable状态。notifyall()方法是唤醒所有等待该资源的线程。

wait()方法只能在同步代码块中使用,sleep()可以在任何地方使用。wait()方法被引用后会解除对象的锁、使其他线程可以执行同步代码块或方法,而sleep()方法则保持已有的任何锁,不会将它们释放

五、线程的安全问题

以后在开发中,我们项目都是运行在服务器当中,而服务器以已经将线程的定义创建、线程的启动,都已经实现了,这些代码不需要编写。最重要的是,我们编写的程序要放到一个多线程的环境下运行,我们要关注这些数据在多线程并发的环境下是否安全

多线程并发、有共享数据、共享数据有修改的行为,当满足上述三个条件之后,就会存在线程安全问题。如何解决这个问题?采用线程排队执行(不能并发)来解决

1、线程同步与互斥

线程同步其实就是线程不能并发了了,线程必须排队执行。这种方式会牺牲一部分效率,但只有数据安全了,才能谈效率。两个线程之间发生了等待关系

异步就是线程t1与线程t2各自执行各自的,谁也不需要等谁,其实就是多线程并发,效率较高

例1:一个账户类,实现线程并发机制

package com.xiancheng.three;

public class Account {
    private String account;//账户
    private double balance;//余额

    public Account(String account, double balance) {
        this.account = account;
        this.balance = balance;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }
    //取款方法
    public void withdraw(double money) {
        //t1和t2并发这个方法(t1和t2是两个栈。两个栈操作同一个多象
        //取款之前的余额
        double before  = this.getBalance();
        //取款之后的余额
        double after = before - money;

        //模拟一些网络延迟,一定出问题
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
//为什么,不是两个线程都延迟了吗,为什么一定会出问题,虽然结果余额都是5000
        
        //更新余额
        //t1执行到这里了,但还没有来得及执行这行代码,t2线程尽量withdraw 方法,此时一定出问题
        this.setBalance(after);

    }
}
package com.xiancheng.three;

public class ThreadAccount extends Thread {
    //两个线程必须共享同一个账户对象
    private Account act;
    //通过构造方法传递过来账户对象

    public ThreadAccount(Account act) {
        this.act = act;
    }

    @Override
    public void run() {
        //run方法执行取款操作
        //假设取款5000
        double money = 5000;
        //取款
        //多线程并发执行这个方法
        act.withdraw(money);
        System.out.println(Thread.currentThread().getName() + " 账户" + act.getAccount() + "取款成功,余额 " + act.getBalance());
    }
}
package com.xiancheng.two;

import com.xiancheng.three.Account;
import com.xiancheng.three.ThreadAccount;

public class AccountTest {
    public static void main(String[] args) {
        //创建账户对象(只创建一个)
        Account act = new Account("act-001",10000);
        //创建两个线程
        Thread t1 = new ThreadAccount(act);
        Thread t2 = new ThreadAccount(act);
        //设置name
        t1.setName("t1");
        t2.setName("t2");
        //启动取款

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

2、synchronized关键字

在java语言中,任何一个对象都有一把锁,其实这把锁就是标记

线程同步机制的语法是:synchronize(共享对象) {线程同步代码块}

synchronize括号中传的这个数据是关键点,这个数据必须是多线程共享的数据,才能达到多线程排队。要写假设有t1、t2、t3、t4、t5五个线程,只需要t1、t2、t3排队,t4、t5不要排队,那么就需要在()中写一个t1、t2、t3共享的对象,而这个对象对t4、t5不是共享的

将例1的Account类改一下:

package com.xiancheng.three;

public class Account {
    private String account;//账户
    private double balance;//余额
    Object obj = new Object();
    //实例变量,Account对象是多线程共享的,Account的实例变量也是共享的

    public Account(String account, double balance) {
        this.account = account;
        this.balance = balance;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }
    //取款方法
    public void withdraw(double money) {

        //以下这几行代码线程必须是排队的
//        Object obj2 = new Object();
        //为什么这个obj2就不是共享的,是局部变量
//        synchronized (obj) {
//            synchronized ("abc") 所有线程都同步,"abc"在字符串常量池中
        
	/*
	假设t1和t2线程并发,开始执行时,一定有个线程先执行。假设先执行的	为t1,当遇到synchronize关键字时,t1会自动找这个“后面共享对象”的对	象锁。找到后并一直占有这把锁,直到synchronize中的同步代码块结	束,才会将锁释放。而在t1占有这把锁时,t2也遇到了synchronize关键		字,t2只能等t1释放这把锁后才能执行synchronize代码块
	*/
            synchronized (this) {
            //取款之前的余额
            double before  = this.getBalance();
            //取款之后的余额
            double after = before - money;

            //模拟一些网络延迟,一定出问题
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //更新余额
            //t1执行到这里了,但还没有来得及执行这行代码,t2线程尽量withdraw 方法,此时一定出问题
            this.setBalance(after);
        }
    }
}

局部变量、常量没有线程安全问题

同步范围越大,效率越低

如果使用局部变量的话,建议使用StringBuilder,而StringBuffer效率较低

非线程安全的ArrayList、HashMap、HashSet

线程安全的Vector、Hashtable

synchronize使用的三种方式:

1)同步代码块synchronize(){同步代码块}较灵活

2)在实例方法上使用synchronize,表示共享的对象一定是this,并且同步的是整个方法体

3)在静态方法上使用synchronize,表示找类锁,类锁只有一把

锁不属于线程,而属于对象,一个线程可以拥有多个对象的锁,而只有同一个对象的锁之间才会存在互斥

死锁

package com.xiancheng.one;

public class ThreadTest07 {
    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();
        //t1和t2线程共享o1、o2
        Thread t1 = new Thread07(o1,o2);
        Thread t2 = new Thread7(o1,o2);

        t1.start();
        t2.start();
    }
}
class Thread07 extends Thread {
    Object o1;
    Object o2;

    public Thread07(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }

    @Override
    public void run() {
        synchronized (o1) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o2) {

            }
        }
    }
}
class Thread7 extends Thread {
    Object o1;
    Object o2;

    public Thread7(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }

    @Override
    public void run() {
        synchronized (o2) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o1) {

            }
        }
    }
}

守护线程:java线程分两大类:守护线程(后台线程,如垃圾回收线程)与用户线程。一般守护线程是一个死循环,所有用户线程只要结束,守护线程自动结束

守护线程用的地方:每天00:00的时候系统数据自动备份。这个需要使用定时器,并且我们可以将定时器设置为守护线程一直看在那里,没到00:00的时候就备份一次所有的用户线程如果结束了,守护线程自动退出,没有必要在进行数据备份了

定时器

生产者与消费者模式

package com.xiancheng.five;

public class Store01 {
    private int count;//实际数量
    public final  int size;//仓库能存的最大的数量

    public Store01(int size) {
        this.size = size;
        count = 0;
    }
    //生产的方法
    public synchronized void allData() {
        //要用while循环,因为要在count=size期间
        while (count == size) {
            //如果数量已经达到了上限,那么生产线程就要等待消费线程
            try {
                System.out.println("ssjjd");
                this.wait();//等待,并释放锁
                //这是让当前线程
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        count++;
        System.out.println(Thread.currentThread().getName() + " add Data " + count);
        this.notifyAll();//唤醒其他线程
    }
    //消费的方法
    public synchronized void removeData() {
        while (count == 0) {
            //如果仓库没了货,就停止消费
            try {
                System.out.println("消费:");
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " remove Data " + count);
            count--;
            this.notifyAll();
        }
    }
}
package com.xiancheng.three;

import com.xiancheng.five.Store01;

public class Produce01 extends Thread {
    private Store01 store;//将仓库作为生产线程的属性

    public Produce01(String name, Store01 store) {
        super(name);
        this.store = store;
    }

    @Override
    public void run() {
        try {
            store.allData();
            //调用allData()方法
            Thread.sleep(300);
            //为什么让线程睡会?
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
package com.xiancheng.two;

import com.xiancheng.five.Store01;

public class Customer01 extends Thread {
    private Store01 store;

    public Customer01(String name, Store01 store) {
        super(name);
        this.store = store;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(100);
            System.out.println("消费者:");
            store.removeData();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
package com.xiancheng.three;

import com.xiancheng.five.Store01;
import com.xiancheng.two.Customer01;

public class StoreTest01 {
    public static void main(String[] args) {
        Store01 s = new Store01(1);

        Produce01 p1 = new Produce01("a",s);
        Produce01 p2 = new Produce01("b",s);

        Customer01 c1 = new Customer01("c",s);
        Customer01 c2 = new Customer01("d",s);
        p1.start();
        p2.start();
        c1.start();
        c2.start();

    }
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java 多线程是指在一个程序中可以同时执行多个线程,并且每个线程独立运行,互不干扰。Java 多线程是Java 平台提供的重要特性,常用于提高程序并发性能和解决复杂问题。 Java 多线程的重点知识点包括以下几个方面: 1. 线程的创建和启动:可以通过继承 Thread 类或实现 Runnable 接口来创建线程,并使用 start() 方法启动线程。 2. 线程的生命周期:线程的生命周期包括新建、就绪、运行、阻塞和死亡等阶段,可以通过调用 sleep()、wait()、join() 等方法来控制线程的状态转换。 3. 线程同步:多个线程之间可能会共享资源,为了保证数据的一致性和避免冲突,需要使用同步机制,如 synchronized 关键字、Lock 接口等。 4. 线程间通信:多个线程之间可以通过共享内存或者消息传递的方式进行通信,如使用 wait()、notify()、notifyAll() 等方法。 5. 线程池:线程池可以管理和复用线程,减少线程的创建和销毁开销,提高系统的性能。 6. 线程安全性:在多线程环境下,存在资源竞争和线程安全性问题,需要采取相应的措施来保证线程的安全性,如使用 synchronized 关键字、volatile 关键字等。 7. 线程调度:Java 提供了线程调度器来控制线程的执行顺序和优先级,可以使用 yield()、join()、setPriority() 等方法进行调度。 8. 线程异常处理:在多线程环境下,线程的异常需要处理和捕获,可以使用 try-catch 块来捕获异常。 综上所述,Java 多线程是Java 平台提供的重要特性,掌握多线程的概念和相关知识点可以帮助开发者提高程序的并发性能和解决复杂问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值