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

Java多线程

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

Java多线程

程序,进程,线程的基本概念+并行与并发: 程序:是为完成特定任务,用某种语言编写的一组指令的集合,即指一段静态的代码,静态对象。 进程:是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程,有它自身的产生,存在和消亡的过程。-------生命周期 线程:进程可进一步细化为线程,是一个程序内部的一条执行路径 即:线程《线程(一个程序可以有多个线程) 程序:静态的代码 进程:动态执行的程序 线程:进程中要同时干几件事时,每一件事的执行路径成为线程。 并行:多个CPU同时执行多个任务,比如:多个人同时做不同的事 并发:一个CPU(采用时间片)同时执行多个任务,比如秒杀平台,多个人做同件事 线程的调度 调度策略: 时间片:线程的调度采用时间片轮转的方式 抢占式:高优先级的线程抢占CPU Java的调度方法: 1.对于同优先级的线程组成先进先出队列(先到先服务),使用时间片策略 2.对高优先级,使用优先调度的抢占式策略 多线程的创建方式有两种

1.实现runbale接口重写run方法

2.继承Thread类

start与run方法的区别: start方法的作用:

1.启动当前线程

2.调用当前线程的重写的run方法(在主线程中生成子线程,有两条线程)

调用start方法以后,一条路径代表一个线程,同时执行两线程时,因为时间片的轮换,所以执行过程随机分配,且一个线程对象只能调用一次start方法。 run方法的作用:在主线程中调用以后,直接在主线程一条线程中执行了该线程中run的方法。(调用线程中的run方法,只调用run方法,并不新开线程)

总结:我们不能通过run方法来新开一个线程,只能调用线程中重写的run方法(可以在线程中不断的调用run方法,但是不能开启子线程,即不能同时干几件事),start是开启线程,再调用方法(即默认开启一次线程,调用一次run方法,可以同时执行几件事)

Runnable接口应该由其实例旨在由线程执行的任何类实现。 该类必须定义一个名为run的无参数方法。 此接口旨在为希望在活动时执行代码的对象提供通用协议。 例如, Runnable是由类Thread实现的。 处于活动状态仅意味着线程已启动且尚未停止。 此外, Runnable提供了使类处于活动状态而不是子类化Thread 。 通过实例化Thread实例并将自身作为目标传入,实现Runnable的类可以在不继承Thread的情况下运行。 在大多数情况下,如果您只打算覆盖run()方法而不打算覆盖其他Thread方法,则应该使用Runnable接口。 这很重要,因为除非程序员打算修改或增强类的基本行为,否则类不应被子类化

多线程例子

1.实现Runnable接口重写run方法
package com.qf;


public class runableTest {

    public static void main(String[] args) {
        Window3 window3 = new Window3();
        Thread t1 = new Thread(window3);
        Thread t2 = new Thread(window3);
        Thread t3 = new Thread(window3);
        t1.setName("售票员1");
        t2.setName("售票员2");
        t3.setName("售票员3");
        t1.start();
        t2.start();
        t3.start();
    }




}
class Window3 implements Runnable{
    private int ticket=100;
    @Override
    public void run() {
        while (true){
            if (ticket>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"当前售出第"+ticket+"张票");
                ticket--;
            }else {
                break;
            }
        }
    }
}
2.继承Thread类
package com.qf;

import java.awt.*;


public class threadTest extends Thread {
    public static void main(String[] args) {
        Window window1 = new Window();
        Window window2 = new Window();
        Window window3 = new Window();
        window1.setName("售票口1");
        window2.setName("售票口2");
        window3.setName("售票口3");
        window1.start();
        window2.start();
        window3.start();

    }

static class Window extends Thread{
        private  int ticket=100;

    @Override
    public void run() {
        while (true){
            if (ticket>0) {
                try {
                    if (ticket>0){
                        sleep(100);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(getName()+"当前售出第"+ticket+"张票");
                ticket--;
            } else {
                break;
            }
        }
    }
}
}

比较创建线程的两种方式:
开发中,优先选择实现Runable接口的方式
原因
1:实现的方式没有类的单继承性的局限性
2:实现的方式更适合用来处理多个线程有共享数据的情况
联系:Thread也是实现自Runable,两种方式都需要重写run()方法,将线程要执行的逻辑声明在run中

3.新增的两种创建多线程方式 1.实现callable接口方式: 与使用runnable方式相比,callable功能更强大些: runnable重写的run方法不如callaalbe的call方法强大,call方法可以有返回值 方法可以抛出异常 支持泛型的返回值 需要借助FutureTask类,比如获取返回结果
package com.qf;

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


public class CallableTest  {
    public static void main(String[] args) {
        Window window = new Window();
//        通过futureTask对象的get方法来接收futureTask的值
        FutureTask futureTask=new FutureTask(window);
        Thread t1 = new Thread(futureTask);
        t1.setName("售票员1");
        t1.start();
        try {
            Object o = futureTask.get();
            System.out.println(o);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

    }





    static class Window implements Callable{
        private int ticket=100;

        @Override
        public Object call() throws Exception {
            while (true){
                if (ticket>0){
                    Thread.sleep(100);
                    System.out.println(Thread.currentThread().getName()+"当前售出第"+ticket+"张票");
                    ticket--;
                }else {
                    break;
                }
            }
            return ticket;
        }
    }
}

4.使用线程池的方式:

背景:经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大。

思路:提前创建好多个线程,放入线程池之,使用时直接获取,使用完放回池中。可以避免频繁创建销毁,实现重复利用。类似生活中的公共交通工具。(数据库连接池)

好处:提高响应速度(减少了创建新线程的时间) 降低资源消耗(重复利用线程池中线程,不需要每次都创建) 便于线程管理
package com.qf;

import com.google.common.util.concurrent.ThreadFactoryBuilder;

import java.util.concurrent.*;



public class ThreadPoolTest {


public static void main(String[] args) {
//    创建线程工厂并设置线程名字,方便出错时回溯
//      ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
//        .setNameFormat("demo-pool-%d").build();
    ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("售票员-%d").build();

   
    ExecutorService executorService = new ThreadPoolExecutor(5,200,0L, TimeUnit.MILLISECONDS,new linkedBlockingDeque(1024),namedThreadFactory,new ThreadPoolExecutor.AbortPolicy());
    Window1 window1 = new Window1();
    Window2 window2 = new Window2();
//    执行线程
    executorService.execute(window1);
    executorService.execute(window2);
//    关闭线程池
    executorService.shutdown();
}







 }

 class Window1 implements Runnable{
    private int ticket=100;
    @Override
    public void run() {
        while (true){
            if (ticket>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"当前售出第"+ticket+"张票");
                ticket--;
            }else {
                break;
            }

        }
    }
}

 class Window2 implements Runnable{
    private int ticket=100;
    @Override
    public void run() {
        while (true){
            if (ticket>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"当前售出第"+ticket+"张票");
                ticket--;
            }else {
                break;
            }

        }
    }
    
}


这里贴一张阿里Java开发规范
需要的java依赖


            com.google.guava
            guava
            16.0.1

线程通信方法: wait()/ notify()/ notifayAll():此三个方法定义在Object类中的,因为这三个方法需要用到锁,而锁是任意对象都能充当的,所以这三个方法定义在Object类中。 由于wait,notify,以及notifyAll都涉及到与锁相关的操作

wait(在进入锁住的区域以后阻塞等待,释放锁让别的线程先进来操作)---- Obj.wait 进入Obj这个锁住的区域的线程把锁交出来原地等待通知

notify(由于有很多锁住的区域,所以需要将区域用锁来标识,也涉及到锁) ----- Obj.notify 新线程进入Obj这个区域进行操作并唤醒wait的线程 有点类似于我要上厕所,我先进了厕所关了门,但是发现厕所有牌子写着不能用,于是我把厕所锁给了别人,别人进来上厕所还是修厕所不得而知,直到有人通知我厕所好了我再接着用。 所以wait,notify需要使用在有锁的地方,也就是需要用synchronize关键字来标识的区域,即使用在同步代码块或者同步方法中,且为了保证wait和notify的区域是同一个锁住的区域,需要用锁来标识,也就是锁要相同的对象来充当

一个线程的生命周期
线程是一个动态执行的过程,它也有一个从产生到死亡的过程。

下图显示了一个线程完整的生命周期。

  • 新建状态:
    使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。

  • 就绪状态:
    当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。

  • 运行状态:
    如果就绪状态的线程获取 CPU 资源,就可以执行
    run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

  • 阻塞状态:
    如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

  • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。

  • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。

  • 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会

进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。

  • 死亡状态:
    一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
线程的同步:在同步代码块中,只能存在一个线程。

线程的安全问题:

什么是线程安全问题呢?

线程安全问题是指,多个线程对同一个共享数据进行操作时,线程没来得及更新共享数据,从而导致另外线程没得到最新的数据,从而产生线程安全问题。

上述例子中:创建三个窗口卖票,总票数为100张票

1.卖票过程中,出现了重票(票被反复的卖出,ticket未被减少时就打印出了)错票。

2.问题出现的原因:当某个线程操作车票的过程中,尚未完成操作时,其他线程参与进来,也来操作车票。(将此过程的代码看作一个区域,当有线程进去时,装锁,不让别的线程进去) 生动理解的例子:有一个厕所,有人进去了,但是没有上锁,于是别人不知道你进去了,别人也进去了对厕所也使用造成错误。

3.如何解决:当一个线程在操作ticket时,其他线程不能参与进来,直到此线程的生命周期结束

4.在java中,我们通过同步机制,来解决线程的安全问题。 方式一:同步代码块 使用同步监视器(锁) Synchronized(同步监视器){ //需要被同步的代码 } 说明: 1. 操作共享数据的代码(所有线程共享的数据的操作的代码)(视作卫生间区域(所有人共享的厕所)),即为需要共享的代码(同步代码块,在同步代码块中,相当于是一个单线程,效率低) 2. 共享数据:多个线程共同操作的数据,比如公共厕所就类比共享数据 3. 同步监视器(俗称:锁):任何一个的对象都可以充当锁。(但是为了可读性一般设置英文成lock)当锁住以后只能有一个线程能进去(要求:多个线程必须要共用同一把锁,比如火车上的厕所,同一个标志表示有人) Runable天生共享锁,而Thread中需要用static对象或者this关键字或者当前类(window。class)来充当唯一锁

方式二:同步方法

使用同步方法,对方法进行synchronized关键字修饰 将同步代码块提取出来成为一个方法,用synchronized关键字修饰此方法。 对于runnable接口实现多线程,只需要将同步方法用synchronized修饰 而对于继承自Thread方式,需要将同步方法用static和synchronized修饰,因为对象不唯一(锁不唯一) 总结:

1.同步方法仍然涉及到同步监视器,只是不需要我们显示的声明。

2.非静态的同步方法,同步监视器是this 静态的同步方法,同步监视器是当前类本身。继承自Thread。class

package com.qf;


public class SynchronizedTest extends Thread {
    public static void main(String[] args) {
        Window window1 = new Window();
        Thread t1 = new Thread(window1);
        Thread t2 = new Thread(window1);
        Thread t3 = new Thread(window1);
        t1.setName("售票员1");
        t2.setName("售票员2");
        t3.setName("售票员3");
        t1.start();
        t2.start();
        t3.start();

    }

}
class Window implements Runnable{
    private int ticket=100;
    @Override
    public void run() {
        while (true){
            synchronized (this) {
                if (ticket>0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"当前售出第"+ticket+"张票");
                    ticket--;
                }else {
                    break;
                }

            }
        }
    }
}

方式三:JDK5.0新增的lock锁方法

package com.qf;

import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;


public class ReentrantLockTest {
    public static void main(String[] args) {
        Window4 window1 = new Window4();
        Thread t1 = new Thread(window1);
        Thread t2 = new Thread(window1);
        Thread t3 = new Thread(window1);
        t1.setName("售票员1");
        t2.setName("售票员2");
        t3.setName("售票员3");
        t1.start();
        t2.start();
        t3.start();
    }
}
class Window4 implements Runnable{
    private int ticket=100;
//    使用给定的公平策略创建一个新的ReentrantLock 。
//
//参数:
//公平 - 如果此锁应使用公平排序策略,则为true
    private final ReentrantLock lock = new ReentrantLock(true);
    @Override
    public void run() {
        while (true){
            lock.lock();
            try {
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "当前售出第" + ticket + "张票");
                    ticket--;
                } else {
                    break;
                }

            } finally {
                lock.unlock();
            }

        }
    }
}
总结:Synchronized与lock的异同?

相同:二者都可以解决线程安全问题

不同:synchronized机制在执行完相应的代码逻辑以后,自动的释放同步监视器 lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())(同时以为着lock的方式更为灵活) 优先使用顺序: LOCK-》同步代码块-》同步方法

死锁问题 甲现在要去吃饭他先去拿碗,然后等筷子,乙先去拿筷子,然后等碗,然后两个人就这样一直等着
package com.qf;


public class DeadLockTest {
    public static void main(String[] args) {
        final String lock1="碗";
        final String lock2="筷子";

        new Thread("线程1"){
            @Override
            public void run() {
                synchronized (lock1) {
                    System.out.println(Thread.currentThread().getName()+"拿到了"+lock1+"还差"+lock2);
                    try {
                        Thread.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"睡眠5ms结束");
                    synchronized (lock2){
                        System.out.println(Thread.currentThread().getName()+"获得"+lock2);
                    }
                }
            }
        }.start();


        new Thread("线程2"){
            @Override
            public void run() {
                synchronized (lock2) {
                    System.out.println(Thread.currentThread().getName()+"拿到了"+lock2+"还差"+lock1);
                    try {
                        Thread.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"睡眠5ms结束");
                    synchronized (lock1){
                        System.out.println(Thread.currentThread().getName()+"获得"+lock1);
                    }
                }
            }
        }.start();

    }

}

运行结果打印

死锁产生的4个必要条件

1、互斥:某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。
2、占有且等待:一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。
3、不可抢占:别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
4、循环等待:存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/358426.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

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

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