java多线程编程

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();
    }
}
View Code

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();
    }
}
View Code

非静态同步方法申请的锁是类的当前对象的锁,静态同步方法申请的锁是类的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();
    }
}
View Code

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();
                    }
                }
            }
        }
    }
}
View Code
原文地址:https://www.cnblogs.com/renjing/p/thread.html