程序、进程、线程的基本概念

程序

是为了完成特定的任务,用某种语言编写的一组指令的集合。即指一段特定的静态的代码。

进程

正在运行中的程序,(程序的一次执行过程),加载在内存中,操作系统会为进程分配内存空间,是动态(交换数据)的。

线程

进程进一步划分为线程,是程序内部的一条执行路径。并行的去执行多条线程,称之为多线程。
(新建一条线程:主程序新开一条线程,并行的执行)

进程和线程的关系:一个进程可能会有多个线程,多个线程共享进程的内存资源。

线程的创建和使用

1. 声明一个类,继承Thread类,重写run()方法

  1. /**
  2. * 多线程的创建
  3. * 方式一
  4. * 继承 Thread
  5. * 重写 run方法
  6. * 调用 start方法启动
  7. *
  8. * 例子:遍历100以内的偶数
  9. */
  10. //1. 继承 Thread 类
  11. class MyThread extends Thread{
  12. //2. 重写 run 方法
  13. @Override
  14. public void run() {
  15. for (int i = 1; i <= 100; i++){
  16. if(i % 2 == 0){
  17. System.out.println(i+Thread.currentThread().getName());
  18. }
  19. }
  20. }
  21. }
  22. public class ThreadTest {
  23. public static void main(String[] args) {
  24. //3. 创建对象
  25. MyThread t1 = new MyThread();
  26. //4. 开始线程
  27. t1.start();
  28. }
  29. }

调用start()方法会自动调用该线程的run()方法

问题1 :那么能否不调用satrt()方法,直接去调用run()方法呢?

不可以!!!!

直接调用run()方法时,实际上还是main线程
Thread.currentThread().getName() 可以使用这个方法获取线程名字测试一下

问题2 : 能使用start方法,再创建一个线程吗?

不可以!!!!!
一个线程只能开启一次,二次调用 start 则会报 java.lang.IllegalThreadStateException 异常

Thread 的常见方法
  1. /**
  2. * Thread 的常用方法
  3. * 1. start() 启动线程
  4. * 2. run() 当前线程要执行的操作
  5. * 3. getName() 获取线程的名字
  6. * 4. currentThread() 静态方法,返回当前执行的线程
  7. * 5. setName() 设置线程的名字
  8. * 6. yield() 释放cpu执行权
  9. * 7. join() 线程a中调用线程b的join方法,a进入阻塞状态,直到线程b执行完毕,然后a继续执行
  10. * 8. stop() 强制线程结束,已过时
  11. * 9. sleep() 让当前线程阻塞等待多少秒
  12. */

线程的优先级

getPriority() 获取优先级
setPriority() 设置优先级

优先级:::

Thread.MAX_PRIORITY 10

Thread.NORM_PRIORITY 5 默认情况下

Thread.MAX_PRIORITY 1

我们设置的优先级只是我们想要某些线程先执行,java会给他的资源更多一些,但是cpu其实也是有可能去调度低的线程。

2. 实现Runnable接口

  1. /**
  2. * 创建多线程的方式二:实现Runnable 接口
  3. * 1. 创建一个实现Runnable接口的类
  4. * 2. 实现类去实现Runnable接口的抽象方法: run()
  5. * 3. 创建实现类的对象
  6. * 4. 将此对象作为参数传递到Thread类的构造器,创建Thread类的对象
  7. * 5. 调用start()
  8. */
  9. class MThread implements Runnable{
  10. @Override
  11. public void run() {
  12. for (int i = 0; i < 100; i++) {
  13. if(i % 2 == 0){
  14. System.out.println(i);
  15. }
  16. }
  17. }
  18. }
  19. public class RunnableTest {
  20. public static void main(String[] args) {
  21. MThread t1 = new MThread();
  22. //调用的是当前线程的run --》(但是调用了Runnable类型的run方法)
  23. new Thread(t1).start();
  24. }
  25. }

两种方法的比较

Thread类其实也实现了Runnable接口,所以比较推荐第二种,同时第二种方式也打破了extends的单继承性

线程的生命周期

Thread.State 的变量指代表线程的状态

New:新建,当一个Thread类或其子类的对象被创建时,新生的线程对象处于新建状态

就绪,当线程被satrt()后,将进入线程队列等待cpu的调用

运行,当cpu调用该线程的时候

阻塞,当该线程被人为挂起或执行输入输出操作时,中断自己的执行,让出cpu控制权

死亡,线程完成了自己的任务,或被强制结束,或异常退出后进入死亡状态

线程的一些常用的方法示例

线程停止 stop

不推荐使用Thread 类的stop()方式来进行线程停止,在java8版本就已经表示出要被废弃了
最好是等待线程自己停止,如有需要我们可以自己设置一个标记位来让线程停止

  1. public class ThreadTest implements Runnable{
  2. private boolean flag = true;
  3. @Override
  4. public void run() {
  5. int i = 0;
  6. while (flag) {
  7. System.out.println("thread run...." + i++);
  8. }
  9. }
  10. public void stop() {
  11. this.flag = false;
  12. }
  13. public static void main(String[] args) {
  14. ThreadTest threadTest = new ThreadTest();
  15. new Thread(threadTest).start();
  16. for (int i = 0; i < 999; i++) {
  17. System.out.println("main ..." + i);
  18. if (i == 900) {
  19. threadTest.stop();
  20. System.out.println("threadTest stop..........");
  21. }
  22. }
  23. }
  24. }

线程休眠 sleep

sleep参数以毫秒为单位,可以用来模拟延时之类的,调用sleep后,线程进入指定时间的阻塞状态,sleep时间到达之后线程会进入就绪态。
如果当前线程有锁的话,sleep并不会释放锁。

线程礼让 yield

该方法可以释放cpu,重新进入就绪态,注意是就绪态不是阻塞态!!(就是谦让一下,我现在抢到cpu了,但我谦让一下,我退出来,咱们再抢一次)。
值得注意的是礼让不一定成功,这确实得看cpu的心情,有可能该线程 yield后,又会被cpu调度。

线程插队 join
如果当前cpu正在调度线程A,在A中调用B的join方法。这个时候线程B会进入cpu,A进入阻塞状态,直到B执行完毕,A被唤醒。
举个例子就会明白

public class JoinTest implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程vip来了:" + i);
        }
    }

    public static void main(String[] args) throws InterruptedException {

        JoinTest joinTest = new JoinTest();
        Thread thread = new Thread(joinTest);
        thread.start();

        for (int i = 0; i < 200; i++) {
            System.out.println("main:"+i);
            if (i == 50) {
                //这个时候主线程会进入阻塞态,直到调用join的线程执行完毕
                thread.join();
                System.out.println("joinTest thread is died");
            }
        }
    }

}

线程的同步

线程的安全问题与解决

问题:卖票过程中出现重票、错票—>出现了线程安全问题

问题出现的原因:当某个线程在操作车票的过程中,尚未操作完成,其他线程参与进来也操作车票

解决:在一个线程a在操作ticket的时候,其他线程不能参与进来,直到线程a操作完ticket之后,其他线程才可以操作ticket。
即使线程a出现了阻塞,也不能被改变

在java中我们通过同步机制来解决线程安全问题

方式一:同步代码块

/*
    synchronized(同步监视器){
        需要被同步的代码
    }   

    说明:
    1。操作共享数据的代码,即为被同步的代码
    2。共享数据:多个线程共同操作的数据
    3。同步监视器,俗称:锁。任何一个类的对象都可以充当锁
        要求:多个线程必须共用同一个锁(同一个对象)
*/
public class WindowRunnableTest {
    public static void main(String[] args) {
        WindowRunnable windowRunnable = new WindowRunnable();

        Thread window1 = new Thread(windowRunnable);
        Thread window2 = new Thread(windowRunnable);
        Thread window3 = new Thread(windowRunnable);

        window1.setName("窗口1");
        window2.setName("窗口2");
        window3.setName("窗口3");

        window1.start();
        window2.start();
        window3.start();

    }
}
class WindowRunnable implements Runnable{

    private int ticket = 100;
    //锁
    Object obj = new Object();

    @Override
    public void run() {
        while (true){
            synchronized (this){//synchronized (obj) 或者synchronized (WindowRunnable.class){ 都可以

                if(ticket > 0){

                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName()+": 卖票,票号为"+ticket);

                    ticket--;
                }else{
                    break;
                }
            }
        }
    }
}

方式二:同步方法
将被同步的代码提取出来封装成一个方法

    //同步方法中,同步监视器为this
    //如果同步方法被static修饰 同步监视器就为类名.class
    private synchronized void show(){
        if(ticket > 0){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName()+": 卖票,票号为"+ticket);

            ticket--;
        }
    }

好处:解决了线程的安全问题

坏处:运行慢,变成了串行,操作同步代码时,只能有一个线程参与,相当于单线程的过程,有局限性

懒汉式的线程安全问题

线程的通信

线程间有的时候是需要通信的,比如说最经典的生产者和消费者问题,消费者看到商品里没有货物的时候
,就不再去购买货物了,生产者看到商店货物满了的时候,就不再去生产商品了,假设现在有两个线程,
一个是生产者线程,一个是消费者线程,那么谁来告诉他们商品的情况呢?

这个时候我们就需要使用,线程中的三个通信方法来进行通信了

  • wait():一旦执行此方法,线程进入阻塞,并且释放同步监视器
  • notify():一旦执行此方法,会唤醒一个线程,如果有多个线程,唤醒优先级最高的那个
  • notifyAll():一旦执行此方法,会唤醒所有的线程。
  • 说明,这三个方法,只能在同步代码块,或者同步方法当中,Lock有自己的wait
  • 必须由同一个同步监视器调用 wait 和 notify,否则会报异常 java.lang.IllegalMonitorStateException
/**
 * 两个线程循环打印 1-100
 */
public class CommunicationTest {

    public static void main(String[] args) {
        Number number = new Number();

        Thread thread1 = new Thread(number);
        Thread thread2 = new Thread(number);

        thread1.start();
        thread2.start();
    }
}

class Number implements Runnable{

    private int number = 1;

    @Override
    public void run() {
        while (true){

            synchronized (Number.class) {

                //唤醒一个线程
                Number.class.notify();

                //唤醒所有线程
                //Number.class.notifyAll();
                if (number <= 100){

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName()+"打印:"+number);
                    number++;

                    try {
                        //从就绪态进入阻塞态,而且会释放同步锁,只能由其他线程唤醒。
                        Number.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else {
                    break;
                }
            }
        }
    }
}

生产者消费者问题
/**
 * 生产者和消费者问题
 */
public class ProductorAndCustomer {

    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        Producer producer = new Producer(clerk);
        Consumer Consumer = new Consumer(clerk);

        Thread thread = new Thread(producer);
        Thread thread1 = new Thread(Consumer);

        thread.setName("生产者1");
        thread1.setName("消费者1");

        thread.start();
        thread1.start();


    }

}

class Clerk{

    private int productCount = 0;

    //生产产品,此时同步监视器是 this
    public synchronized void produceProduct() {

        if(productCount < 20){
            productCount++;
            System.out.println(Thread.currentThread().getName()+":开始生产第"+productCount+"个产品");

            notify();

        }else {
            try {
                System.out.println("仓库满20个");
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    //消费产品
    public synchronized void consumeProduct() {

        if(productCount > 0){
            System.out.println(Thread.currentThread().getName()+":开始消费第"+productCount+"个产品");
            productCount--;

            notify();
        }else {
            try {
                System.out.println("没有商品了");
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//生产者
class Producer implements Runnable{

    private Clerk clerk;

    public Producer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+":开始生产");

        while (true){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            clerk.produceProduct();
        }
    }
}

//消费者
class Consumer implements Runnable{

    private Clerk clerk;

    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+":开始消费");
        while (true){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            clerk.consumeProduct();
        }

    }
}

jdk 5.0 新增的创建多线程的方式

Callable

创建多线程的第三种方式,实现Callable 接口
这中方式较之前实现Runnable 的方式相比。多了返回值
但是需要知道,Callable并不是Thread 的子类,所以,想要启动的话,需要借助 Future接口
Future 接口中有唯一的实现类 FutureTask,同时FutureTask也实现了Runnable接口

Callable 的优点

  • 可以返回值
  • 可以抛出异常,被外面的操作捕获
  • 支持范型
public class CallableNewThread {

    public static void main(String[] args) {

        NewThread newThread = new NewThread();

        FutureTask<Integer> futureTask = new FutureTask<>(newThread);

        new Thread(futureTask).start();

        try {
            //futuretask 的get方法会去调用->构造参数传入的对象的call(回调)方法
            Integer sum = futureTask.get();
            System.out.println(sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}


class NewThread implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 100; i++){
            if (i % 2 == 0){
                System.out.println(i);
                sum += i;
            }
        }

        return sum;//自动装箱,变为Integer类型
    }
}

线程池

线程池的好处

  • 提高响应速度,减少了创建线程的时间
  • 降低资源消耗,重复利用线程池中的线程
  • 便于线程管理
public class ThreadPool {

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

        service.execute(() -> {
            for (int i = 0; i < 100; i++){
                if (i % 2 == 0)
                    System.out.println(Thread.currentThread().getName()+":打印-" + i);
            }

        });//适合使用Runnable,没有返回值
        Future<Integer> future = service.submit(() -> {
            int sum = 0;
            for (int i = 0; i < 100; i++) {
                if (i % 2 != 0) {
                    sum += i;
                    System.out.println(Thread.currentThread().getName() + ":打印-" + i);
                }
            }

            return sum;
        });//适合使用于Callable,返回Future

        Integer integer = null;
        try {
            integer = future.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println(integer);

        //关闭线程池
        service.shutdown();
    }
}

如果需要对线程池进行设置的话,不能直接使用ExecuteService 对象,因为这是一个接口
我们可以将它进行强转(向下转型),找它的实现类,ThreadPollExecutor,然后进行相关设置