多线程与并发

文章目录

  • 1、什么是进程
  • 2、进程的状态
  • 3、线程
  • 4、线程的基本使用
  • 5、线程休眠
  • 6、守护线程与yield
  • 7、join和中断线程
  • 8、线程同步
    • 同步代码块:
    • 同步方法
    • Lock
    • volatile
    • 阻塞队列
    • 使用原子变量实现线程同步
  • 9、死锁
  • 经典案例
  • 线程生命周期和线程池
  • 面试题

1、什么是进程

程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念,而进程是程序在处理机上的一次执行过程,是个动态的概念。

进程是一个具有一定独立功能的程序,一个实体,每一个进程都有自己的地址空间。

2、进程的状态

进程执行的间断性决定了进程可能具有多种状态,事实上,运行中的进程具有以下三种状态:

  • 就绪状态
  • 运行状态
  • 阻塞状态

3、线程

线程实际上是在进程的基础上进一步划分,一个进程启动后。里面的若干程序又可以划分若干个线程。

线程:是进程的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个线程

并行:两个任务同时运行(多个CPU)

并发:就是将任务轮流执行,由于CPU时间片运行时间较短,就会感觉两个任务在同时执行。

4、线程的基本使用

  • 一种是继承Thread类
class MyThread extends Thread{
    public void run{
        //逻辑处理
    }
}
MyThread mt = new MyThread();
mt.start()
  • 另外一种是实现Runnable接口(推荐使用)
class MyRunnable implements Runnable{
    public void run(){
        //逻辑处理
    }
}
MyRunnable mr = new MyRunnable();
Thread t = new Thread(mr);
t.start();

5、线程休眠

pulic static void sleep(long mills)

在当前线程的执行中,暂停指定的毫秒数,释放CPU的时间片

6、守护线程与yield

public final void setDaemon(boolean on)
将此线程标记为daemon线程或用户线程,当运行的唯一线程都是守护进程线程时,Java虚拟机将退出
参数为true时,线程为守护线程

public static void yield()
暂停当前正在执行的线程对象,并执行其他线程

void setPriority(int newPriority) 
更改线程优先级
static int MAX_PRIORITY 最大优先级,抢到CPU时间片的概率大
static int MIN_PRIORITY 最小优先级
static int NORM_PRIORITY 默认优先级
package per.learn.multhread;


public class ThreadDemo2 {
    public static void main(String[] args) {
        MyRunnable2 mr = new MyRunnable2();
        Thread tr  = new Thread(mr);
        tr.setDaemon(true);//设置为守护线程
        tr.start();
        for(int i =0;i<10;i++){
            System.out.println("main--"+i);
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class MyRunnable2 implements Runnable{
    private long millis;

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


OUT:
main--0
--0
main--1
main--2
--1
main--3
main--4
--2
main--5
main--6
main--7
--3
main--8
main--9
--4

main运行完,线程tr并未执行完。

7、join和中断线程

public final void join() throws InterruptedException
等待这个线程死亡
调用此方法的行为方式与调用相同
join(0)

public void interrupt()
中断这个线程
除非当前线程中断自身,这是允许的
public static boolean interrupted()
测试当前线程是否中断,该方法会清除中断状态

join方法:
加入线程,将调用的线程执行指定时间或执行完毕,再执行其他

package per.learn.multhread;

public class ThreadDemo3 {
    public static void main(String[] args) {
        MyRunnable3 mr3 = new MyRunnable3();
        Thread t = new Thread(mr3);
        t.start();
        for(int i =0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"--"+i);
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(i==5){
                try{
                    t.join();//让线程t执行完毕
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //t.interrupt();//中断线程,只是做了中断标记,抛出异常,线程仍然会执行
            }
        }
    }
}
class MyRunnable3 implements Runnable{
    @Override
    public void run() {
        for(int i =0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"--"+i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Out:
main--0
Thread-0--0
main--1
main--2
Thread-0--1
main--3
main--4
Thread-0--2
main--5
Thread-0--3
Thread-0--4
Thread-0--5
Thread-0--6
Thread-0--7
Thread-0--8
Thread-0--9
main--6
main--7
main--8
main--9

package per.learn.multhread;

public class ThreadDemo3 {
    public static void main(String[] args) {
        MyRunnable3 mr3 = new MyRunnable3();
        Thread t = new Thread(mr3);
        t.start();
        for(int i =0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"--"+i);
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(i==5){
//                try{
//                    t.join();//让线程t执行完毕
//                }catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
                t.interrupt();//中断线程,只是做了中断标记,抛出异常,线程仍然会执行
            }
        }
    }
}
class MyRunnable3 implements Runnable{
    @Override
    public void run() {
        for(int i =0;i<10;i++){
            if(Thread.interrupted()){
                break;
            }
            System.out.println(Thread.currentThread().getName()+"--"+i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace(); //会将中断状态清除
                Thread.currentThread().interrupt();//将中断状态重新标记
            }
        }
    }
}


main--0
Thread-0--0
main--1
main--2
Thread-0--1
main--3
main--4
main--5
Thread-0--2
main--6
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at per.learn.multhread.MyRunnable3.run(ThreadDemo3.java:35)
	at java.lang.Thread.run(Thread.java:748)
main--7
main--8
main--9

8、线程同步

  • 多线程共享数据
    在多线程的操作中,多个线程可能同时处理同一个资源,这就是多线程中的共享数据,会发生不安全的情况。
  • 线程同步

同步代码块:

同步是一种高开销的操作,因此应该尽量减少同步的内容。
通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。

synchronized(object){ 
    要同步的操作
}

例:

package per.learn.multhread;

import sun.awt.windows.ThemeReader;

public class ThreadDemo4 {
    public static void main(String[] args) {
        MyRunnable5 mr5 = new MyRunnable5();
        Thread t1 = new Thread(mr5);
        Thread t2 = new Thread(mr5);
        //模拟两个窗口
        t1.start();
        t2.start();
    }
}
class MyRunnable5 implements Runnable{
    private int ticket=10;//车票
    private Object obj =  new Object();
    @Override
    public void run() {
        for(int i=0;i<300;i++){
            synchronized (obj){
                if(ticket > 0){
                    ticket--;
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("您购买的车票已剩余"+ticket+"张");
                }
            }
        }
    }
}

同步方法

public synchronized void method(){
要同步的操作
}

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

注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类

package per.learn.multhread;

import sun.awt.windows.ThemeReader;

public class ThreadDemo4 {
    public static void main(String[] args) {
        MyRunnable5 mr5 = new MyRunnable5();
        Thread t1 = new Thread(mr5);
        Thread t2 = new Thread(mr5);
        //模拟两个窗口
        t1.start();
        t2.start();
    }
}
class MyRunnable5 implements Runnable{
    private int ticket=10;//车票
    private Object obj =  new Object();
    private synchronized void method(){
        if(ticket > 0){
            ticket--;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("您购买的车票已剩余"+ticket+"张");
        }
    }
    @Override
    public void run() {
        for(int i=0;i<300;i++){
            method();
                }
            }
}

Lock

结构化更加方便,以上两种只能等同步方法或同步代码块执行完毕后才能释放锁。
ReentrantLock类是可重入、互斥、实现了Lock接口的锁,
它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力

ReenreantLock类的常用方法有:

    ReentrantLock() : 创建一个ReentrantLock实例 
    lock() : 获得锁 
    unlock() : 释放锁 
注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用 
package per.learn.multhread;

import sun.awt.windows.ThemeReader;

import java.util.concurrent.locks.ReentrantLock;

public class ThreadDemo4 {
    public static void main(String[] args) {
        MyRunnable5 mr5 = new MyRunnable5();
        Thread t1 = new Thread(mr5);
        Thread t2 = new Thread(mr5);
        //模拟两个窗口
        t1.start();
        t2.start();
    }
}
class MyRunnable5 implements Runnable{
    private int ticket=10;//车票
    private Object obj =  new Object();
    ReentrantLock lock = new ReentrantLock();
    private void method2(){
        lock.lock();//锁
        try{
            if(ticket > 0){
                ticket--;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("您购买的车票已剩余"+ticket+"张");
            }
        }finally {
            lock.unlock();//释放锁
        }
    }
    @Override
    public void run() {
        for(int i=0;i<300;i++){
            method2();
                }
            }
}

注意:

  • 如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码
  • 如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁

volatile

  • volatile关键字为域变量的访问提供了一种免锁机制,
  • 使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新,
  • 因此每次使用该域就要重新计算,而不是使用寄存器中的值
  • volatile不会提供任何原子操作,它也不能用来修饰final类型的变量

阻塞队列

LinkedBlockingQueue 类常用方法

  • LinkedBlockingQueue() : 创建一个容量为Integer.MAX_VALUE的LinkedBlockingQueue
  • put(E e) : 在队尾添加一个元素,如果队列满则阻塞
  • size() : 返回队列中的元素个数
  • take() : 移除并返回队头元素,如果队列空则阻塞

当队列满时:
add()方法会抛出异常
offer()方法返回false
put()方法会阻塞

使用原子变量实现线程同步

需要使用线程同步的根本原因在于对普通变量的操作不是原子的。

那么什么是原子操作呢?
原子操作就是指将读取变量值、修改变量值、保存变量值看成一个整体来操作
即-这几种行为要么同时完成,要么都不完成。

在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类,
使用该类可以简化线程同步。

其中AtomicInteger 表可以用原子方式更新int的值,可用在应用程序中(如以原子方式增加的计数器),
但不能用于替换Integer;可扩展Number,允许那些处理机遇数字类的工具和实用工具进行统一访问。

AtomicInteger类常用方法:
AtomicInteger(int initialValue) : 创建具有给定初始值的新的AtomicInteger
addAddGet(int dalta) : 以原子方式将给定值与当前值相加
get() : 获取当前值

9、死锁

过多的同步有可能出现死锁。

经典案例

如:服务员需要等待厨师准备好食物,然后通知服务员上菜,然后回去继续等待,厨师代表生产者,服务员代表消费者。

package per.learn.multhread;

/**
 * 两个线程协同工作,先生产,再消费
 */
public class ProducterCustomerDemo {
    public static void main(String[] args) {
        Food food = new Food();//共享food对象
        Producter p = new Producter(food);
        Customer c = new Customer(food);
        //先生产,再消费
        Thread t1 = new Thread(p);
        Thread t2 = new Thread(c);
        t1.start();
        t2.start();
    }
}
/**
 * 消费者
 */
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 Producter implements Runnable{
    private Food food;
    public Producter(Food food){
        this.food=food;
    }

    @Override
    public void run() {
        //模拟生产20份
        for (int i = 0; i <20 ; i++) {
            if(i%3==0){
                food.set("锅包肉","酸甜口味");
            }
            else if(i%3==1){
                food.set("佛跳墙","多辣");
            }else{
                food.set("小炒肉","多肉");
            }
        }
    }
}
/*
共享
 */
class Food{
    private String name;
    private String describe;
    private boolean flag = true;//true 表示生产,false表示消费
    /**
     * 生产产品
     */
    public synchronized void set(String name,String describe){
        if(!flag){
            //表示不能生产,要释放cpu和锁
            try {
                this.wait();//线程进入等待状态,释放监视器所有权(对象锁)
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.setName(name);
        try {
            Thread.sleep(1000);  //模拟生产过程 产生时间差
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.setDescribe(describe);
        flag = false;
        this.notify();//唤醒等待的线程(随机的其中一个)
    }

    /**
     * 消费
     */
    public synchronized  void get(){
        if(flag){
            //表示不能消费
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(this.getName()+"->"+this.getDescribe());
        flag = true;
        this.notify();
    }
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescribe() {
        return describe;
    }

    public void setDescribe(String describe) {
        this.describe = describe;
    }

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

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

    public Food() {
    }
}

线程生命周期和线程池

多线程与并发_第1张图片
线程池是预先创建线程的一种技术。线程池在还没有任务来之前,创建一定数量的线程,放入空闲队列中,然后对这些资源进行复用。减少频繁的创建和销毁对象。线程池接口是ExecutorService。

Executor接口:
执行已提交的Runnable任务对象

ExecutorService:
Executor提供了管理终止方法,以及可为追踪一个或多个异步任务执行状况而生成Future的方法。

Executors类:
此包中所定义的Executor、ExecutorService等的工厂和实用方法。

newSingleThreadExecutor:
创建单线程线程池。相当于单线程串行执行所有任务,如果这个唯一线程因为异常结束,那么会有一个新的线程替代它,此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

newFixedThreadPool:创建固定大小的线程池,每次提交一个任务就创建一个线程,直到达到线程池最大大小。
如果某个线程因为异常结束,就会补充一个新线程

newCachedThredPool: 创建一个可缓存的线程池,如果线程池的大小超过了处理任务所需要的线程,那么就会收回部分空闲(60s不执行任务)的线程,线程池不会对线程池大小做限制,依赖操作系统(或者说JVM)能创建的最大线程池大小。

newSchedeledThreadPool:创建一个大小无限的线程池,支持定时及周期性执行任务的需求。(需要指定初始大小)

package per.learn.multhread;
import java.util.concurrent.*;

public class ThreadDemo5 {
    public static void main(String[] args) {
        //创建线程池(4种)
        //ExecutorService es = Executors.newSingleThreadExecutor();
        //ExecutorService es = Executors.newFixedThreadPool(2);
        ScheduledExecutorService es = Executors.newScheduledThreadPool(2);
        //es.execute(new MyRunnable6());
        //es.execute(new MyRunnable6());
        es.schedule(new MyRunnable6(),3000, TimeUnit.MILLISECONDS);

        es.shutdown();//结束
    }
}
class MyRunnable6 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"---"+i);
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

面试题

sleep() wait de 区别:
sleep:让线程进入休眠状态,让出CPU时间时间片,不释放对象锁

wait:让线程进入等待状态,让出CPU时间片,释放对象锁,等待其他线程通过notify()方法来唤醒,也可以设置等待时间,该线程再次竞争资源

你可能感兴趣的:(Java)