栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

13Java第十三章线程

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

13Java第十三章线程

程序、进程、线程

程序:在计算机上安装的程序代码(静态的)。

进程:运行中的程序,从硬盘上被加载到内存中,分配空间,是操作系统分配空间的单位。

线程:线程是进程内部的最小执行单元(单位),是操作系统调度单位。

举例:QQ

安装到电脑上(静态代码) 双击运行:加载到内存中

CPU执行以线程为单位。

线程、进程的关系

线程隶属于进程。

一个进程中可以包含多个线程,一个进程中至少包含一个线程(即主线程)。

可以在主线程中创建其他线程。

main方法

用来启动java主线程的。

线程

线程是操作系统调度执行的单位,线程是具备可以独立执行的。如果是多线程,那么两个线程可以是同时执行的。

线程是一个可以独立被操作系统执行调度的,多核CPU下,同时可以执行多个线程。

创建线程
//此程序非多线程! 
//这种现象它只是方法调用,程序会按照调用的先后顺序执行。
public class Sample { 
    public void method1(String str){ 
        System.out.println(str); 
    }
    public void method2(String str){ 
        method1(str); 
    }
    public static void main(String[] args) { 
        Sample s = new Sample(); s.method2("hello!"); 
    } 
}
继承线程的两种方式 继承Thread类的方式

不可以再继承其他类。

extends Thread:

MyThread myThread = new MyThread();

myThread.start();

public class MyThread extends Thread{
    //线程中执行的任务代码必须写在run()中
    @Override
    public void run(){
        for (int i = 0; i < 1000; i++) {
            System.out.println("MyThread:"+i);
        }
    }
}

public class Test {
    //启动java主线程
    public static void main(String[] args) {
        //创建自己实现的线程对象
        MyThread myThread = new MyThread();
//        myThread.run(); 调用run()方法,并非启动线程,就是普通的方法调用。
        //启动线程,并不是启动后就会立即执行。在操作系统处排上队了,什么时候执行由操作系统调度算法决定。
        myThread.start();
        System.out.println("main结束运行"); //此时已经是多线程了
        for (int i = 0; i < 100; i++) {
            System.out.println("main"+i);
        }

    }
}
实现Runnable接口的方式

还可以继承其他类。

implements Runnable:

ThreadDemo threadDemo = new ThreadDemo();

Thread thread = new Thread(threadDemo);

thread.start();

public class ThreadDemo implements Runnable{
    @Override
    public void run(){
        for (int i = 0; i < 1000; i++) {
            System.out.println("ThreadDemo:"+i);
        }
    }
}

public class Test {
    public static void main(String[] args) {
        //方式2
        //创建线程的执行任务,并不是线程。
        ThreadDemo threadDemo = new ThreadDemo();
        //Thread是线程的管理类,创建线程,并为其分配任务。
        Thread thread = new Thread(threadDemo);
        //启动线程
        thread.start();
        for (int i = 0; i < 100; i++) {
            System.out.println("main"+i);
        }
    }
}
Thread类

Thread类是用来管理线程的。

run()方法:实现线程任务

start()方法:启动线程

currentThread():获得当前执行的线程

线程是有优先级的,优先级范围是1-10,默认是5。

SetPriority(int newPriority):设置线程的优先级

getPriority():返回线程的优先级

以下三个方法会影响线程的状态。

join():等待当前线程结束。

sleep():线程休眠指定的时间。

yield():线程主动让出。

线程优先级

优先级用整数表示,取值范围是1~10,一般情况下,线程的默认优先级都是5,但是也可以通过setPriority和getPriority方法来设置或返回优先级。

较高优先级的线程有更多的机会获得CPU的执行权(不是绝对的)。

public class ThreadDemo implements Runnable{
    @Override
    public void run(){
        System.out.println("thread:"+Thread.currentThread().getPriority()); //优先级
    }
}

public class Test {
    public static void main(String[] args) {
        //方式2
        //创建线程的执行任务,并不是线程。
        ThreadDemo threadDemo = new ThreadDemo();
        
        Thread thread = new Thread(threadDemo);

        thread.setName("mythread111"); //为线程设置名称
        thread.start();

        thread.setPriority(3);
        Thread.currentThread().setPriority(10); //main优先级设置为10

//        System.out.println("thread:"+thread.getPriority()); //优先级
        System.out.println("main:"+Thread.currentThread().getPriority()); //优先级
    }
}
调度策略

时间片

抢占式:高优先级的线程抢占CPU。

Java的调度方法

同优先级线程组成先进先出队列,使用时间片策略。

对高优先级,使用优先调度的抢占式策略。

线程生命周期

生命周期:就是什么时候创建,又什么时候销毁(死亡)。

五种状态

新建

MyThread t = new MyThread( );

就绪

运行

阻塞

死亡

sleep():线程休眠指定的时间。

public class ThreadDemo implements Runnable{
    @Override
    public void run(){
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

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

        ThreadDemo threadDemo = new ThreadDemo();
        Thread thread = new Thread(threadDemo);
        thread.start();
    }
}

yield():可以直接从运行状态回到就绪队列中

public class ThreadDemo implements Runnable{
    @Override
    public void run(){
        for (int i = 0; i < 100; i++) {
            if(i%10==0){ //20%10
                Thread.yield(); //主动让线程让步,让步后回到就绪队列,有可能又先于main获得执行权
            }
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

public class Test {
    public static void main(String[] args) {
        ThreadDemo threadDemo = new ThreadDemo();
        Thread thread = new Thread(threadDemo);
        thread.start();
        for (int i = 0; i < 100; i++) {
            System.out.println("main"+i);
        }
    }
}

join():等待当前线程结束。

public class Test {
    public static void main(String[] args) throws InterruptedException {
        ThreadDemo threadDemo = new ThreadDemo();
        Thread thread = new Thread(threadDemo);
        thread.start();
        thread.join(); //等待当前线程结束,阻塞main线程
        for (int i = 0; i < 100; i++) {
            System.out.println("main"+i);
        }
    }
}

public class ThreadDemo implements Runnable{
    @Override
    public void run(){
        for (int i = 0; i < 100; i++) {
            if(i%10==0){ //20%10
                Thread.yield(); //主动让线程让步,让步后回到就绪队列,有可能又先于main获得执行权
            }
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}
线程分类

Java中的线程分为两类:用户线程和守护线程。

正常创建的线程是用户线程,守护线程与用户线程功能是一样的,去完成某件事情。

用户线程的工作完成后就结束了,守护线程是等待所有的用户线程结束后,守护线程才会自动退出。

例如垃圾回收任务,就是在一个守护线程中进行。

守护线程的作用是为其他线程的运行提供便利服务。

用户线程和守护线程两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。

注意:设置线程为守护线程必须在启动线程之前,否则会跑出一个IllegalThreadStateException异常。

//用户线程
public class ThreadDemo1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}

//守护线程
public class ThreadDemo2 extends Thread{
    @Override
    public void run() {
        while (true){
            
            System.out.println(Thread.currentThread().getName());
        }
    }
}

public class Test {
    public static void main(String[] args) {
        ThreadDemo1 t1 = new ThreadDemo1();
        t1.setName("用户线程");
        t1.start();
        ThreadDemo2 t2 = new ThreadDemo2();
        t2.setName("守护线程");
        //setDaemon()将线程设置为守护线程,必须在启动线程前设置,否则会报错
        t2.setDaemon(true);
        t2.start();
    }
}
多线程

在一个应用程序中可以有多个线程任务执行。

程序中同时需要执行多个任务时,就需要多个线程。

例如,360安全卫士

多线程的优缺点 优点

提高程序的响应(速度)。

提高CPU的利用率。

改善程序结构,将复杂任务分为对个线程,独立运行。

缺点

线程也是程序,所以线程需要占用内存,线程越多占用内存也越多,多线程对内存消耗增加。

可以通过升级硬件设备来解决

多线程需要协调和管理,所以需要CPU时间跟踪线程,多线程对CPU要求提高。

可以通过升级硬件设备来解决

多个线程访问同一个共享资源,会出现线程安全问题(多线程&&访问同一个共享问题)。

现在都是多核CPU,那么就可以在同一时间点上同时处理多个线程。

单一的多线程不会出现线程安全问题,每一个线程都在做自己的事情,没有交集。

线程安全问题由并发引起。

解决线程安全问题:加锁+排队

为出票方法加锁,一次只能有一个线程进入到出票方法中。

线程同步 多线程同步

多个线程同时读写同一份共享资源时,可能会引起冲突。所以引入线程“同步”机制,即各线程间要有先来后到。

同步就是排队+锁:

几个线程之间要排队,一个个对共享资源进行操作,而不是同时进行操作。为了保证数据在方法中被访问时的正确性,在访问时加入锁机制。

一个线程持有锁会导致其他所有需要此锁的线程挂起;在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题。

并发与并行 并行

多个CPU同时执行多个任务。

线程并行执行:多个人同时做不同的事情,互不干扰。

并发

在一个时间段内依次执行操作。

线程并发执行:在一个时间段内,依次执行某件事情,一个一个来做,交替做。

例如卖票,抢购,秒杀看似同时进行, 实际是一个一个执行。

必须程序控制线程并发执行 高并发 双十一 秒杀 抢购

同步锁

同步锁可以是任何对象,必须唯一,保证多个线程获得是同一个对象(用来充当锁标记)。

同步执行过程

1.第一个线程访问,锁定同步对象,执行其中代码。

2.第二个线程访问,发现同步对象被锁定,无法访问。

3.第一个线程访问完毕,解锁同步对象。

4.第二个线程访问,发现同步对象没有锁,然后锁定并访问。

模拟卖票线程

两个线程同时访问一个共享数据,现在没有任何控制,两个线程同时可以进入方法中执行

public class TickThread extends Thread{
    //假设有10张票 static变量只有一份,多个对象共享
    static int num = 10;
    //模拟出票
    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(num > 0){
                System.out.println(Thread.currentThread().getName()+":"+num);
                num--;
            }else {
                System.out.println("票卖完了");
                break;
            }
        }
    }
}
窗口1:10
窗口2:10
窗口2:9
窗口2:8
窗口2:7
窗口2:6
窗口2:5
窗口2:3
窗口2:2
窗口2:1
票卖完了
窗口1:2
票卖完了

出现问题:

两个线程同时访问一个共享数据,现在没有任何控制,两个线程同时可以进入方法中执行。

确保一个时间点只有一个线程访问共享资源。可以给共享资源加一把锁,哪个线程获取了这把锁,才有权利访问该共享资源。

使用同步锁(正确)

在Java代码中实现同步:使用synchronized(同步锁)关键字同步方法或代码块。

synchronized (同步锁){ 
    // 需要被同步的代码; 
} 
//synchronized还可以放在方法声明中,表示整个方法,为同步方法。 

使用extends:

使用synchronized关键字,synchronized可以修饰代码块。

synchronized修饰方法,如果是非静态方法,那么锁对象是this。如果是静态方法,那么锁对象是类的Class对象(一个类只有一个Class对象)

方法一:

public class TickThread extends Thread{
    //假设有10张票 static变量只有一份,多个对象共享
    static int num = 10;
    //多个线程共享的同一个锁对象
    static Object obj = new Object();
    //模拟出票
    @Override
    public void run() {
        while (true){

            synchronized (obj){
                if(num > 0){
                    System.out.println(Thread.currentThread().getName()+":"+num);
                    num--;
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else {
                    System.out.println("票卖完了");
                    break;
                }
            }
        }
    }
}

public class Test {
    public static void main(String[] args) {
        //创建两个线程对象 num是成员变量
        TickThread t1 = new TickThread();
        t1.setName("窗口1");
        t1.start();

        TickThread t2 = new TickThread();
        t2.setName("窗口2");
        t2.start();
    }
}

方法二:

public class TickThread extends Thread{
    //假设有10张票 static变量只有一份,多个对象共享
    static int num = 10;
    //多个线程共享的同一个锁对象
    static Object obj = new Object();
    //模拟出票
    @Override
    public void run() {
        while(true){
            this.print();
            if(num == 0){
                System.out.println("票卖完了");
                break;
            }
        }
    }

  
    public static synchronized void print(){
        if(num > 0){
            System.out.println(Thread.currentThread().getName()+":"+num);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            num--;
        }
    }
}

public class Test {
    public static void main(String[] args) {
        //创建两个线程对象 num是成员变量
        TickThread t1 = new TickThread();
        t1.setName("窗口1");
        t1.start();

        TickThread t2 = new TickThread();
        t2.setName("窗口2");
        t2.start();

        //t1对象地址==t2对象地址
        System.out.println(t1.getClass() == t2.getClass()); //true
    }
}

使用Runnable接口:

synchronized是靠底层指令控制实现。

synchronized加锁实现,底层运行是通过编译后的指令中来进行控制的。

synchronized可以修饰代码块,修饰方法,注意锁的对象(锁对象可能会变)。

synchronized加锁方式是隐式的,进入到同步代码块时,自动获取锁;同步代码块执行完成后,自动释放锁。

方法一:

public class TickThread implements Runnable{
    //是否加static并无影响,因为只创建了一个线程任务对象,num只有一份
    static int num = 10; //这就是共享资源
    @Override
    public void run() {
        while (true){
            //可以用this,因为this只用了一个
            synchronized (this){
                if(num > 0){
                    System.out.println(Thread.currentThread().getName()+":"+num);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                num--;
                }else {
                    System.out.println("票卖光了");
                    break;
                }
            }
        }
    }
}

public class Test {
    public static void main(String[] args) {
        //创建一个出票的任务
        TickThread t = new TickThread(); //只创建了一个线程任务对象
        
        Thread t1 = new Thread(t,"窗口1");
        t1.start();
        Thread t2 = new Thread(t,"窗口2");
        t2.start();
    }
}

方法二:

public class TickThread implements Runnable{
    //是否加static并无影响,因为只创建了一个线程任务对象,num只有一份
    static int num = 10; //这就是共享资源
    @Override
    public void run() {
        while(true){
            this.print();
            if(num == 0){
                System.out.println("票卖完了");
                break;
            }
        }
    }

    public static synchronized void print(){
        if(num > 0){
            System.out.println(Thread.currentThread().getName()+":"+num);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            num--;
        }
    }
}

public class Test {
    public static void main(String[] args) {
        //创建一个出票的任务
        TickThread t = new TickThread(); //只创建了一个线程任务对象
        
        Thread t1 = new Thread(t,"窗口1");
        t1.start();
        Thread t2 = new Thread(t,"窗口2");
        t2.start();
    }
}
Lock接口

Lock锁是靠java代码来控制的。

ReentrantLock类实现了Lock接口,可以来控制与synchronized相同的功能,拥有与synchronized相同的并发性和内存语义,但是两者的实现细节完全不同。

在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。

public class TickThread implements Runnable{
    static int num = 10; //这就是共享资源
    ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true){
            try {
                //加锁
                lock.lock();
                if(num > 0){
                    System.out.println(Thread.currentThread().getName()+":"+num);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    num--;
                }else {
                    System.out.println("票卖光了");
                    break;
                }
            }finally {
                //synchronized如果出现异常会自动释放锁
                //释放锁,尽量放在finally代码块中释放锁,一旦try中出现异常,也保证把锁释放掉
                lock.unlock();
            }
        }
    }
}

public class Test {
    public static void main(String[] args) {
        //创建一个出票的任务
        TickThread t = new TickThread(); //只创建了一个线程任务对象
        Thread t1 = new Thread(t,"窗口1");
        t1.start();
        Thread t2 = new Thread(t,"窗口2");
        t2.start();
    }
}
线程死锁

不同的线程分别占用对方需要的同步资源不放弃,出现死锁,发生死锁后,程序不会报错,只会等待,所有的线程都处于阻塞状态,无法继续。

锁嵌套时,容易发生死锁现象。加锁时,考虑清楚锁的顺序,尽量减少嵌套使用。

public class DieThread extends Thread{

    boolean flag ;
    static Object objA = new Object();
    static Object objB = new Object();

    public DieThread(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        //双方分别持有对方需要的同步资源不放弃,造成死锁
        if(flag){
            synchronized (objA){
                System.out.println("if objA");
                synchronized (objB){
                    System.out.println("if objB");
                }
            }
        }else {
            synchronized (objB){
                System.out.println("else objB");
                synchronized (objA){
                    System.out.println("else objA");
                }
            }
        }
    }
}

public class Test {
    public static void main(String[] args) {
        DieThread d1 = new DieThread(true);
        d1.start();

        DieThread d2 = new DieThread(false);
        d2.start();
    }
}
线程通信

线程通讯指的是多个线程通过相互牵制,相互调度,制约运行,即线程间的相互作用。

涉及三个方法:

wait():让线程等待,wait()方法后线程就进入到阻塞状态,,并释放同步监视器,必须通过另一个线程唤醒。

notify():唤醒等待中的线程。一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。

notifyAll():唤醒等待中的所有被wait的线程。

注意:wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。

//两个线程交替打印1-100之间的数字
public class PrintThread implements Runnable{
    int num = 0; //共享变量
//    Object obj = new Object(); //也可将锁换为obj

    @Override
    public void run() {
        while (true){
            synchronized (this){
                //唤醒等待的线程
                this.notify();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                if(num <= 100){
                    System.out.println(Thread.currentThread().getName()+":"+num);
                    num++;
                }else {
                    break;
                }
                try {
                    //让线程等待。wait方法一定要调用的时锁对象中的wait方法,可以释放掉锁。
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

public class Test {
    public static void main(String[] args) {
        PrintThread p = new PrintThread();

        Thread t1 = new Thread(p);
        t1.start();

        Thread t2 = new Thread(p);
        t2.start();
    }
}
sleep()和wait()

相同点:

都可以让线程进入阻塞状态。

不同点:

sleep()

是Thread类中的方法

不会释放锁

休眠时间到了后,会自动进入到就绪状态

wait()

是Object类中的方法

wait方法是可以释放锁

wait后的线程需要使用notify/notifyAll唤醒

生产者和消费者问题

两个线程之间相互牵制使用。

问题描述:生产者(Productor)将产品放在柜台(Counter),而消费者(Customer)从柜台 处取走产品,生产者一次只能生产固定数量的产品(比如:1),这时柜台中不能再放产品,此时生产者应停止生产等待消费者拿走产品,此时生产者唤醒消费者来取走产品,消费者拿走产品后,唤醒生产者,消费者开始等待。

生产者线程 +1

​ 柜台中的商品

消费者线程 -1

public class Productor extends Thread{
    Counter counter;

    public Productor(Counter counter) {
        this.counter = counter;
    }

    @Override
    public void run() {
        while (true){
            counter.jia();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}



public class Customer extends Thread{
    Counter counter;

    public Customer(Counter counter) {
        this.counter = counter;
    }

    @Override
    public void run() {
        while (true){
            counter.jian();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}



public class Counter {
    int num = 1; //商品数量为1
    
    public synchronized void jia(){
        if(num == 0){
            num++;
            System.out.println("生产者生产了一个商品");
            this.notify();
        }else {
            try {
                this.wait(); //生产者等待 释放锁
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //消费者调用
    public synchronized void jian(){
        if(num == 1){
            num--;
            System.out.println("消费者取走了一个商品");
            this.notify();
        }else {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class Test {
    public static void main(String[] args) {
        Counter counter = new Counter(); //共享同一个柜台,共享数据

        Productor productor = new Productor(counter);
        productor.start(); //启动生产者线程
        Customer customer = new Customer(counter);
        customer.start(); //启动消费者线程
    }
}
第三种创建线程的方式

继承Thread还是实现Runnable,最终都是重写Run()。run方法没有返回值,也不能抛出异常,就存在局限性。

Java中推出了新的接口 Callable接口,定义了一个call()方法,可以有返回值(使用泛型自定义),可以抛出异常。

实现Callable接口与使用Runnable相比,Callable功能更强大些。

相比run()方法,可以有返回值

方法可以抛出异常

支持泛型的返回值

需要借助FutureTask类,获取返回结果

接收任务:

FutureTask futureTask = new FutureTask(任务); 

创建线程:

Thread t = new Thread(futureTask); 
t.start(); 
Integer val = futureTask.get(); //获得线程call方法的返回值

示例:

public class SumThread implements Callable {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        Thread.sleep(100);
        for (int i = 0; i <= 10; i++) {
            sum += i;
        }
        return sum;
    }
}

public class Test {
    public static void main(String[] args) {
        SumThread sumThread = new SumThread();

        FutureTask futureTask = new FutureTask(sumThread);
        Thread t = new Thread(futureTask);
        t.start();

        try {
            Integer sum = futureTask.get(); //获得线程所返回的结果
            System.out.println(sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/737219.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号