问题:
多线程的程序到底是并行还是并发?
并行:宏观和微观都是同时进行。
并发:宏观上同时进行,微观是交替进行。
如果电脑是单个CPU单核,不可能实现并行。
如果电脑是多个CPU或者多核,并且操作系统也支持多任务的操作系统,才有可能支持并行。
目前大家的电脑都是可以支持并行的。
目前因为我们两个线程都在IDEA的控制台输出信息,控制台只有一个,所以一次只能接收一个线程的输出数据,所以会出现交替的情况。
总结:是否能够并行看三个要素:
(1)CPU
(2)操作系统
(3)多个线程使用的资源是否有多个,如果大家用一个也是不能并行的
一、多线程的生产者与消费者问题
1、什么是生产者与消费者问题?
有的时候任务需要多个线程来一起完成,一些线程是负责“产生/生产”数据的,一些线程是负责“消费/消耗”数据的。
数据缓冲区的大小毕竟是有限的,生产者不能无限制的往缓冲区填充数据,
当缓冲区“满”的时候,“生产者”线程应该停下来“wait”等待,等待“消费者”线程取走一些数据之后,重新“唤醒/通知”“生产者”继续生产;
反过来,如果缓冲区里面没有数据,即是“空”,
“消费者”线程此时应该停下来“wait”等待,等待“生产者”先生成一些数据之后,重新“唤醒/通知”“消费者”继续消费;
2、具体的问题:
(1)线程安全问题?有 ==> 如何解决? ==>使用锁,目前学过的是同步锁synchronized
如何判断一个多线程的程序是否有线程安全问题?
A:是否有多个线程同时运行(无论并行还是并发,只要是宏观角度是同时进行)
B:这个多个线程是否使用了“共享”数据
C:多个线程是否既有读操作又有写操作
(2)协作问题即通信问题?有
多个线程需要“等待”和“唤醒”操作。
==>如何解决? 使用Object类看到过的 wait()、 wait(long timeout) 、 wait(long timeout, int nanos)
notify() 、notifyAll()
IllegalMonitorStateException:非法监视器对象异常。当wait和notify等这些方法被调用时,必须确保它们是由“锁”对象调用的。否则就会报这个异常。
什么是监视器对象?其实就是“锁”对象。
3、例如:二虎同学觉得写代码太难了,不打算做程序员了,和他媳妇(翠花)准备开一个饭馆。
二虎负责在厨房做菜,他媳妇负责在前厅招待客户。
厨房与前厅中间有一个窗口(工作台),当二虎炒好一盘菜之后,就把菜放到“工作台”,
他媳妇就可以端给客户。
菜比喻成数据。 “工作台”比喻成数据缓冲区。
二虎相当于生产者,他负责把数据放到“工作台”上。
他媳妇(翠花)相当于消费者,她负责把“工作台”上的数据取走。
当“工作台”满的时候,二虎应该停下来,等他媳妇(翠花)取走一些菜之后再唤醒他;
当“工作台”空的时候,他媳妇应该停下来,等他炒好新的菜之后再唤醒她。
public class TestCommunicate {
public static void main(String[] args) {
Workbench workbench = new Workbench();
Cook cook = new Cook("二虎",workbench);
Waiter waiter = new Waiter("翠花",workbench);
cook.start();
waiter.start();
}
}
//Cook代表厨师
class Cook extends Thread{
private Workbench workbench;
public Cook(String name, Workbench workbench) {
super(name);
this.workbench = workbench;
}
@Override
public void run() {
while(true){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
workbench.put();
}
}
}
//Waiter代表服务员
class Waiter extends Thread{
private Workbench workbench;
public Waiter(String name, Workbench workbench) {
super(name);
this.workbench = workbench;
}
@Override
public void run() {
while(true){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
workbench.take();
}
}
}
//Workbench工作台
class Workbench{
private static final int MAX_VALUE = 10;//工作台上最多能放MAX_VALUE盘菜
private int total;//记录工作台上的菜的数量
//同步方法,非静态方法的锁对象默认是this
public synchronized void put(){
if(total >= MAX_VALUE){
//如果当前方法是非静态的同步方法,那么wait方法就默认由this调用即可,
//如果当前方法是静态的同步方法,那么wait方法就要由 “当前类名.class”调用即可
//如果当前方法不是同步方法,是同步代码块,synchronized (锁对象){} 那么就由同步代码块指定的“锁对象“调用调用
try {
wait();//默认现在是this调用wait 虽然是this对象在调用wait方法,但是要看是在哪个线程中调用的,哪个线程调用,就会导致哪个线程“等待”
} catch (InterruptedException e) {
e.printStackTrace();
}
}
total++;
System.out.println(Thread.currentThread().getName() + "生产一份菜,现在剩余:" + total);
notify();//默认现在是this调用notify
}
public synchronized void take(){
if(total <= 0){
try {
wait();//默认现在是this调用wait 哪个线程对象调用,就是哪个线程停下来
} catch (InterruptedException e) {
e.printStackTrace();
}
}
total--;
System.out.println(Thread.currentThread().getName() + "取走了一份菜,现在剩余:" + total);
notify();//默认现在是this调用notify
}
}
4、问题升级一下
二虎和他媳妇挣到钱了,扩张一下,雇了其他的厨师和服务员,也就是生产者和消费者不是一对一的关系了,
而是多个生产者和多个消费者的情况。
public final void notify():唤醒在此对象监视器上等待的单个线程。
public final void notifyAll():唤醒在此对象监视器上等待的所有线程。
无论是wait还是notify和notifyAll都要由对象监视器(即锁对象)来调用的原因,
就是我们要区别哪些线程是和我们是一组“生产者消费者”。
如果说JAVA程序中有多组的“生产者消费者”,大家各种完成的任务是不同的,
那么要通过锁区分。只有使用相同锁的,才会去唤醒它,否则和我们无关。
void wait():无限等待,直到有人唤醒它
void wait(long timeout) :限时等待,不需要唤醒也可以在时间到了自动醒来
void wait(long timeout, int nanos) :限时等待,不需要唤醒也可以在时间到了自动醒来
5、为什么wait方法和notify系列的方法,不放在Thread类中,而是在Object类中?
因为我们的wait和notify和notifyAll这些方法必须由“锁”对象调用,
而“锁”对象可能是任意引用数据类型的对象,所以我要保证任意类型的对象都要有这个方法,
所以只能放在Object类中。
如果放在Thread类中,只能由Thread类的对象来调用。
public class TestCommunicate {
public static void main(String[] args) {
Workbench workbench = new Workbench();
Cook cook = new Cook("二虎",workbench);
Waiter waiter = new Waiter("翠花",workbench);
Cook cook2 = new Cook("邱世玉",workbench);
Waiter waiter2 = new Waiter("如花",workbench);
cook.start();
waiter.start();
cook2.start();
waiter2.start();
}
}
//Cook代表厨师
class Cook extends Thread{
private Workbench workbench;
public Cook(String name, Workbench workbench) {
super(name);
this.workbench = workbench;
}
@Override
public void run() {
while(true){
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
workbench.put();
}
}
}
//Waiter代表服务员
class Waiter extends Thread{
private Workbench workbench;
public Waiter(String name, Workbench workbench) {
super(name);
this.workbench = workbench;
}
@Override
public void run() {
while(true){
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
workbench.take();
}
}
}
//Workbench工作台
class Workbench{
private static final int MAX_VALUE = 1;//工作台上最多能放MAX_VALUE盘菜
//为了让问题暴露的更快,更明显,我暂时先把MAX_VALUE修改为1
private int total;//记录工作台上的菜的数量
//同步方法,非静态方法的锁对象默认是this
public synchronized void put(){
// if(total >= MAX_VALUE){
while(total >= MAX_VALUE){
//如果当前方法是非静态的同步方法,那么wait方法就默认由this调用即可,
//如果当前方法是静态的同步方法,那么wait方法就要由 “当前类名.class”调用即可
//如果当前方法不是同步方法,是同步代码块,synchronized (锁对象){} 那么就由同步代码块指定的“锁对象“调用调用
try {
wait();//默认现在是this调用wait 虽然是this对象在调用wait方法,但是要看是在哪个线程中调用的,哪个线程调用,就会导致哪个线程“等待”
// wait(5000);//默认现在是this调用wait 虽然是this对象在调用wait方法,但是要看是在哪个线程中调用的,哪个线程调用,就会导致哪个线程“等待”
} catch (InterruptedException e) {
e.printStackTrace();
}
}
total++;
System.out.println(Thread.currentThread().getName() + "生产一份菜,现在剩余:" + total);
// notify();//默认现在是this调用notify 唤醒在此对象监视器上等待的单个线程。
notifyAll();//默认现在是this调用notify 唤醒在此对象监视器上等待的所有线程。
}
public synchronized void take(){
// if(total <= 0){
while(total <= 0){//循环
try {
wait();//默认现在是this调用wait 哪个线程对象调用,就是哪个线程停下来
// wait(5000);//默认现在是this调用wait 哪个线程对象调用,就是哪个线程停下来
} catch (InterruptedException e) {
e.printStackTrace();
}
}
total--;
System.out.println(Thread.currentThread().getName() + "取走了一份菜,现在剩余:" + total);
// notify();//默认现在是this调用notify
notifyAll();//默认现在是this调用notify
}
}
二、守护线程(了解)
有一种线程是只为其他线程服务的,不能独立存在的,当被服务的线程结束之后,它自动就结束了。
例如:Java后台的GC线程等都是守护线程。
public class TestDaemonThread {
public static void main(String[] args) {
MyThread my = new MyThread();
my.setDaemon(true);//把my线程对象变为守护线程
my.start();
for(int i=1; i<=5; i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main=" + i);
}
}
}
class MyThread extends Thread{
public void run(){
while(true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我默默的守护你");
}
}
}
三、死锁(了解)
总结:当两个或更多个线程,互相等待对方占有的锁时,就会发生死锁情况。
尽量避免嵌套同步或者加条件。
模拟死锁,讲个故事,二虎的女朋友翠花被绑架了,绑匪说要500万,给钱就放人。二虎说,你先放人我给你钱。
public class TestDead {
public static void main(String[] args) {
Object money = new Object();
Object girl = new Object();
Boy boy = new Boy(money,girl);
Bang bang = new Bang(money, girl);
boy.start();
bang.start();
}
}
class Boy extends Thread{
private Object money;
private Object girl;
public Boy(Object money, Object girl) {
this.money = money;
this.girl = girl;
}
public void run(){
synchronized (money) {
System.out.println("你先放人,我给你500万");
synchronized (girl){
System.out.println("给你500万,拜拜");
}
}
}
}
class Bang extends Thread{
private Object money;
private Object girl;
public Bang(Object money, Object girl) {
this.money = money;
this.girl = girl;
}
public void run(){
synchronized (girl) {
System.out.println("你先给我500万,我再放人,否则撕票");
synchronized (money){
System.out.println("给你人,谢谢");
}
}
}
}