1.进程与线程概念
代码参考多线程测试实例
1.1什么是进程
程序是指令和数据的有序集合,是静态的概念。而进程是程序在处理机上的一次执行过程,它是一个动态概念。
进程是一个具有一定独立功能的程序,一个实体。每一个进程都有它自己的地址空间。
1.2 进程的状态
1.就绪状态(Ready)
2.运行状态(Running)
3.阻塞状态(Blocked)
1.3线程
线程是在进程基础上的进一步划分,一个进程启动之后,里面的若干程序又可以划分为若干个线程。
线程是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程至少有一个线程(单线程程序)
并行:两个任务同时运行(多个CPU)
并发:两个任务同时请求运行,而处理器只能接受一个任务,就会把两个任务安排轮流执行,由于CPU时间片运行时间较短,就会感觉两个任务在同时执行。
2 线程的基本使用
在java中如果要实现多线程的操作,有两种方法。
2.1 线程实现的两种方式
(1)继承Thread类
class MyThread extends Thread {
public void run () {
... //逻辑处理
}
}
MyThread mt = new MyThread();
mt.start();
(2) 实现Runnable接口(推荐)
class MyRunnable implements Runnable {
public void run () {
... //逻辑处理
}
}
MyRunnable mr = new MyRunnable();
Thread t = new Thread(mr);
t.start();
3 线程休眠
public static void sleep(long millis)
throw InterruptedException 使当前正在执行的线程以指定的毫秒数暂停,释放CPU的时间片,具体取决于系统定时器和调度程序的精度和准确性。
public static void sleep(long millis, int nonos) throw InterruptedException 毫秒,纳秒
static Thread currentThread() 返回当前正在执行的线程对象的引用。
4 join与中断线程
public final void join() throws InterruptedException
等待这个线程死亡
异常InterruptedException-如果任何线程中断当前线程。当抛出此异常时,当前线程的中断状态将被清除
public void interrupt()
中断这个线程
除非当前线程中断自身,这是始终允许的。
public static boolean interrupted()
测试当前线程是否中断。该方法可以清除线程的中断状态。如果这个方法被连续调用两次,那么第二个调用将返回false(除非当前线程再次中断,在第一个调用已经清楚其中断状态之后,第二个调用之前已经检查过)。
自定义标记中断线程(推荐使用)
5 线程同步
(1)多线程共享数据
在多线程的操作中,多个线程有可能同时处理同一个资源,这就是多线程中的共享数据。
(2)线程同步
解决数据共享问题,必须使用同步,所谓同步就是指多个线程在同一个时间段内只能有一个线程执行指定代码,其他线程要等待此线程完成之后才可以继续执行。
线程进行同步,有以下三种方法:
1)同步代码块
Synchronized(要同步的对象){
要同步的操作;
}
public class ThreadDemo4 {
public static void main(String[] args) {
Myrunnable5 mr = new Myrunnable5();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
t1.start();
t2.start();
}
}
class Myrunnable5 implements Runnable{
private int tickets = 10; //售票
//private Object obj = new Object(); // 同步锁
@Override
public void run() {
for (int i = 0; i < 300; i++) {
//synchronized(obj) {
synchronized(this) {
if (tickets > 0) {
tickets--;
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("it remains " + tickets + " tickets");
}
}
}
}
}
2)同步方法
public synchronized void method() {
要同步的操作;
}
public synchronized void method1() {
if (tickets > 0) {
tickets--;
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("it remains " + tickets + " tickets");
}
}
3 )Lock( ReentrantLock)
//互斥锁
private ReentrantLock lock = new ReentrantLock();
private void method2() {
lock.lock(); //上锁
try {
if (tickets > 0) {
tickets--;
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("it remains " + tickets + " tickets");
}
}finally { //放在finnaly中确保锁的释放
lock.unlock(); //释放锁
}
}
3 同步准则
当编写synchronized块时,有几个简单的准则可以遵循,这些准则在避免死锁和性能的危险方面大有帮助。
1)是代码块保持简洁,把不随线程变化的预处理和后处理移除synchronized块。
2)不要阻塞,如Inputstream等。
3)在持有锁的时候,不要调用其他对象加锁的方法。
6 死锁
过多的同步有可能出现死锁,死锁的操作一般是在程序运行的时候才有可能出现。
7 生产者消费者案例
/**
* 两个线程间协同工作,先生产,再消费
* sleep 与 wait的区别:
* sleep:让线程进入休眠状态,让出CPU时间片,不释放对象监视器的所有权(对象所)
* wait: 让线程进入等待状态,让出CPU时间片,并释放对监视器对所有权,等待其他线程通过notify方法唤醒。
* @author moushuai
*
*/
public class ProducerCustomerDemo {
public static void main(String[] args) {
Food food = new Food();
producer producer = new producer(food);
customer customer = new customer(food);
Thread t1 = new Thread(producer);
Thread t2 = new Thread(customer);
t1.start();
t2.start();
}
}
class Food{
private String name;
private String desc;
private boolean flag = true;//True表示可以生产,false表示可以消费
public Food(String name, String desc) {
super();
this.name = name;
this.desc = desc;
}
public Food() {
super();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return "Food [name=" + name + ", desc=" + desc + "]";
}
/**
* 生产方法
* @param name
* @param desc
*/
public synchronized void set(String name, String desc) {
//不能生产
if (!flag) {
try {
this.wait();//线程进入等待状态,释放监视器的所有权(对象锁)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.desc = desc;
flag = false;
this.notify();//唤醒等待的线程(随机其中一个)
}
/**
* 消费方法
*/
public synchronized void get() {
//不能消费
if (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(name + " -> " + desc);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
this.notify();
}
}
/**
* 生产者
* @author moushuai
*
*/
class producer implements Runnable{
private Food food;
public producer(Food food) {
this.food = food;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if ( i%2 == 0) {
food.set("beef", "very delicious!");
}else {
food.set("java sets", "nice sets, good for programmer!");
}
}
}
}
/**
* 消费者
* @author moushuai
*
*/
class customer implements Runnable{
private Food food;
public customer(Food food) {
this.food = food;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
food.get();
}
}
}
8 线程生命周期
9 线程池
线程池是预先创建线程的一种技术。线程池还没有任务到来之前,创建一定数量的线
程,放入空闲队列中,然后对这些资源进行复用。减少频繁的创建和销毁对象。
jdk1.5版本以上提供了现成的线程池。
java里面线程池的顶级接口是executor,是一个执行线程的工具。
线程池接口是ExecutorService。
java.util.concurrent包: 并发编程中很常用的实用工具类
Executor接口: 执行已提交的Runnable任务的对象。
ExecutorService接口:提供了管理终止的方法,以及可为跟踪一个或多个异步任务
执行状况而生成Future的方法。
Executors类: 此包中定义的Executor,ExecutorService等的工厂和实用方法。
在Executors类里面提供了一些静态工厂,生成一些常用的线程池:
newSingleThreadExecutor: 创建一个单线程的线程池。这个线程只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程保证所有的任务的执行顺序按照任务的提交顺序执行。
public static void main(String[] args) {
ExecutorService es = Executors.newSingleThreadExecutor();
es.execute(new MyRunnable6());
es.execute(new MyRunnable6());
es.shutdown();
}
newFixedThreadPool: 创建固定大小的线程池 。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新的线程。
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(2);
es.execute(new MyRunnable6());
es.execute(new MyRunnable6());
es.execute(new MyRunnable6());
es.shutdown();
}
newCachedThreadPool(不常用): 创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程又可以智能的添加新线程来处理任务。此线程不会对线程池大小做限制,其大小完全依赖与操作系统(或者JVM)能够创建的最大线程大小。
public static void main(String[] args) {
ExecutorService es = Executors.newCachedThreadPool();
es.execute(new MyRunnable6());
es.execute(new MyRunnable6());
es.execute(new MyRunnable6());
es.shutdown();
}
newScheduledThreadPool: 创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
public static void main(String[] args) {
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(2);
newScheduledThreadPool.schedule(new MyRunnable6(), 3000, TimeUnit.MILLISECONDS);
newScheduledThreadPool.schedule(new MyRunnable6(), 4000, TimeUnit.MILLISECONDS);
newScheduledThreadPool.schedule(new MyRunnable6(), 8000, TimeUnit.MILLISECONDS);
newScheduledThreadPool.shutdown();
}