nispgithub.github.io

基本概念

程序、进程、线程

程序(program):是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。

进程(process):是程序的一次执行过程,或是正在运行的一个程序。动态过程:有它自身的产生、存在和消亡的过程。

线程(thread):进程可进一步细化为线程,是一个程序内部的一条执行路径。

多核CPU

单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。

如果是多核的话,才能更好的发挥多线程的效率,现在的服务器都是多核的。

多线程应用场合

线程的创建和使用

Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread类来体现。

Thread类的特性:

构造器:

相关方法:

创建线程的方式:

继承Thread类的

步骤:

  1. 定义子类继承Thread类;
  2. 子类重写Thread类中的run方法;
  3. 创建Thread子类对象,即创建线程对象;
  4. 调用线程对象start方法(启动线程,调用run方法)。
public class ChatThread extends Thread{
	@Override
	public void run() {
		for(int i = 0; i < 10; i++) {
			System.out.println("聊天");
			try {
				Thread.currentThread().sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

public class DrinkThread extends Thread {
	@Override
	public void run() {
		for(int i = 0; i < 10; i++) {
			System.out.println("喝酒");
			try {
				Thread.currentThread().sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

public class EatThread extends Thread{
	@Override
	public void run() {
		for(int i = 0; i < 10; i++) {
			System.out.println("吃饭");
			try {
				Thread.currentThread().sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

public class MyTest2 {
    public static void main(String[] args) {
        //创建线程
        ChatThread chat = new ChatThread();
		DrinkThread drink = new DrinkThread();
		EatThread drink = new EatThread();
		//启动线程
		chat.start();
		drink.start();
		drink.start();
    }
}

实现Runnable接口

步骤:

  1. 定义子类实现Runnable接口;
  2. 子类中重写Runnable接口中的run方法;
  3. 通过Thread类构造方法创建线程对象;
  4. Runnable接口的子类对象作为实际参数传递给Thread类的构造方法中;
  5. 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。
public class ChatThread implements Runnable {
	@Override
	public void run() {
		for(int i = 0; i < 100; i++) {
			System.out.println("聊天");
			try {
				Thread.currentThread().sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

public class DrinkThread implements Runnable {
	@Override
	public void run() {
		for(int i = 0; i < 100; i++) {
			System.out.println("喝酒");
			try {
				Thread.currentThread().sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

public class EatThread implements Runnable {
	@Override
	public void run() {
		for(int i = 0; i < 100; i++) {
			System.out.println("吃饭");
			try {
				Thread.currentThread().sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

public class MyTest2 {
    public static void main(String[] args) {
        Thread chat = new Thread(new ChatThread());
        Thread drink = new Thread(new DrinkThread());
        Thread eat = new Thread(new EatThread());

        chat.start();
        drink.start();
        eat.start();
    }
}

优势:

实现Callable接口

与使用Runnable相比, Callable功能更强大些

关于Future接口

import java.util.concurrent.Callable;

/**
 * 使用实现Callable接口的方式创建线程
 */
public class NumThread implements Callable<Integer> {

    //重写call方法
    @Override
    public Integer call() throws Exception {
        //1+2+3+4+...+100
        int sum = 0;
        for(int i = 1; i <= 100; i++) {
            sum += i;
        }
        return sum;
    }
}

public class MyTest8 {
    public static void main(String[] args) {
        NumThread numThread = new NumThread();
        FutureTask<Integer> task = new FutureTask<>(numThread);
        //创建线程
        Thread thread = new Thread(task);
        //启动线程
        thread.start();
        //设置线程优先级
        thread.setPriority(Thread.MAX_PRIORITY);

        try {
            Integer sum = task.get();
            System.out.println(sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

使用线程池

存在问题及解决思路

背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程, 对性能影响很大。

思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完 放回池中。可以避免频繁创建销毁、实现重复利用。

好处

线程池相关API

JDK 5.0起提供了线程池相关API:ExecutorServiceExecutors

ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

public class NumberThread implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            if(i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

public class NumberThread1 implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            if(i % 2 != 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}


public class MyTest9{
    public static void main(String[] args) {
        //创建线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        NumberThread numberThread = new NumberThread();
        NumberThread1 numberThread1 = new NumberThread1();

        //执行线程
        service.execute(numberThread);
        service.execute(numberThread1);

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

线程的调度和生命周期

线程的调度

调度策略:

调度方法

涉及的方法

public class MyTest3 {
    public static void main(String[] args) throws InterruptedException {
        Thread.currentThread().setName("主线程");

        MyThread mythread = new MyThread();
        mythread.setName("子线程");
        mythread.start();
        //设置线程的优先级
        mythread.setPriority(Thread.MAX_PRIORITY);
        //打印100行%%%%%
        for (int i = 0; i < 30; i++) {
            System.out.println("%%%%%" + Thread.currentThread().getName() + ":" + i);
            if(i == 2) {
                //Thread.currentThread().yield();
                //mythread.stop();
                mythread.join();
                System.out.println(mythread.isAlive());
            }
            try {
                Thread.currentThread().sleep(100);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

线程的生命周期

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

就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件;

运行:当就绪的线程被调度并获得处理器资源时,便进入运行状态, run()方法定义了线程的操作和功能;

阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态;

死亡:线程完成了它的全部工作或线程被提前强制性地中止。

线程同步

public class Window implements Runnable {
    private int count = 100;
    @Override
    public void run() {
        while(true) {
            if(count > 0) {
                try {
                    Thread.currentThread().sleep(6);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "卖第" + (count--) + "张票");
            } else {
                break;
            }
        }
    }
}

public class MyTest4 {
    public static void main(String[] args) {
        Window window = new Window();
        Thread window1 = new Thread(window);
        Thread window2 = new Thread(window);
        Thread window3 = new Thread(window);

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

现象

  1. 多个窗口卖同一张票;
  2. 票的编号出现了0或者负值。

问题的原因:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。

解决思路:对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。

Java对于多线程的安全问题提供了专业的解决方式:同步机制

synchronized代码块

public class Window1 implements Runnable {
	private int count = 100;
	@Override
	public void run() {
		while(true) {
			//只剩一张票
			/* synchronized代码块运行完毕,才会切换到另一个线程执行
			 * 什么样的内容要放到synchronized代码块中?
			 *   涉及到多个线程修改的地方要放到synchronized代码块中
			 * 锁:一定要是所有线程共享的	
			*/
			synchronized (this) {
				if(count > 0) {
					System.out.println(Thread.currentThread().getName() + "卖第" + (count--) + "张票");
				} else {
					break;
				}
			}
			try {
				Thread.currentThread().sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

synchronized`方法

//所有窗口共用100张票
public class Window2 implements Runnable {
	private int count = 100;
	@Override
	public void run() {
		while(true) {
			sale();
			if(count <= 0) {
				break;
			}
			try {
				Thread.currentThread().sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	//同步方法  该方法运行完毕之后,其他的线程才会运行
	public synchronized void sale() {
		if(count > 0) {
			System.out.println(Thread.currentThread().getName() + "卖第" + (count--) + "张票");
		}
	}
}

Lock

从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。

java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。

ReentrantLock类实现了Lock ,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。

import java.util.concurrent.locks.ReentrantLock;

public class Window3 implements Runnable {
    private int count = 100;
    private ReentrantLock lock = new ReentrantLock(true);
    @Override
    public void run() {
        while(true) {
            try {
                lock.lock();
                if(count > 0) {
                    System.out.println(Thread.currentThread().getName() + "卖第" + (count--) + "张票");
                } else {
                    break;
                }
            } finally {
                lock.unlock();
            }

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

线程安全的单例模式懒汉式

//synchronized代码块方式
public class SingleObject {
    private static SingleObject obj = null;
    
    private SingleObject() {
        
    }
    
    public static SingleObject getInstance() {
        synchronized(SingleObject.class) {
            if(obj == null) {
                obj = new SingleObject();
            }
        }
        return obj;
    }
}

//synchronized方法方式
public class SingleObject {
    private static SingleObject obj = null;
    
    private SingleObject() {
        
    }
    
    public static synchronized SingleObject getInstance() {
        if(obj == null) {
            obj = new SingleObject();
        }
        return obj;
    }
}

//Lock方式
public class SingleObject {
    private static SingleObject obj = null;
    private static ReentrantLock lock = new ReentrantLock(true);
    
    private SingleObject() {
        
    }
    
    public static SingleObject getInstance() {
        try {
            lock.lock();
            if(obj == null) {
                obj = new SingleObject();
            }
        } finally {
            lock.unlock();
        }
        return obj;
    }
}

5.5、死锁

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。

出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。

public class Lock {
	static Object objA = new Object();
	static Object objB = new Object();
}

public class MyThreadA extends Thread {
	@Override
	public void run() {
		while(true) {
			synchronized (Lock.objA) {
				synchronized (Lock.objB) {
					System.out.println("MyThreadA...");
				}
			}
		}
	}
}

public class MyThreadB extends Thread {
	@Override
	public void run() {
		while(true) {
			synchronized (Lock.objB) {
				synchronized (Lock.objA) {
					System.out.println("MyThreadB...");
				}
			}
		}
	}
}

public class MyTest5 {
	public static void main(String[] args) {
		MyThreadA a = new MyThreadA();
		MyThreadB b = new MyThreadB();
		a.setPriority(Thread.MAX_PRIORITY);
		b.setPriority(Thread.MIN_PRIORITY);
		a.start();
		b.start();
	}
}

解决方法:

六、线程通信

相关方法:

以上三个方法只有在synchronized方法或synchronized代码块中才能使用。

两个线程,交替打印1~100。

public class PrintNum implements Runnable {
    int i = 1;

    @Override
    public void run() {
        while (true) {
            synchronized (Object.class) {
                //唤醒正在排队等待同步资源的线程中优先级最高者结束等待
                Object.class.notify();
                if (i <= 100) {
                    System.out.println(Thread.currentThread().getName() + ":" + i++);
                } else
                    break;
                try {
                    //令当前线程挂起并放弃CPU、同步资源并等待
                    Object.class.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

public class MyTest6 {
    public static void main(String[] args) {
        PrintNum printNum = new PrintNum();

        Thread th1 = new Thread(printNum);
        Thread th2 = new Thread(printNum);

        th1.start();
        th2.start();
    }
}

生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了共享固定大小缓冲区的两个线程——即所谓的生产者消费者——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。

要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。

import java.util.ArrayList;
import java.util.List;

//仓库
public class Factory {
	//最大容量
	public static final int MAX_SIZE = 100;
	private static List<Object> list = new ArrayList<Object>();
	
	public static List<Object> getList() {
		return list;
	}
	
	public static void setList(List<Object> list) {
		Factory.list = list;
	}
}

public class Producer extends Thread {
	@Override
	public void run() {
		while(true) {
			synchronized (Factory.getList()) {
				//仓库没有满
				if(Factory.getList().size() < Factory.MAX_SIZE) {
					//生产
					Factory.getList().add(new Object());
					System.out.println(Thread.currentThread().getName() + ":生产\t" + "总数量:" + Factory.getList().size());
					Factory.getList().notifyAll();
					try {
						Thread.sleep(2);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				} else {//仓库已满
					try {
						System.out.println(Thread.currentThread().getName() + ":仓库已满,取消生产任务\t" + "总数量:" + Factory.getList().size());
						Factory.getList().wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}
}

public class Consumer extends Thread {
	@Override
	public void run() {
		while(true) {
			synchronized (Factory.getList()) {
				Factory.getList().notifyAll();
				//有商品
				if(Factory.getList().size() > 0) {
					//消费商品
					Factory.getList().remove(0);
					System.out.println(Thread.currentThread().getName() + ":消费\t" + "总数量:" + Factory.getList().size());
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				} else { //无商品
					try {
						System.out.println(Thread.currentThread().getName() + ":仓库已空,取消消费任务\t" + "总数量:" + Factory.getList().size());
						Factory.getList().wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}
}

public class MyTest7 {
	public static void main(String[] args) {
		Producer p = new Producer();
		p.start();
		Consumer c1 = new Consumer();
		Consumer c2 = new Consumer();
		c1.start();
		c2.start();
	}
}