关于多线程编程的总结

  • 多线程编程的知识结构图

  • 进程的概念

  在操作系统中,进程是程序的一次执行。比如当双击某个可执行文件后,系统就创建一个进程专门执行这个程序的代码,在执行过程中,进程会申请、持有或释放操作系统资源(文件、内存等)

  时间片轮询算法

  某一时刻,操作系统中可有多个进程存在,但只能有一个进程在执行。也就是说某一时刻CPU只能处理一个进程,其余进程则等待操作系统调度

   操作系统调度进程的一种方式是时间片轮询算法:

  1 分配给当前正在执行的进程一个很短的时间片,当前进程用完时间片后就被暂停,并被放入就绪队列。

  2 然后操作系统从就绪队列中以轮询的方式随机选择一个进程来执行。轮询方式可使调度尽可能公平,但轮询是不严格的,各进程被调度的次数并不一样

   另外,如果正在执行的进程申请了某个资源,但没有立即得到这个资源,进程就会暂停执行,并被放入阻塞队列。获得资源后,这个进程会被加入到就绪队列等待调度

   由于计算机运算速度非常快,分配给进程的时间片又非常短,给人的感觉就好像在某一时刻,多个进程(程序)在同时执行

  • 线程的概念

  线程和进程非常相似,又被称为轻量级进程。一个进程可拥有多个线程,这些线程共享此进程所持有的系统资源

   现代操作系统中,调度、执行的基本单位变成了线程,进程则还是资源分配的基本单位。由于线程本身几乎不持有系统资源,在调度时系统开销就很小(但不同进程间的线程调度时,系统开销仍然很大)

   操作系统可以拥有多个进程,感觉就像多个程序同时在执行;进程可以拥有多个线程,感觉就像一个程序可以同时做多件事情

  • Thread

  多线程编程是指让程序使用多个线程同时分别做一件事情的不同部分,或者同时做不同的事情。但并不是所有的事情都适合多线程,多线程编程的目的是提高程序执行效率、提高人们工作效率等

   Thread类用来创建线程对象,编写线程执行的代码、控制线程等,开发人员可以编写一个类继承Thread,并重写run方法,在run方法里面编写线程将要执行的代码

   创建线程对象后,只需要调用start()方法即可让线程进入就绪队列,等待操作系统调度

   需要特别注意的是调度具有随机性和随时性。也就是说无法确定下一次调度哪个线程,也无法确定什么时刻进行调度

public class MyThreadTest {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();//把myThread加入到就绪队列里面
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("MyThread执行了");
    }
}
  • Runnable接口

  除了继承Thread重写run方法外,在简单的情况下,还可通过实现Runnable接口的方式编写线程执行的代码

Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Runnable接口方式实现多线程");
    }
});
  • 线程状态

  线程的整个生命周期过程可能会经过多次状态转变:

  • 初始状态:创建了一个线程对象
  • 就绪状态:线程具备运行的所有条件,在就绪队列中,在等待操作系统调度
  • 运行状态:线程正被CPU处理
  • 阻塞状态:线程在等待一个事件,逻辑上不可执行,在阻塞队列中
  • 结束状态:线程执行结束,也就是run方法执行结束

  • 同步代码块

  线程同步控制,即使用某种方式使得一个线程在操作完某个数据前,别的线程无法操作这个数据,从而避免多个线程同时操作一个数据,进而避免线程安全问题

  线程同步控制的方式有同步锁机制、等待/通知机制、信号量机制等,它们应用在不同复杂度的场景下

  synchronized同步锁机制:Java中每个对象都有一把锁,同一时刻只能有一个线程持有这把锁。线程可以使用synchronized关键字向系统申请某个对象的锁,得到锁之后,别的线程再申请该锁时,就只能等待。持有锁的线程在这次操作完成后,可以释放锁,以便其他线程可以获得锁

   synchronized有两种形式,synchronized代码块和synchronized方法

public class SynchronizedBlockTest {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<Integer>();
        for (int i = 0; i < 100000; i++) {
            list.add(i);
        }
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (list) {
                    for (int i = 0; i < list.size(); i++) {
                        System.out.println(list.get(i));
                    }
                }
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (list) {
                    list.clear();
                }
            }
        });
        thread1.start();
        thread2.start();
    }
}
  • 同步方法

  非静态同步方法申请的锁是类的当前对象的锁,静态同步方法申请的锁是类的Class对象的锁。同步方法执行完后即向系统归还锁

   synchronized代码块和synchronized方法的效果一样,可根据具体场景灵活选用

   对于简单的需要线程同步控制的应用场景,synchronized基本够用

   但需要注意,所有需要同步的线程必须都申请同一个对象的锁,当申请不同的锁或者有的线程没有使用synchronized时,同步锁机制就会失效

public class SynchronizedMethodTest {

    public static void main(String[] args) {
        Data data = new Data();
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                data.bianliList();
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                data.clearList();
            }
        });
        thread1.start();
        thread2.start();
    }
}

class Data {
    private List<Integer> list;
    public Data() {
        list = new ArrayList<Integer>();
        for (int i = 0; i < 100000; i++) {
            list.add(i);
        }
    }
    public synchronized void bianliList() {
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }
    public synchronized void clearList() {
        list.clear();
    }
}
  • wait/notify 等待/通知机制

  对于稍复杂的情况,比如多个线程需要相互合作有规律的访问共享数据,就可以使用wait/notify机制,即等待/通知机制,也称等待/唤醒机制

   等待/通知机制建立在synchronized同步锁机制的基础上,即在同步代码块(或同步方法)内,如果当前线程执行了lockObject.wait()(lockObject表示提供锁的对象),则当前线程立即暂停执行,并被放入阻塞队列,并向系统归还所持有的锁,并在lockObject上等待,直到别的线程调用lockObject.notify()

   如果有多个线程在同一个对象上等待,notify()方法只会随机通知一个等待的线程,也可以使用notifyAll()方法通知所有等待的线程。被通知的线程获得锁后会进入就绪队列

public class WaitNotifyTest {
    public static void main(String[] args) {
        Object lockObject = new Object();
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lockObject) {
                    try {
                        System.out.println("线程1即将开始在lockObject上等待");
                        lockObject.wait();
                        System.out.println("线程1收到通知并获得锁,开始继续执行");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lockObject) {
                    System.out.println("线程2将随机通知在lockObject上等待的线程");
                    lockObject.notify();
                }
            }
        });
        thread1.start();
        thread2.start();
    }
}
  • 生产者与消费者模式

  一个很典型的生产者消费者例子:现有一个生产者、一个消费者、10个盘子(缓冲区),生产者把生产的产品放入空盘子中,当没有空盘子时就停止生产;消费者消费盘子中的产品,当所有的盘子都是空盘子时就停止消费

public class ProducerConsumerTest{
    public static void main(String[] args) {
        List<Integer> buffer = new LinkedList<Integer>();
        int maxSize = 10;

        Producer producer = new Producer(buffer, maxSize);
        Consumer consumer = new Consumer(buffer);

        producer.start();
        consumer.start();
    }
}
//模拟生产者
class Producer extends Thread {

    private List<Integer> buffer; //缓冲区,表示多个盘子
    private int maxSize; //表示盘子个数
    public Producer(List<Integer> buffer, int maxSize) {
        this.buffer = buffer;
        this.maxSize = maxSize;
    }
    @Override
    public void run() {
        int id = 0;
        while (true) {
            synchronized (buffer) {
                if (buffer.size() < maxSize) {//有空盘子则继续生产
                    id++;//表示生产了一个产品
                    buffer.add(id); //表示把产品放入一个空盘子中
                    System.out.println("生产产品" + id + "并通知消费者可以消费了");
                    buffer.notify(); //通知消费者有产品可以消费了
                } else {
                    //如果没有空盘子则等待
                    System.out.println("没有空盘子了,生产者停止生产产品");
                    try {
                        buffer.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

//模拟消费者
class Consumer extends Thread {
    private List<Integer> buffer; //缓冲区,表示多个盘子
    public Consumer(List<Integer> buffer) {
        this.buffer = buffer;
    }
    @Override
    public void run() {
        while (true) {
            synchronized (buffer) {
                if (buffer.size() > 0) { //有不空的盘子
                    int id = buffer.remove(0); //表示消费了一个产品
                    System.out.println("消费产品" + id + "并通知生产者有空盘了");
                    buffer.notify();
                } else {
                    //全部都是空盘子则等待
                    System.out.println("全部都是空盘子,消费者停止消费产品");
                    try {
                        buffer.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
  • 守护线程

  守护线程是一种特别的线程,主要为同一进程中的其他线程提供服务,而且经常使用无限循环持续提供服务。

  当所属的进程中所有的非守护线程都执行完时,所有的守护线程也随即结束,整个进程也将结束(调用System.exit()可使程序直接结束)

  在调用start()前调用setDaemon(true)可将一个线程设置为守护线程

public class DaemonTest {

    public static void main(String[] args) {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("守护线程主要是提供某种服务");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        thread1.setDaemon(true);
        thread1.start();

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread2.start();
    }
}
  • interrupt

  如果线程2可以访问线程1对象本身,就可以通过调用线程1对象的interrupt()方法来打断处于阻塞状态(sleep、wait)的线程1,使之从阻塞队列转移到就绪队列(sleep)或者转移到另外的阻塞队列(wait)

public class InterruptTest {

    public static void main(String[] args) {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程1即将进入阻塞状态");
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    System.out.println("线程1被打断,开始执行catch里面的代码");
                    e.printStackTrace();
                }
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                thread1.interrupt();
            }
        });
        thread1.start();
        thread2.start();
    }
}

猜你喜欢

转载自www.cnblogs.com/zhuchaoli/p/10390998.html
今日推荐