多线程

java.exe至少有三个线程:一个main()主线程,一个GC线程(守护线程),一个异常处理线程

一、创建多线程的三种方式

1、通过继承Thread类(Thread实现了Runnable接口),重写run()方法;

2、通过实现Runnable接口的run()方法;

3、通过实现Callable接口的call()方法,支持泛型的返回值和抛出异常,要借助FutureTask来封装,既可以封装Callable,也可以封装Runnable;

FutureTask实现了Runnable接口,支持异步获取线程执行结果,取消执行任务,异步获取执行结果会阻塞主线程;

可以将FutureTask放入线程池中

线程池:通过线程池来创建的优点:先创建好线程再放入线程池,可以避免频繁的创建、销毁线程,提高响应速度,减小资源消耗,便于线程管理;

主要分为任务部分和线程部分,通过一个生产者消费者模型来将二者解耦

任务调度过程分三步:第一步查看是否还有空闲的核心线程;第二步查看缓存队列是否满了;第三步查看最大线程的数量

线程调度过程:抢占式调度(由系统来按优先级来决定下一个线程执行) 和 协同式调度(由线程来通知下一个线程执行)

execute():没有返回结果,只能接收Runnable类型的任务

submit():返回一个Future类型的对象,用来异步获取Callable任务的返回结果,可以接收Runnable和Callable任务

shutdown()和shutdownNow()的区别

前面三个都是通过Thread的start()方法来运行:start()启动线程,调用run()方法

继承Thread类创建多个线程要用多个对象,一个对象不能多次调用start()

public class MyTreadTest {
    public static void main(String[] args){
        MyThread mt = new MyThread("新线程1--");
        //mt.run();
        mt.start();  //开启多线程,开辟一个新的栈执行run()

        // MyThread2 mt2 = new MyThread2();
        new Thread(new MyThread2(), "新线程2--").start();;

        for(int i=0; i<3; i++)
            System.out.println(Thread.currentThread().getName() +" "+ i);

        //匿名内部类实现多线程
        new Thread(){
            @Override
            public void run(){
                System.out.println("匿名内部类1");
            }
        }.start();

        new Thread(new Runnable(){
            @Override
            public void run(){
                System.out.println("匿名内部类2");
            }
        }).start();;
    }
}

// 创建线程的第一种方式,继承Thread类
class MyThread extends Thread{
    public MyThread(){}
    public MyThread(String name){
        super(name);
    }

    @Override
    public void run(){
        for(int i=0; i<3; i++)
            System.out.println(getName() + i);
    }
}
// 创建线程的第二种方式,实现Runable接口
// 避免了单继承的局限性;增强了程序的扩展性
class MyThread2 implements Runnable{
    @Override
    public void run(){
        for(int i=0; i<3; i++)
            System.out.println(Thread.currentThread().getName() +" "+ i);
    }
}
Thread Runnable
/**
 * 1、Callable支持泛型
 * 2、call()可以抛出异常,后面捕获
 * 3、call()有返回值
 * */

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

class CallableThread implements Callable<Integer>{  // 支持泛型
    @Override
    public Integer call() throws Exception{
        int s = 0;
        for(int i=0; i<=100; i++){
            Thread.sleep(10);
            s += i;
        }
        return s;
    }
}

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CallableThread ct = new CallableThread();
        FutureTask<Integer> ft = new FutureTask<Integer>(ct);
        new Thread(ft).start();
        new Thread(ft).start();        // FutureTask只会执行一次call()
        if(!ft.isDone()){
            System.out.println("主线程执行其他任务");
        }
        System.out.println(ft.get());  // 获得call()的返回值,会阻塞主线程
        System.out.println("12345");
    }
}
Callable
import java.util.concurrent.*;

class NumberRun implements Runnable{
    private int s = 0;

    @Override  // 不能抛出异常,没有返回结果
    public void run() {
        for(int i=0; i<100; i++) {
            s += i;
        }
        System.out.println(Thread.currentThread().getName()+": "+s);
    }
}

class NumberCall implements Callable<Integer>{
    private int s = 0;

    @Override  // 可以抛出异常,有返回结果
    public Integer call() throws Exception {
        for(int i=0; i<100; i++){
            s += i;
        }
        System.out.println(Thread.currentThread().getName()+": "+s);
        return s;
    }
}

public class ThreadPool {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 线程池一:Executors.newCachedThreadPool();
        // 核心线程数为0,最大线程数为Integer.MAX_VALUE,空闲线程60s后被回收,允许创建大量线程,不添加任务
        //  手动设置线程池参数
        new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L,
                TimeUnit.SECONDS, new SynchronousQueue<Runnable>());

        // 线程池二:Executors.newFixedThreadPool(5);
        // 核心线程数和最大线程数一致,允许添加大量任务
        new ThreadPoolExecutor(5, 5, 0L,
                TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());

        // 线程池三:Executors.newSingleThreadExecutor();
        // 核心线程数和最大线程数都为1,将等待的任务放入队列,队列的最大长度为Integer.MAX_VALUE,允许添加大量任务
        new ThreadPoolExecutor(1, 1, 0L,
                TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());

        // 线程池四:Executors.newScheduledThreadPool();
        // 也是通过ThreadPoolExecutor()实现的,可以传入一个ThreadFactory对象


        // 创建有10个线程的线程池
        ExecutorService serviceInterface = Executors.newFixedThreadPool(10);
        // ThreadPoolExecutor可以设置线程池属性
        ThreadPoolExecutor service = (ThreadPoolExecutor)serviceInterface;

        // 使用Runnable提交任务
        service.execute(new NumberRun());
        System.out.println("(Runnable无返回值): " + service.submit(new NumberRun()).get());
        // service.execute(new NumberCall());  // 不能提交Callable任务
        service.execute(new FutureTask<>(new NumberRun(), null));

        // 使用Callable提交任务
        service.submit(new NumberCall());
        // FutureTask即可传入Callable,也可传入Runnable
        service.submit(new FutureTask<Integer>(new NumberCall()));
        // execute不能提交Callable任务,但可执行经过FutureTask封装的Callable任务,因为FutureTask实现了Runnable接口
        service.execute(new FutureTask<Integer>(new NumberCall()));
        // 使用Callable提交任务,并获取返回值
        System.out.println("获取返回值: " + service.submit(new NumberCall()).get());

        // 不再接收新任务,等线程池中的任务执行完之后关闭线程池,
        service.shutdown();
        // 不再接收新任务,不等待,直接尝试关闭线程池
        // service.shutdownNow();
        
    }
}
ThreadPool

 

线程的方法:

1、Thread.currentThread():获取当前线程

2、Thread.yield():释放当前CPU的执行权,线程进入就绪状态(下一次仍然有可能抢到执行权),不会抛出异常

3、Thread.sleep():阻塞当前线程,线程进入阻塞状态,抛出异常

4、join():线程a里面调用线程b的join(),则线程a进入阻塞状态,直到线程b执行完

5、线程的优先级:getPriority()、setPriority(int p)、MIN_PRIORITY=1、MAX_PRIORITY=10、NORM_PRIORITY=5(默认)

 

二、线程安全

1、同步代码块:通过synchronized来实现,注意Thread和Runnable的同步监视器区别

public class ThreadSecurityDemo {
    public static void main(String[] args){
        ThreadSecutiry ts = new ThreadSecutiry();
        new Thread(ts).start();
        new Thread(ts).start();
        new Thread(ts).start();
    }
}

class ThreadSecutiry implements Runnable{
    private int ticket = 100;

    Object obj = new Object();  // 所有对象只能拥有一把锁

    @Override
    public void run(){
        while(true){
            synchronized(obj){  // 传入一个锁对象,可以是任意类型,this也可以
                if(ticket>0){
                    // try{
                    //     Thread.sleep(10);
                    // }
                    // catch(InterruptedException e){
                    //     e.printStackTrace();
                    // }
                    System.out.println(Thread.currentThread().getName()+"正在出售第"+ticket+"张票");
                    ticket--;
                }
            }
        }
    }
}    
Runnable
class TicketThread extends Thread {
    private static int ticket = 100;
    private static Object obj = new Object();  // 保证锁唯一

    @Override
    public void run(){
        while(true){
            synchronized(TicketThread.class){
                if(ticket>0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+" 正在卖第 " +(101-ticket)+" 张票");
                    ticket--;
                }else
                    break;
            }
        }
    }
}

public class TicketThreadTest{
    public static void main(String[] args) {
        TicketThread tt1 = new TicketThread();
        TicketThread tt2 = new TicketThread();
        tt1.start();
        tt2.start();
    }
}
Thread

2、同步方法(写一个synchronized修饰的方法)

public class ThreadSecurityDemo {
    public static void main(String[] args){
        ThreadSecutiry ts = new ThreadSecutiry();
        System.out.println(ts);
        new Thread(ts).start();
        new Thread(ts).start();
        new Thread(ts).start();
    }
}

class ThreadSecutiry implements Runnable{
    private int ticket = 100;

    @Override
    public void run(){
        System.out.println("this: "+this);
        while(true){
            sellTicket();
        }
    }
    // 锁对象是this
    public synchronized void sellTicket(){
        if(ticket>0){
            System.out.println(Thread.currentThread().getName()+"正在出售第"+ticket+"张票");
            ticket--;
        }
    }
}
View Code
public class ThreadSecurityDemo {
    public static void main(String[] args){
        ThreadSecutiry ts = new ThreadSecutiry();
        new Thread(ts).start();
        new Thread(ts).start();
        new Thread(ts).start();
    }
}

class ThreadSecutiry implements Runnable{
    private static int ticket = 100;

    @Override
    public void run(){
        while(true){
            sellTicket();
        }
    }
    // 锁对象是class文件对象
    public static synchronized void sellTicket(){
        if(ticket>0){
            System.out.println(Thread.currentThread().getName()+"正在出售第"+ticket+"张票");
            ticket--;
        }
    }
}
View Code

3、锁机制:

java.util.cocurrrent.locks提供了Lock和ReadWriteLock接口,ReentrantLock实现了Lock接口,ReentrantReadWriteLock实现了ReadWriteLock接口,都是通过内部类Sync继承AQS实现;

等待可中断:没有获取到锁的线程不必一直等待获取锁,可以先执行其他任务;

可实现公平锁:线程按申请锁的顺序来获取锁,直接进入队列排队等待;非公平锁:先尝试获取锁,再进入队列排队等待,synchronized是非公平锁

可绑定多个条件?:

Condition condition = lock.newCondition();

可以使用condition.await()和condition.signal()/signalAll()来阻塞或唤醒条件上线程,而不是像Object的notify()一样随机唤醒

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadSecurityDemo {
    public static void main(String[] args){
        ThreadSecutiry ts = new ThreadSecutiry();
        new Thread(ts).start();
        new Thread(ts).start();
        new Thread(ts).start();
    }
}

class ThreadSecutiry implements Runnable{
    private int ticket = 100;

    Lock lock = new ReentrantLock();  // 所有线程只能有一把锁,如果有多个对象,这里就要设为static

    @Override
    public void run(){
        while(true){
            lock.lock();
            if(ticket>0){
                try{
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName()+"正在出售第"+ticket+"张票");
                    ticket--;
                }
                catch(InterruptedException e){
                    e.printStackTrace();
                }
                finally{
                    lock.unlock();
                }
            }
        }
    }
}
View Code

   4、ThreadLocal

当多个线程使用一个共享变量时,ThreadLocal为每个使用这个变量的线程维护一个独立的变量副本,ThrealLocal依靠ThreadLocalMap来存储数据,key是ThreadLocal,value是存储的值,每个线程只有一个ThreadLocalMap

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.*;

public class ThreadLocalTest {
    private static int a = 500;
    public static void main(String[] args) {

        ThreadLocal threadLocal = new InheritableThreadLocal();  // 用于信息共享
        threadLocal.set("老王");
        ThreadLocal threadLocal2 = new ThreadLocal();
        threadLocal2.set("老王");
        System.out.println(threadLocal.get().equals(threadLocal2.get())); // true
        new Thread(() -> {
            System.out.println(threadLocal.get());   // "老王"
            System.out.println(threadLocal2.get());  // null
            System.out.println(threadLocal.get().equals(threadLocal2.get()));  // false
        }).start();


        new Thread(()->{
            ThreadLocal<Integer> local = new ThreadLocal<Integer>();
            while(true){
                local.set(++a); //子线程对a的操作不会影响主线程中的a, 为什么这里是从22开始?
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("子线程:"+local.get());
            }
        }).start();
        a = 22;
        ThreadLocal<Integer> local = new ThreadLocal<Integer>();
        local.set(a);
        while(true){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("主线程:"+local.get());
        }
    }
}
ThreadLocal

   5、volatile关键字

只能用来修饰多个线程共享的字段,无法修饰方法或者代码块

volatile可以实现可见性、有序性,不能实现原子性

 

synchronized的原理???

ReentrantLock的原理:

ReentrantLock位于java.util.concurrent包中,可实现公平锁和非公平锁,默认是非公平锁;

内部有个抽象的静态内部类:Sync,Sync继承了AbstractQueuedSynchronizer(AQS)

lock()和tryLock()的区别:lock()在获取不到锁的时候会一直等待,而tryLock()在获取不到锁的时候不会等待

synchronized和Lock的区别?

1、synchronized属于JVM层面,Lock属于api层面(具体的类来实现)

2、synchronized在代码执行完之后会自动的释放锁;Lock要手动的加锁和释放锁

3、Lock等待可中断、可实现公平锁、可绑定多个条件

 

并发编程中的原子性可见性有序性:

原子性:要么执行完,要么不执行

可见性:一个线程修改变量之后,其他线程可立刻知道变量被修改了

有序性:执行的顺序按照代码的先后顺序执行(会存在指令重排)

 

线程的六种状态(生命周期):

1、新建(NEW)

2、可运行(RUNNABLE):就绪(调用start()方法,等待CPU使用权) 和 运行中(获得CPU的使用权)    yield()

3、锁阻塞(BLOCKED):等待同步锁

4、无限等待(WAITING):wait(),join(),LockSupport.park(),等待其他线程的唤醒

5、计时等待(TIMED_WAITING):sleep(long time),wait(long time),join(long time)

6、被终止(TERMINATED)

public class WaitAndNotify {
    public static void main(String[] args){
        Object obj = new Object();
        
        // 消费者
        new Thread(){
            @Override
            public void run(){
                synchronized(obj){
                    try{
                        System.out.println(Thread.currentThread().getName()+"进入waitting状态,释放锁对象");
                        obj.wait(); //进入无限等待状态
                    }
                    catch(InterruptedException e){
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"被唤醒");
                }
            }
        }.start();

        // 生产者
        new Thread(){
            @Override
            public void run(){
                try{
                    Thread.sleep(5000);
                }
                catch(InterruptedException e){
                    e.printStackTrace();
                }
                synchronized(obj){
                    System.out.println(Thread.currentThread().getName()+"获取到锁对象,调用notify方法,释放锁对象");
                    obj.notify();  // 唤醒等待的线程
                }
            }
        }.start();
    } 
}
View Code

 

线程通性wait()/notify()

wait():使线程进入阻塞状态,并且释放锁,sleep不会释放锁; wait()和sleep的区别? 一个来自Object和一个来自Thread

notify():随机唤醒一个阻塞的线程,被唤醒的线程不会立即拿到锁,而是去竞争锁

notifyAll():唤醒全部阻塞线程

只能在同步代码块或同步方法中配套使用,并且调用者要和同步代码块或同步方法的同步监视器一致,否则导致 "IllegalMonitorStateException" 异常

class Number implements Runnable{
    private int i = 100;
    @Override
    public void run(){
        while(true){
            synchronized (this) {   // 实现交互打印,线程通信?
//                notify();
                this.notifyAll();   // synchronized是this,这里也要是this,wait也要是this
                if (i > 0) {
                    try {
                        Thread.sleep(10);  // 不会释放锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ": " + i--);

                    try {
                        this.wait();  // 会释放锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                } else {
                    break;
                }
            }
        }
    }
}

public class CommunicationTest {
    public static void main(String[] args) {
        Number number = new Number();
        new Thread(number,"n1").start();
        new Thread(number,"n2").start();
        new Thread(number,"n3").start();
    }
}
View Code

 

线程中断:

interrupt():不会中断运行中的线程(除非线程循环检测中断标志位),但是会改变中断标志位为false,有调用(sleep、wait、join等)时会抛出InterruptedException

Thread.interrupted():判断线程是否被中断,并改变中断标志位为false,后面再调用时返回false;

isInterrupted():只是判断线程是否已经中断;

并不是所有的阻塞都能中断,synchronized、I/O操作等不能被中断

try{
  while(!Thread.interrupted()){     // 非阻塞过程中断
        // do something
  }
}catch (InterruptedException e){  // 阻塞过程中断
       
}
interrupt()中断
public class InterruptTest2 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new MyThread());
        thread.start();
        Thread.sleep(1000);
        // 注意:要循环检测中断状态才能中断线程;interrupt()只是发出中断请求,如果不检测不能退出
        thread.interrupt();
        // main线程阻塞,阻塞时中断线程会抛出异常
        thread.join(); 
        System.out.println("end");
    }
}

class MyThread implements Runnable{
    @Override
    public void run(){
        Thread hello = new Thread(new HelloThread());
        hello.start();
        try {
            hello.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println("线程响应中断异常,立即中断");
        }
        // 中断hello线程,不中断仍然会运行
        hello.interrupt();
    }
}

class HelloThread implements Runnable{
    @Override
    public void run(){
        int n = 0;
        // 注意 Thread.interrupted() 和 Thread.currentThread().isInterrupted()的区别
        // Thread.interrupted() 第一次返回true并清除中断标志位,后面调用都是返回false
        // Thread.currentThread().isInterrupted()只是查询中断标志位判断是否中断
        while(!Thread.interrupted()){   
            n++;
            System.out.println("*****hello world" + n + "*****");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
                break;
            }
        }
    }
}
interrupt()

设置中断标志中断线程:

public class InterruptTest3 {
    public static void main(String[] args) throws InterruptedException {
        HelloTest thread = new HelloTest();
        thread.start();
        Thread.sleep(1);
        thread.flag = false;
    }
}

class HelloTest extends Thread {

    public volatile boolean flag = true;

    @Override
    public void run(){
        int i = 0;
        while(flag){
            System.out.println("通过设置标志来中断当前线程" + i++);
        }
        System.out.println("end");
    }
}
自定义volatile类型的中断标志

 

java的死锁:不同的线程分别占用对方的资源,都在等待对方释放资源

死锁检测:

1、jps确定java进程id

2、jstack -pid 可以检查死锁  (此处还没成功实验)

 

posted @ 2021-09-14 17:49  菠萝机  阅读(39)  评论(0编辑  收藏  举报