多线程

2021-06-07 03:04

阅读:422

标签:time   write   提前   文件创建   下载器   f11   mss   priority   生产者消费者模式   

Java.Thread类

process(进程)和Thread(线程)

程序:指令和数据的有效集合,本身没有任何运行含有,是一个静态的概念

进程:执行程序的一次执行过程,是一个动态的概念。是系统资源分配的单位

线程:通常一个进程可以包含多个线程(由调度器安排调度,不可人为干预)一个进程至少有一个线程,线程是CPU调度和执行的单位

1. 线程的创建

  1. Thread Class 继承Thread类 *
  2. Runnable接口 实现Runnable接口*
  3. Callable接口 实现Callable接口

1. 通过继承Thread

  1. 自定义线程类并继承Thread类

  2. 重写run()方法,编写线程执行体

  3. 创建线程对象,调用start()方法启动线程

    启动方式:子类对象.start()

注:run()方法只有主线程一条执行路径,start()方法有多条执行路径,线程交替执行

package com.zhou3.Thread;

public class TestThreadDemo01 extends Thread{
    @Override
    public void run() {
        //run方法线程体
        for (int i = 0; i 

2. 通过实现Runnable

  1. 定义线程类实现Runnable接口

  2. 实现run()方法,编写线程执行体

  3. 创建Thread线程对象,通过start()方法启动线程

    启动方式:传入目标对象+Thread对象.start()

推荐使用此方式:可以避免单继承,灵活方便,方便一个对象被多个线程使用

package com.zhou3.Thread;
/*
多个线程同时操作同一个对象
没有对单一对象进行操作控制,存在并发问题
 */
public class TestThread implements Runnable{
    private int ticketNumber = 10;
    @Override
    public void run() {
        while (true){
            if(ticketNumber 
package com.zhou3.Thread;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;

/*
练习多线程,同步下载漂亮的图片
 */
public class TestThreadDemo02 implements Runnable{
    //类属性
    private String url;
    private String name;
//有参构造函数
    public TestThreadDemo02(String url,String name){
        this.name = name;
        this.url = url;
    }

    @Override
    public void run() {
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url,name);
        System.out.println(name + "已经下载");
    }
}

//下载器
class WebDownloader{
    public void downloader(String url,String name){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,downloader方法出现问题");
        }
    }
//main线程实现 多线程对象执行
    public static void main(String[] args) {
        TestThreadDemo02 t1 = new TestThreadDemo02("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fattach.bbs.miui.com%2Fforum%2F201110%2F20%2F104212ds59m9rtqspt6tzt.jpg&refer=http%3A%2F%2Fattach.bbs.miui.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1618908610&t=8c2e3d5e4418b17dcdccf84593a7cfed","1.jpg");
        TestThreadDemo02 t2 = new TestThreadDemo02("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic.jj20.com%2Fup%2Fallimg%2F1114%2F0H320120Z3%2F200H3120Z3-6-1200.jpg&refer=http%3A%2F%2Fpic.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1618908800&t=e7ef5c1ee4be471886018bc7b88f5aee","2.jpg");
        TestThreadDemo02 t3 = new TestThreadDemo02("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fb.zol-img.com.cn%2Fdesk%2Fbizhi%2Fimage%2F1%2F1680x1050%2F1349289433496.jpg&refer=http%3A%2F%2Fb.zol-img.com.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1618908797&t=fbd6b61bf2cec0bfed9d6ef9e23ce412","3.jpg");

        new Thread(t1).start();
        new Thread(t2).start();
        new Thread(t3).start();
    }
}

3. 通过实现Callable接口

  1. 实现Callable接口,需要返回值类型
  2. 重写call方法,需要抛出异常
  3. 创建目标对象
  4. 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
  5. 提交执行:Future result1 = ser.submit(t1);
  6. 获取结果:boolean r1 = result1.get()
  7. 关闭服务:ser.shutdownNow();
package com.zhou3.Thread;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;

/*
使用Callable接口实现多线程
好处:1. 可以定义返回值 2. 可以抛出异常
 */
//多线程类
public class CallableDemo01 implements Callable {
    //线程类属性
    private String url;
    private String fileName;

    //线程类构造器
    public CallableDemo01(String url,String fileName){
        this.url = url;
        this.fileName = fileName;
    }
    //多线程操作体
    @Override
    public Boolean call() throws Exception {
        webDownloader webDownloader1 = new webDownloader();
        webDownloader1.Downloader(url,fileName);
        System.out.println(fileName + "已经下载完成");
        return true;

    }

}
//下载器类
class webDownloader{
    public void Downloader(String url,String fileName){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(fileName));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,URL或文件创建时出错");
        }
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CallableDemo01 c1 = new CallableDemo01("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fattach.bbs.miui.com%2Fforum%2F201110%2F20%2F104212ds59m9rtqspt6tzt.jpg&refer=http%3A%2F%2Fattach.bbs.miui.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1618908610&t=8c2e3d5e4418b17dcdccf84593a7cfed","1.jpg");
        CallableDemo01 c2 = new CallableDemo01("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic.jj20.com%2Fup%2Fallimg%2F1114%2F0H320120Z3%2F200H3120Z3-6-1200.jpg&refer=http%3A%2F%2Fpic.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1618908800&t=e7ef5c1ee4be471886018bc7b88f5aee","2.jpg");
        CallableDemo01 c3 = new CallableDemo01("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fb.zol-img.com.cn%2Fdesk%2Fbizhi%2Fimage%2F1%2F1680x1050%2F1349289433496.jpg&refer=http%3A%2F%2Fb.zol-img.com.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1618908797&t=fbd6b61bf2cec0bfed9d6ef9e23ce412","3.jpg");

        //创建执行服务
        //创建线程池
        ExecutorService ser = Executors.newFixedThreadPool(3);


        Future result1 = ser.submit(c1);
        Future result2 = ser.submit(c2);
        Future result3 = ser.submit(c3);
        //获取结果
        boolean rc1 = result1.get();
        boolean rc2 = result1.get();
        boolean rc3 = result1.get();

        //关闭服务
        ser.shutdownNow();
    }
}

2. Lambda表达式

λ是希腊字母表中排行第十一位的字母,英语名称位Lambda

1. Lambda表达式的使用限制

Lambda表达式的出现是为了简化繁琐的表达式,可以用于匿名内部类定义过多。属于函数式编程

//格式
(paramss) -> expression[表达式]
(paramss) -> statement[语句]
(paramss) -> {statements}

函数式接口:

任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口

public interface Runnable{
	public abstract void run();
}

对于函数式接口,我们可以通过lambda表达式创建该接口的对象

2. Lambda表达式的简化过程

package com.zhou3.Thread.Lambda;

/*
推导Lambda表达式
 */
public class LambdaDemo01 {
    //2.静态内部类
    static class Like2 implements iLike{
        @Override
        public void Lambda() {
            System.out.println("I Like Lambda2");
        }
    }

    public static void main(String[] args) {
        iLike like = new Like();
        like.Lambda();
        like = new Like2();
        like.Lambda();

        //3.局部内部类
        class Like3 implements iLike{
            @Override
            public void Lambda() {
                System.out.println("I Like Lambda3");
            }
        }

        like = new Like3();
        like.Lambda();
        //4.匿名内部类
        like = new iLike() {
            @Override
            public void Lambda() {
                System.out.println("I Like Lambda4");
            }
        };
        like.Lambda();

        //5.Lambda表达式,是对匿名内部类的再简化
        //Lambda表达式就是对程序一步步的简化得来的,由于函数式接口只有一个方法,所以匿名内部类的接口和方法部分就理所当然的被去掉了
        like = () -> {
            System.out.println("I Like Lambda5");
        };
        like.Lambda();

    }
}

//定义一个函数式接口
interface iLike{
    void Lambda();
}
//1.接口实现类
class Like implements iLike{
    @Override
    public void Lambda() {
        System.out.println("I Like Lambda1");
    }
}

3. 为什么要使用Lambda表达式呢!

  1. 避免匿名内部类定义过多
  2. 让代码看起来简洁
  3. 去掉了一堆没有意义的代码

3. 线程的五大状态

调度:操作系统给线程分配时间片(就绪状态进入运行状态的方式)

  1. 创建状态(new Thread时,线程进入创建状态)
  2. 就绪状态(当线程调用start()方法时,线程进入就绪状态)
  3. 阻塞状态(当调用sleep,wait,同步锁时,线程进入阻塞状态)
  4. 运行状态(此时线程内的代码体才开始真正的执行)
  5. 死亡状态(线程中断或结束,此时线程不可被重启)

1. 线程的操作方法

方法 说明
setPriority(int newPriority) 更改线程的优先级
static void sleep(long millis) 让当前正在执行的线程体休眠指定的毫秒数
void join() 等待该线程终止
static void yield() 暂停正在执行的线程对象,状态退回到就绪状态,重写等待时间片分配
boolean isAlive() 测试线程是否处于活动状态

2. 线程停止

  1. 不推荐使用JDK提供的stop()、destroy()方法(已经废弃)
  2. 推荐让线程自己停下来
  3. 建议使用一个标志位终止变量等flag=false时,线程终止运行
//在主线程中通过公开方法修改标志位来停止线程
package com.zhou3.Thread.ThreadState;

public class StopTest implements Runnable{
    //1. 设置全局标志位
    private boolean flag = true;
    @Override
    public void run() {
        int i = 0;
        while(flag){
            System.out.println("Thread time ==" + i++);
        }
    }

    //2. 设置一个线程停止的公开方法
    public void threadStop1(){
        this.flag = false;
    }

    public static void main(String[] args) {
        StopTest stopTest1 = new StopTest();
        new Thread(stopTest1).start();
        for (int i = 0; i 

3. 线程休眠

  1. sleep(时间)方法可以指定当前线程阻塞的毫秒数(1000ms =1s)
  2. sleep存在异常InterruptedException
  3. sleep的时间达到后,该线程就重新进入就绪状态等待时间片的分配
  4. sleep可以模拟网络延时,和倒计时等
  5. 每一个对象都有一个锁,sleep不会释放锁

模拟网络延时可以放大问题发生的可能性

通过线程休眠实现每秒打印异常当前时间

package com.zhou3.Thread.ThreadState;

import java.text.SimpleDateFormat;
import java.util.Date;

public class CountDownTest {
    public static void main(String[] args) {
        Date date1 = new Date(System.currentTimeMillis());//获取当前系统时间

        while (true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //格式化时间并输出
            System.out.println(new SimpleDateFormat("HH:mm:ss").format(date1));
            //刷新当前时间
            date1 = new Date(System.currentTimeMillis());
        }
    }
}

4. 线程礼让

  1. 通过一种不阻塞线程的方式,让线程暂停
  2. 将线程从运行状态转为就绪状态(重写等待CPU分配时间片)

5. 线程强制执行(插队)

Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞

package com.zhou3.Thread.ThreadState;

public class TestJoin implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i 

6. 线程状态观测

通过Thread.getState()方法来观测线程状态

package com.zhou3.Thread.ThreadState;

public class TestStateObservation {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for (int i = 0; i 

4. 线程优先级

线程优先级高不一定先执行,只是先执行的概率会大大提高(给的资源多)

  1. Java提供一个线程调度器监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度那个线程来执行

  2. 线程优先级的表示

    用数字来表示,范围1~10

    Thread.MIN_PRIORITY = 1;
    Thread.MAX_PRIORITY = 10;
    Thread.NORM_PRIORITY = 5;
    
  3. 改变线程优先级的方法

    getPriority()
    setPriority()
    
    package com.zhou3.Thread.ThreadState;
    
    public class TestPriority {
        public static void main(String[] args) {
            MyPriority myPriority1 = new MyPriority();
            Thread thread1 = new Thread(myPriority1,"1");
            Thread thread2 = new Thread(myPriority1,"2");
            Thread thread3 = new Thread(myPriority1,"3");
            Thread thread4 = new Thread(myPriority1,"4");
            Thread thread5 = new Thread(myPriority1,"5");
    
            thread1.start();
    
            thread2.setPriority(1);
            thread2.start();
    
            thread3.setPriority(10);
            thread3.start();
    
            thread4.setPriority(8);
            thread4.start();
    
            thread5.setPriority(3);
            thread5.start();
    
            //主线程优先级,默认优先级
            System.out.println(Thread.currentThread().getName()+"优先级:"+Thread.currentThread().getPriority());
        }
    }
    
    class MyPriority implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"优先级:"+Thread.currentThread().getPriority());
        }
    }
    

5. 守护(daemon)线程

  1. 线程分为用户线程守护线程
  2. 虚拟机必须保证用户线程执行完毕(main)
  3. 虚拟机不用等待守护线程执行完毕(gc垃圾回收)

6. 线程同步

并发:同一个对象多个线程同时操作

现实生活中:我们遇到这种问题(食堂排队打饭)时,解决办法就是排队一个一个来

在处理多线程问题时,遇到这种并发情况,我们就需要线程同步。

线程同步:是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕,下一次才开始使用

1. 队列和锁

这是线程同步的形成条件

每一个对象都有一把锁,可以实现线程安全

锁机制:synchronized排他锁,可以独占资源,而其他线程必须等待锁的释放

存在的问题:

  1. 性能降低
  2. 如果优先级高的线程等待一个优先级低的线程释放锁,会引起优先级倒置的问题

2. 同步方法

  1. synchronize关键字(包含synchronize方法和synchronize快)两种用法
  2. synchronize方法控制对“对象”的访问,每个对象都有一把锁,synchronize方法必须获得调用该方法的对象的锁才能执行,否则线程会被阻塞

默认锁this

锁的对象需要是Object类型的

java.util.concurrent  //并发包、这是一个有关并发编程的包

7. 死锁

相互等待对方锁定的资源:自己抱着一个锁去等另外一个锁。想要同时持有两个锁,但是都不想给

死锁的避免(四个必要条件)

  1. 互斥条件:一个资源每次只能被一个进程使用
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已经获得的资源保持不放
  3. 不剥夺条件:进程已获得的资源在未使用完之前,不能强行剥夺
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系

上面这四个必要条件,只要想办法破解其中任意一个或多个就可以避免死锁的发生

8. Lock锁

  1. JDK5.0开始,Java提供了更强大的线程同步机制----显示定义同步锁对象来实现同步。同步锁使用Lock对象充当
  2. java.util.concorrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具
  3. ReentrantLock类实现了Lock,它有与synchronized相同的并发型和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁和释放锁

注意:锁要写在并非线程类中

注意:要区分线程循环和方法循环的区别

9. Lock锁和synchronized关键字的对比

  1. Lock是显式锁,synchronized是隐式锁

  2. Lock只有代码块锁,synchronized有代码块锁和方法锁

  3. 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且有更好的拓展性

  4. 优先顺序

    Lock > 同步代码块(已经进入了方法体,分配了相应资源) > 同步代码块(方法体之外)

自我总结:

Lock可以显式的对不安全代码进行加锁和解锁,适合在run方法中写代码的情况

package com.zhou3.Thread.Unsafe;

import java.util.concurrent.locks.ReentrantLock;

/*
lock锁测试
 */
public class TestLock {

    public static void main(String[] args){
        UnSafe safe1 = new UnSafe();
        new Thread(safe1,"hong").start();
        new Thread(safe1,"ming").start();
        new Thread(safe1,"A").start();
    }

}

class UnSafe implements Runnable  {

    private int ticketNumber = 10;

    private final ReentrantLock lock =new ReentrantLock();


    @Override
    public void run(){
        while (true){
            try {
                lock.lock();
                if(ticketNumber 

synchronized关键字是隐形的对this加锁,可以使得本类一次只能一个线程调用。关键字的位置很重要,适合在多线程类中写方法,在run中调用方法

package com.zhou3.Thread.Demo01;


/*
多个线程同时操作同一个对象
 */
public class TestThread {

    public static void main(String[] args) {
        BuyTicket buyTicket1 = new BuyTicket();
        new Thread(buyTicket1,"小美").start();
        new Thread(buyTicket1,"黄牛").start();
        new Thread(buyTicket1,"懒羊羊").start();
//        new Thread(new BuyTicket(),"小美").start();
//        new Thread(new BuyTicket(),"大美").start();
//        new Thread(new BuyTicket(),"小明").start();
    }
}

class BuyTicket implements Runnable{

    boolean flag = true;
    private int ticketNumber = 10;

    @Override
    public void run() {
        while (flag){
//            if(ticketNumber 

10. 线程协作

生产者消费者模式(问题)

Java中提供的几个解决线程通信的问题

方法名 作用
wait() 线程会一直等待,直到其他线程通知,同sleep不同会释放锁
wait(long timeout) 等待指定毫秒数
notify() 唤醒一个处于等待状态的线程
notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级高的线程优先调度

注:以上方法都是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常IllegalMonitorStateException。

1. 管程法

通过缓冲区过度数据

Producer(生产者) 缓冲区 Consumer(消费者)

package com.zhou3.Thread.ThreadCommunication;

/*
测试生产者消费者模型,管程法
生产者,消费者,产品,缓冲区
 */
public class TestPC {
    public static void main(String[] args){
        SynContainer container1 = new SynContainer();
        Producer producer1 = new Producer(container1);
        Consumer consumer1 = new Consumer(container1);
        new Thread(producer1,"工具员").start();
        new Thread(consumer1,"黑心狗").start();
    }

}

class Producer implements Runnable{
    SynContainer synContainer1;
    //构造器
    public Producer(SynContainer synContainer1){
        this.synContainer1 = synContainer1;
    }
    @Override
    public void run(){
        for (int i = 0; i 

2. 信号灯法

注意:一个文件中只能有一个public类

相当于是只有一个位置的缓冲区

注意标志位每次调用方法都要更改

package com.zhou3.Thread.ThreadCommunication;
/*
使用信号灯法,实现传小纸条聊天
我,对象,内容
 */
public class TestLight {
    public static void main(String[] args) {
        Massage massage = new Massage();
        new Thread(new My(massage),"my").start();
        new Thread(new Zx(massage),"zx").start();
        System.out.println("我们会好好的");
    }
}

class My implements Runnable{
    Massage massage = new Massage();
    //我要用这个蓝色的小纸条
    public My(Massage massage){
        this.massage = massage;
    }
    //我这次打算写100条“我喜欢你”到小纸条上
    @Override
    public void run() {
        for (int i = 0; i 

11. 线程池

  1. 经常创建和销毁线程,会耗费大量资源,对性能影响很大
  2. 可以提前创建好多个线程,放入线程池,使用时直接获取,用完再放回去
  3. 好处
    1. 提高响应速度(减少了创建新线程的时间)
    2. 降低资源销毁(重复利用线程池中的线程,不需要每次都创建)
    3. 便于线程的管理
      1. corePoolSize:核心池的大小
      2. maximumPoolSize:最大线程数
      3. keepAliveTime:线程没有任务时最多保持多长时间后会终止

线程池的使用

  1. 从JDK5.0起提供了线程池相关的API:ExecutorService和Executors

  2. ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

    1. void execute(Runnable command)//执行任务,没有返回值,用来执行Runnable
      
    2.  Future submit(Callable task)//有返回值,一般用来执行Callable
      
    3. void shutdown()//关闭线程池
      
  3. Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

package com.zhou3.Thread.TestPool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestPoolRunnable {
    public static void main(String[] args) {
        //创建线程池
        MyThread myThread1 = new MyThread();
        ExecutorService service = Executors.newFixedThreadPool(10);
        service.execute(myThread1);
        service.execute(myThread1);
        service.execute(myThread1);
        service.execute(myThread1);

        service.shutdownNow();
    }
}
class MyThread implements Runnable{
    @Override
    public void run() {
            System.out.println(Thread.currentThread().getName());

    }
}

多线程

标签:time   write   提前   文件创建   下载器   f11   mss   priority   生产者消费者模式   

原文地址:https://www.cnblogs.com/zhoushuaiyi/p/14591925.html


评论


亲,登录后才可以留言!