java多线程与并发

进程与线程

进程:程序在处理机上的一次执行过程,一个具有独立功能的程序,每一个进程都有它自己的地址空间(一个程序的运行)
进程的状态:进程执行的间断性,决定了进程具有多种状态

  • 就绪状态(Ready)
  • 运行状态(Running)
  • 阻塞状态(Blocked)

线程:线程是在进程基础上的进一步划分,一个进程启动后,里面的若干程序可以划分为若干个线程,线程是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个线程

并行:两个任务同时运行(多核CPU)
并发:两个任务同时请求运行,而处理器一次只能接受一个任务,就会把两个任务安排轮流执行,由于CPU时间片运行时间短,感觉两个任务在同时运行

线程的基本使用

package concurrency;

public class ThreadDemo {

    public static void main(String[] args) {
        
        MyThread mt = new MyThread();
        mt.start();//启动线程(实际上是在运行时由虚拟机才启动,只是准备就绪的意思)
        
        MyRunnable mr = new MyRunnable();
        Thread t2 = new Thread(mr);
        
        mt.start();//启动线程(实际上是在运行时由虚拟机才启动,只是准备就绪的意思)
        t2.start();
        
    }
}

/*
 * 实现线程的第一种方式:继承thread类
 */
class MyThread extends Thread{
    @Override
    public void run() {
        for(int i=0;i<50;i++) {
            System.out.println(Thread.currentThread().getName()+"-"+i);//获取当前线程的名字
        }
    }
}

/*
 * 实现线程的第二种方式:实现Runnable接口
 * @ 推荐这种方式
 */
class MyRunnable implements Runnable{
    @Override
    public void run() {
        for(int i=0;i<50;i++) {
            System.out.println(Thread.currentThread().getName()+"-"+i);
        }
    }
}

线程的休眠

静态方法sleep以指定毫秒数暂停,释放CPU的时间片
异常:

  • IllegalArgumentException:如果参数millis为负数
  • InterruptedException:如果任何线程中断当前线程,当抛出此异常时,当前线程的中断状态将被清除

join与中断线程

join方法:等待线程死亡(即执行完),与join(0)相同
interrupt:标记中断这个线程
静态interrupted:测试当前线程是否中断,同时清楚中断标记

中断线程的方式:

  1. 使用interrupt方法来中断线程,设置一个中断状态(标记),但是很多方法会清除该标记,需要重新标记
  2. 使用自定义标记的方式(一般使用这种方式)
package concurrency;

public class ThreadDemo {

    public static void main(String[] args){

        MyRunable mr = new MyRunable();
        Thread t = new Thread(mr);
        MyRunnable2 mr2 = new MyRunnable2();
        Thread t2 = new Thread(mr2);

        t.start();
        t2.start();

        for (int i=0;i<50;i++){

            System.out.println(Thread.currentThread().getName()+"--"+i);
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(i==20){
//                try {
//                    t.join();//让t线程先执行指定时间或执行完毕
//                } catch (InterruptedException e) {
//                    //抛出的该异常会清楚中断标记
//                    e.printStackTrace();
//                }
//                t.interrupt();//中断线程,只是做了一个中断标记
                mr2.flag = false;
            }
        }
    }
}

class MyRunable implements Runnable{

    @Override
    public void run() {
        for(int i=0;i<50;i++){
            if(Thread.interrupted()){
                //测试中断状态,使方法会把中断状态清除
                break;
            }
            System.out.println(Thread.currentThread().getName()+"--"+i);
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                //该异常会清除中断标记
                e.printStackTrace();
                Thread.currentThread().interrupt();//重新标记异常
            }
        }
    }
}

/**
 * 自定义标记
 */
class MyRunnable2 implements Runnable{

    public boolean flag = true;//标记

    @Override
    public void run() {
        int i=0;
        while(flag){
            System.out.println(Thread.currentThread().getName()+"=="+(i++));
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

守护线程

线程分为守护线程和用户线程
public final void setDaemon(boolean on): 将此线程标记为daemon线程或用户线程,当运行的唯一线程是守护线程时,虚拟机将退出
public final boolean isDaemon(): 测试该线程是否是守护线程
public static void yield(): 暂停当前执行的线程对象,让出本次CPU执行的时间片,并执行其他线程
long getId():返回该线程的标识符
String getName():返回该线程的名称
void setName(String name):改变线程名称
boolean isAlive():是否处于活跃状态
void setPriority(int newProprity):改变线程的优先级(抢到时间片概率的高低)

  • MAX_PRIORITY:最高优先级
  • MIN_PRIORITY:最低优先级
  • NORM_PRIORITY:默认
package concurrency;

public class ThreadDemo2 {

    public static void main(String[] args){

        MyRunnable3 mr3 = new MyRunnable3();
        Thread t3 = new Thread(mr3);
        t3.setDaemon(true);//设置为守护线程
        System.out.println(t3.isAlive());//线程是否是活跃状态(false)
        t3.start();
        System.out.println(t3.isAlive());//true

        for (int i = 0; i < 50; i++) {
            System.out.println("main--"+i);
            try {
                Thread.sleep(30);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

class MyRunnable3 implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println("--"+i);
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

线程同步

  1. 多线程共享数据
    多个线程同时处理同一个资源,多线程共享数据时,会发生线程不安全的情况(一个数据被使用多遍的情况),这种情况下必须使用同步
  2. 同步
    多个线程在同一个时间段内只能有一个线程执行指定代码,其他线程要等待此线程完成之后才可以继续执行
    实现同步的三种方法:
    (1) 同步代码块
package concurrency;


public class ThreadDemo3 {

    public static void main(String[] args){
        MyRunnable4 mr4 = new MyRunnable4();

        Thread t1 = new Thread(mr4);
        Thread t2 = new Thread(mr4);
        //两个窗口售票
        t1.start();
        t2.start();
    }
}

class MyRunnable4 implements Runnable{

    private int ticket = 10;//售票
    //private Object obj = new Object();//同步对象,检查是否是同一对象,同一对象才能同步(确定是否是同一把锁)

    @Override
    public void run() {
        for (int i = 0; i < 300; i++) {
            if(ticket > 0){
                //第一种方法,同步代码块(同步锁)
                //执行到这里时,会对该同步对象加上锁,另一个线程无法进入,只能等该线程执行完释放锁
                synchronized (this) {
                    try {
                        Thread.sleep(1000);//模拟售票时间
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("您购买的票已剩余" + ticket-- + "张");
                }
            }
        }
    }
}

(2) 同步方法
同步的对象是当前对象(this)

private synchronized void method(){
        try {
            Thread.sleep(1000);//模拟售票时间
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("您购买的票已剩余" + ticket-- + "张");
    }

(3) Lock

ReentrantLock lock = new ReentrantLock();//互斥锁
    private void method2() {
        lock.lock();//锁
        //使用try和final,防止死锁
        try {
            try {
                Thread.sleep(1000);//模拟售票时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("您购买的票已剩余" + ticket-- + "张");
        }finally {
            lock.unlock();//释放锁
        }
        
    }
  1. 原则
  • 同步会牺牲性能,同步代码块要尽可能简短
  • 不要阻塞
  • 在持有锁的时候,不要对其他对象调用方法

生产者与消费者案例

package concurrency;

public class ProducterCustomerDemo {

    public static void main(String[] args) {

        Food food = new Food();
        Producter producter = new Producter(food);
        Customer customer = new Customer(food);

        Thread t1 = new Thread(producter);
        Thread t2 = new Thread(customer);
        t1.start();
        t2.start();
    }
}

/**
 *  生产者
 */
class Producter implements Runnable{
    private Food food;

    public Producter(Food food){
        this.food = food;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if(i%2 == 0){
                food.set("板栗","一斤25");
            }else{
                food.set("樱桃","一斤70");
            }
        }
    }
}

/**
 * 消费者
 */
class Customer implements Runnable{
    private Food food;
    public Customer(Food food){
        this.food = food;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            food.get();
        }
    }
}

/**
 * 食物
 */
class Food{
    private String name;
    private String desc;
    private boolean flag = true;//true表示可以生产,false表示可以消费

    /**
     * 生产产品
     * @param name
     * @param desc
     */
    public synchronized void set(String name,String desc){
        //不能生产
        if(!flag){
            try {
                this.wait();//Object方法,线程进入等待状态,会释放监视器的所有权(对象锁)
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        this.setName(name);
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.setDesc(desc);
        flag = false;
        this.notify();//唤醒其中一个等待线程

    }

    /**
     * 消费产品
     */
    public synchronized void get(){
        //不能消费
        if(flag){
            try {
                this.wait();//Object方法,线程进入等待状态,会释放监视器的所有权(对象锁)
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(this.getName()+"->"+this.getDesc());
        flag = true;
        this.notify();//唤醒其中的一个等待线程
    }

    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;
    }

    public Food() {
    }

    public Food(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }

    @Override
    public String toString() {
        return "Food{" +
                "name='" + name + '\'' +
                ", desc='" + desc + '\'' +
                '}';
    }
}

线程池

线程池是预先创建线程的一种技术,线程池在任务还没有到来之前,创建一定数量的线程放入空闲队列中,然后对这些资源进行复用,减少频繁的创建和销毁对象

线程池的顶级接口是Executor,线程池接口是ExecutorService

  • Executor接口:执行已经提交的Runnable任务的对象
  • ExecutorService接口:提供管理终止的方法,以及跟踪一个或多个异步任务执行状况而生成的Future方法
  • Executors类:定义了Executor,ExecutorService等的工厂和实用方法
    (1) newSingleThreadExecutor
    单线程线程池,此线程池保证所有任务的执行顺序按照任务的提交顺序执行
    (2)newFixedThreadPool
    创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程池到达最大大小,一旦达到最大值就保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程
    (3)newCachedThreadPool
    创建一个可缓存的线程池,如果线程池的大小超过了处理任务所需的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务
    (4)newScheduledThreadPool
    创建一个大小无限的线程池,此线程池支持定时以及周期性执行任务的需求
package concurrency;

import java.util.concurrent.ExecutorService;    
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ThreadDemo4 {
    public static void main(String[] args) {
        //创建一个单线程的线程池
        ExecutorService es1 = Executors.newSingleThreadExecutor();
        ExecutorService es2 = Executors.newFixedThreadPool(2);//指定线程池数量,等于1相当于单线程
        ExecutorService es3 = Executors.newCachedThreadPool();
        ExecutorService es4 = Executors.newScheduledThreadPool(3);//大小无限,但仍然需要指定大小

        es1.execute(new MyRunnable5());//执行线程
        es1.execute(new MyRunnable5());

        es2.execute(new MyRunnable5());
        es2.execute(new MyRunnable5());

        es3.execute(new MyRunnable5());
        es3.execute(new MyRunnable5());

        ((ScheduledExecutorService) es4).schedule(new MyRunnable5(),3000, TimeUnit.MILLISECONDS);

        es1.shutdown();//关闭线程
        es2.shutdown();
        es3.shutdown();
        es4.shutdown();

    }
}

class MyRunnable5 implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("run--"+i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}