java 多线程开发

java 多线程开发,第1张

java 多线程开发 多线程与并发 (1)、进程与线程的概念 1) 什么是进程
  • 就是独立运行的一个程序, 是一个实体, 每个进程都有它自己的内存空间。

  • 进程的状态

    • 进程执行时的间断性,决定了进程可能具有多种状态。运行中的进程具有以下三种基本状态
      1. 就绪状态(Ready)
      1. 运行状态(Running)
      1. 阻塞状态(Blocked)

2) 什么是线程
  • 线程就是进程当中的一条执行路径,线程之间共享一个内存空间, 线程之间可以自由的切换, 并发执行。一个进程中可以多个线程,但是最少得有一个线程,

  • 例子: 在电子厂流水线中, 一条流水线就表示一个进程, 而流水线上的人 就是线程, 也就是真正来组装产品的人, 在映射过来的话就是 线程就是真正来执行代码的。

3) 并行与并发
  • 并行: 当两个cpu核心运行两个任务得时候, 那么这个两个任务是同时运行,那么就是并行。(cpu核心数大于或等于运行任务数)

  • 并发: 是指两个任务同时请求运行, 而处理器只有一个核心只能接收一个任务, 就会把两个任务轮流运行, 由于

    处理器时间片(CPU运行该程序的时间)运行时间较短, 就感觉两个程序同时运行。

(2) 使用java来创建线程 创建线程的的两种方式 1) 继承Thread类
// 方法1: 继承Thread 类
class MyThread extends Thread {
    public void run() {
       // 当前线程要做的事情
        for (int i = 0; i < 100; i++) {
            // Thread.currentThread() 获取当前线程实例
            // .getName() 获取当前线程名字
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

// main
// 创建线程
Thread t1 = new MyThread();
// 运行线程(先是就绪状态, 最终是JVM开启线程)
t1.start();
2) 实现Runnable 接口
// 方法2: 实现Runnable 接口
class MyRunnable implements Runnable {
    public void run() {
        // 当前线程要做的事情
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}
// main
// 创建实例(Running 接口,可以理解为一个任务)
Runnable myRunnable = new MyThread();
// 通过Thread 类对Runnable进行包装(将任务添加到一个线程中)
Thread t2 = new Thread(myRunnable);
t2.start();
(3) 线程休眠
// 放在哪个线程里面,就表示哪个线程要休眠
Thread.sleep(毫秒数);
  • 一旦当前线程休眠,则会释放CPU时间片, 一旦释放CPU时间片, 则其他线程会进行争取这个世时间片。
(4) join 的方法的使用
  • 加入线程, 让调用的线程先去执行指定的时间或者执行完毕。
  • 如果没有给定时间,则表示将该线程执行完毕 等待该线程的死亡。
// 可以指定毫秒数 和纳秒数
thread.join() 
public class ThreadDemo2 {
    // 主线程
    public static void main(String[] args) {
        MyRunnable2 myRunnable2 = new MyRunnable2();
        // 创建一个子线程
        Thread t1 = new Thread(myRunnable2);
        t1.start();
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread() + "--" + i);
            if (i == 20) {
                try {
                    // 注意:此时主线程是阻塞的, 直到t1执行完毕
                    t1.join();
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }


        }
    }
}

class MyRunnable2 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName() + "--" + i);
            try {
                Thread.sleep(250);
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
(5) 线程的中断 1) 方法1 通过interrupt() 方法 来中断
public class InterruptDemo {
    public static void main(String[] args) {
       MyRunnable3 myRunnable3 = new MyRunnable3();
       Thread t1 = new Thread(myRunnable3);
       t1.start();
        for (int i = 0; i < 50; i++) {
            if (i == 20) {
                // 1. 中断t1线程
                // 注意: 此时在线程中如果调用了sleep()方法 那么该方法会抛出了一个 InterruptedException 异常
                t1.interrupt();
            }
        }
    }
}
class MyRunnable3 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName() + "--" + i);
            // 3. isInterrupte() 方法表示当前线程是否已经中断
            if (Thread.currentThread().isInterrupted()) {
                break;
            }else {
                try {
                    Thread.sleep(0);
                }catch (InterruptedException e) {
                    e.printStackTrace();
                    // 2. 如果线程调用了interrupt方法, 则会在调用sleep方法处抛出InterruptedException异常
                    // 并且会自动去除线程中断异常, 一旦去除,则线程还是会运行。 所以interrpt() 方法只起到了标记的作用
                    // 所以需要在中断一次, 是为下一次循环判断
                    Thread.currentThread().interrupt();
                }
            }
        }
    }
}
2) 方法2 自定义中断线程
public static void main(String[] args) {
       MyRunnable4 myRunnable4 = new MyRunnable4();
       Thread t2 = new Thread(myRunnable4);
       t2.start();
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName() + "--" + i);
            try {
                Thread.sleep(250);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (i == 20) {
                // 2. 中断t1线程
                myRunnable4.flag = false;
            }
        }
    }
}

class MyRunnable4 implements Runnable {
    // 1. 创建一个中断条件
    public boolean flag = true;
    @Override
    public void run() {
        int i = 0;
        while (flag){
            System.out.println(Thread.currentThread().getName() + "--" + (i++));
            try {
                Thread.sleep(250);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

(6) 线程的其他方法
  • setDaemon(boolean on) 可以设置为守护线程或用户线程

  • 如果运行的线程都是守护线程,则JVM会退出

  • isDaemon() 判断是否是守护线程

  • getId() 返回该线程的标识符

  • getName() 获取 该线程的名字

  • **setName(String name) 设置该线程的名字 **

  • isAlive() 判断该线程是否为活动状态

  • setPriority(int newPriority) 更改该线程的优先级

    • 优先级: 可以提高该线程抢到时间片的概率
    static int MAX_PRIORITY // 线程可以具有的最高优先级。
    static int MIN_PRIORITY // 线程可以具有的最低优先级。
    static int NORM_PRIORITY // 分配给线程的默认优先级
    
(7) 线程同步 1) 多线程共享数据
  • 在多线程 *** 作中, 多个线程同时可以处理同一个资源。

  • 先看代码

public class SynchronizeDemo {
    public static void main(String[] args) {
        Ticket tk = new Ticket();
        // 创建线程
        Thread t1 = new Thread(tk);
        t1.setName("抢票机器1");
        Thread t2 = new Thread(tk);
        t2.setName("抢票机器2");
		
        // 开始抢票
        t1.start();
        t2.start();
    }
}

// 售票
class Ticket implements Runnable{
    private int currentTicket = 10; // 当前剩余票的数量
    @Override
    public void run() {
        // 抢票
        while (currentTicket > 0) {
            System.out.println(Thread.currentThread().getName()+"成功抢到票" + currentTicket--);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

2) 线程同步
  • 线程同步就是为了解决数据共享问题, 所谓的同步就是 多个线程在同一时刻只能有一个线程执行指定代码,

    其他线程必须等待该线程执行完毕后才可以执行。

一、同步方法1(使用synchronized代码块)
// main() 方法不变
// 售票
class Ticket implements Runnable{
    private int currentTicket = 10; // 当前剩余票的数量
    // 随便定义一个对象
    private final Object obj = new Object();
    @Override
    public void run() {
        // 抢票
        while (currentTicket > 0) {
            
            // 如果多个线程都在运行当前任务 最简单的可以直接使用 this 当前任务对象, 来当作同步锁
            synchronized (obj) {
                System.out.println(Thread.currentThread().getName()+"成功抢到票" + currentTicket--);
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

二、同步方法2(使用synchronized 修饰的方法)
  • 使用这个方法使用锁对象是当前任务的实例,也就是this
// 售票
class Ticket implements Runnable{
    private int currentTicket = 10; // 当前剩余票的数量
    @Override
    public void run() {
        // 抢票
        while (currentTicket > 0) {
           // 使用synchronized 修饰的方法
            syncFun();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
	// synchronized 修饰的方法
    public synchronized void syncFun() {
       if (currentTicket > 0) { 
            System.out.println(Thread.currentThread().getName()+"成功抢到票" + currentTicket--);
        }
    }
}
三、同步方法3(使用Lock互斥锁)
// 售票
class Ticket implements Runnable{
    private int currentTicket = 10; // 当前剩余票的数量
    private final Object obj = new Object();
    // 创建一个互斥锁
    private final Lock lock = new ReentrantLock();

    @Override
    public void run() {
        // 抢票
        while (currentTicket > 0) {
            // 加锁
            lock.lock();
             if (currentTicket > 0) { 
                System.out.println(Thread.currentThread().getName()+"成功抢到票" + currentTicket--);
            }
            // 解锁
            lock.unlock();
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

四、生产者与消费者示例
  • 这里我用两种队列实现的
1) 使用BlockingQueue (java.util.concurrent.BlockingQueue)
class Food2 {
    private String name;
    private String desc;

    public Food2(String name) {
        this.name = name;
    }

    public Food2() {
    }

    public String getName() {
        return name;
    }
    @Override
    public String toString() {
        return "Food2{" +
                "name='" + name + ''' +
                ", desc='" + desc + ''' +
                '}';
    }
}
public class ProductorAndConsumer2 {
    // 创建一个队列
    private static final BlockingQueue q = new linkedBlockingQueue<>();
    // 创建一个生产者
    static class Productor implements Runnable {
        @Override
        public void run() {
            // 生产10道菜
            for (int i = 0; i < 10; i++) {
                Food2 food2 = new Food2("红烧肉" + i);
                System.out.println("生产者:生产出一道" + food2.getName());
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                     // 将这道菜添加到队列(类似于将菜放到了取菜口, 让用户取餐)
                    q.put(food2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    // 创建一个消费者
    static class Consumer implements Runnable {
        @Override
        public void run() {
           // 不停的消费
            while (true) { // 这里写了一个死的
                try {
                    // 从队列中取数据(消费者到取餐口取餐,进行消费)
                    Food2 food = q.take(); // 这个take方法, 如果队列中没有数据,则会阻塞
                    Thread.sleep(500);
                    System.out.println("消费者:消费了一道" + food.getName());
                } catch (InterruptedException e) {
                   e.printStackTrace();
               }
            }
        }
    }
    public static void main(String[] args) {
        // 创建任务
        Productor p = new Productor();
        Consumer c = new Consumer();
        // 创建线程
        Thread t1 = new Thread(p);
        Thread t2 = new Thread(c);
        t1.start();
        t2.start();

    }
}

// 运行结果只 截取了一部分查看
2) 使用synchronized 和 linkedlist 队列
public class ProductorAndConsumer2 {
    // 创建一个队列
    private static final Queue q2 = new linkedList<>();
    private static final int MAX_CAPACITY = 1; // 仓库最大容量
    // 创建一个生产者
    static class Productor implements Runnable {
        public void run() {
           synchronized (q2) {
               // 生产10道菜
               for (int i = 0; i < 10; i++) {
                   if (q2.size() == MAX_CAPACITY) { // 仓库已满
                       try {
                           // 让当前线程等待
                           q2.wait();
                       } catch (InterruptedException e) {
                           e.printStackTrace();
                       }
                   }
                   Food2 food2 = new Food2("红烧肉" + i);
                   System.out.println("生产者:生产出一道" + food2.getName());
                   try {
                       Thread.sleep(500);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   // 将这道菜添加到队列(类似于将菜放到了取菜口, 让用户取餐)
                   q2.add(food2);
                   // 唤醒其他线程
                   q2.notify();

               }
           }
        }
    }

    // 创建一个消费者
    static class Consumer implements Runnable {
        @Override
        public void run() {
           // 不停的消费
            while (true) {
               synchronized (q2) {
                   if (q2.size() == 0) {
                       try {
                           // 当前线程等待
                           q2.wait();
                       } catch (InterruptedException e) {
                           e.printStackTrace();
                       }
                   }
                   // 从队列中取数据(消费者到取餐口取餐,进行消费)
                   Food2 food = q2.poll();
                   try {
                        Thread.sleep(500);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   System.out.println("消费者:消费了一道" + food.getName());
                   q2.notify(); // 唤醒其他线程
               }
            }
        }
    }
    public static void main(String[] args) {
        // 创建任务
        Productor p = new Productor();
        Consumer c = new Consumer();
        // 创建线程
        Thread t1 = new Thread(p);
        Thread t2 = new Thread(c);
        t1.start();
        t2.start();

    }
}
3)总结
  • 使代码块保持简短。把不随线程变化的预处理和后处理移出synchronized 块。

  • 不要阻塞。如InputStream.read()。

  • 在持有锁的时候,不要对其它对象调用方法。

  • 如果同步过多可能会造成死锁

  • sleep 与 wait 的区别?

    • sleep: 让线程进入休眠状态, 让出CPU时间片, 不释放对象的监视器的所有权(对象锁不会被释放)
    • wait: 让线程进入等待状态, 让出CPU时间片, 释放对象监视器的所有权, 等待其他线程通过notify来唤醒
(8) 线程池 1) 概述
  • 线程池是预先创建线程的一种技术, 线程池在任务五还没有到来之前, 创建一定数量的线程, 放入空闲队列中,

    然后对这些线程进行复用, 减少频繁的创建和销毁对象。

  • 在JDK1.5 以后提供了线程的线程池

  • Executor 是线程池的顶级接口

  • 具体使用的是Executor 下的子接口 ExecutorService;

  • Executor 接口: 只有一个方法, 就是执行已经提交的Runnable任务对象

  • ExecutorService 接口: 提供了很多方法,来 *** 作先成功池

  • 具体的实现类是Executors 类

  • 它们都在java.util.concurrent 包下

  • 下面展示两个比较常用的线程池做介绍, 其他线程池的使用都差不多

2) newSingleThreadExecutor 类 创建单线程线程池
  • 创建一个单线程线程池, 这个线程池只有一个线程工作, 也就是单线程串行执行所有任务(按顺序一个一个执行),

    如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此 线程池保证所有任务的执行顺序按照任务的提交顺序执行。

  • 代码

public class ExecutorDemo {
    public static void main(String[] args) {
        // 创建一个单线程的线程池
         ExecutorService es = Executors.newSingleThreadExecutor();

        // 让线程执行指定任务
        es.execute(new MyRunnable7());
        es.execute(new MyRunnable7());
        // 终止线程池
        es.shutdown();
    }
}
class MyRunnable7 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("run--" + i);
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

3) newFixedThreadPool 类 创建固定大小的线程池
  • 创建固定大小的线程池。每次提交一个任务就创建一个线程,直 到线程达到线程池的最大大小。

    如果某个线程因为执行异常而结束, 那么线程池会补充一个新线程。

  • 代码

 public static void main(String[] args) {
     // 创建一个指定线程数量的线程池
     ExecutorService es = Executors.newFixedThreadPool(2);

     // 让线程执行指定任务
     es.execute(new MyRunnable7());
     es.execute(new MyRunnable7());
     // 终止线程池
     es.shutdown();
 }
class MyRunnable7 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("run--" + i);
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5697435.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存