1. 多线程编程
2. Thread和Runnable
java中实现多线程的方式有两种,继承Thread类、实现Runnable接口
2.1 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执行了"); } }
2.2 Runnable
除了继承Thread重写run方法外,在简单的情况下,还可通过实现Runnable接口的方式编写线程执行的代码
Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("Runnable接口方式实现多线程"); } });
3.线程安全问题
一个数据,如一个对象或对象中的某个字段,如果有多个线程可以同时访问它,就可能会出现线程安全问题:数据错乱、程序出错或其他无法预知的问题
比如线程1要遍历一个list,线程2要把这个list清空,如果这两个线程同时执行就可能会出现线程安全问题
public class ThreadQuestionTest { 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() { for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { list.clear(); } }); thread1.start(); thread2.start(); } }
输出:
0 null null null null ...
4. 线程同步
线程同步控制,即使用某种方式使得一个线程在操作完某个数据前,别的线程无法操作这个数据,从而避免多个线程同时操作一个数据,进而避免线程安全问题
线程同步控制的方式有同步锁机制、等待/通知机制、信号量机制等,它们应用在不同复杂度的场景下
4.1同步代码块
synchronized同步锁机制
Java中每个对象都有一把锁,同一时刻只能有一个线程持有这把锁。线程可以使用synchronized关键字向系统申请某个对象的锁,得到锁之后,别的线程再申请该锁时,就只能等待。持有锁的线程在这次操作完成后,可以释放锁,以便其他线程可以获得锁
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(); } }
4.2 同步方法
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(); } }
非静态同步方法申请的锁是类的当前对象的锁,静态同步方法申请的锁是类的Class对象的锁。同步方法执行完后即向系统归还锁
synchronized代码块和synchronized方法的效果一样,可根据具体场景灵活选用
对于简单的需要线程同步控制的应用场景,synchronized基本够用
但需要注意,所有需要同步的线程必须都申请同一个对象的锁,当申请不同的锁或者有的线程没有使用synchronized时,同步锁机制就会失效
5. 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(); } } }); thread2.start(); thread1.start(); } }
5.1 wait/notify-生产者消费者实例
一个很典型的生产者消费者例子:现有一个生产者、一个消费者、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(); } } } } } }