1 Star 0 Fork 0

cxylk / Java-Notes

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
线程.md 26.75 KB
一键复制 编辑 原始数据 按行查看 历史
cxylk 提交于 2021-08-27 17:53 . :sparkles:线程池

前言

在Java中,线程由Thread类表示,用户创建线程的唯一方式是创建Thread类的一个实例,每一个线程都和这样的实例关联。在相应的Thread实例上调用start()方法来启动一个线程。

内存模型:

操作系统有自己的内存模型,C/C++这些语言直接使用的就是操作系统的内存模型,而Java为了屏蔽各个系统的差异,定义了自己的统一内存模型。

简单来说,Java开发者不再关心每个CPU核心有自己的内存,然后共享内存。而是把关注点转移到:每个线程都有自己的工作内存所有线程共享主内存。

Thread类

  • Thread类中的start()和run()方法有什么区别

    start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果不一样。当调用run()方法的时候,只会在原来的线程中调用,没有新的线程启动。start()方法才会启动新线程。

    实际上,start()方法的作用是通知"线程规划器"该线程已经准备就绪,以便让系统安排一个时间来调用其run()方法,也就是使线程得到运行。

  • Thread类中的start()方法其中调用了native方法start0(),改方法的调用关系:

    -------->jvm.cpp

    -------->thread.cpp

    -------->os_linux.cpp

    -------->pthread_create

    在os_linux.cpp中有这样的一个方法,*java_start(Thread *thread):

    static void *java_start(Thread *thread) {
      // Try to randomize the cache line index of hot stack frames.
      // This helps when threads of the same stack traces evict each other's
      // cache lines. The threads can be either from the same JVM instance, or
      // from different JVM instances. The benefit is especially true for
      // processors with hyperthreading technology.
      static int counter = 0;
      int pid = os::current_process_id();
      ...
      // call one more level start routine
      thread->run();
    
      return 0;
    }

    点击run方法,会进入thread.cpp中的JavaThread::run()方法,里面的实现方法为

    thread_main_inner();

使用线程

三种使用线程的方法:

  • 实现Runnable接口;
  • 实现Callable接口;
  • 继承Thread类
  • 其实还可以使用Executor框架来创建线程池(线程池非常高效,很容易实现和使用)

实现Runnable和Callable接口的类只能当做一个可以在线程中运行的任务,不是真正意义上的线程,因此最后还需要通过Thread来调用。可以理解为任务是通过线程驱动从而执行的

实现Runnable接口

需要实现接口中的run()方法

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        // ...
    }
}

使用Runnable实例再创建一个Thread实例,然后调用Thread实例的start()方法来启动线程。

public static void main(String[] args) {
    MyRunnable instance = new MyRunnable();
    Thread thread = new Thread(instance);
    thread.start();
}

实现Callable接口

与Runnable相比,它有返回值,并且返回值通过FutureTask进行封装

需要实现接口中的call()方法

@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;//会抛出异常
}
public class MyCallable implements Callable<Integer> {
    public Integer call() {
        return 123;
    }
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
    MyCallable mc = new MyCallable();
    FutureTask<Integer> ft = new FutureTask<>(mc);
    Thread thread = new Thread(ft);
    thread.start();
    System.out.println(ft.get());
}

为什么Thread()中可以传进ft? 因为 1.public class FutureTask implements RunnableFuture 2.public interface RunnableFuture extends Runnable, Future 参数类型是接口,调用方法传递的参数可以是这接口中的任何一个实现类对象(面向接口编程)

继承Thread类

同样也是需要实现run()方法,因为Thread类也实现了Runnable接口。

当调用start()方法启动一个线程时,虚拟机会将该线程放入就绪队列中等待被调度,当一个线程被调度时会执行该线程的run()方法

public class ThreadDemo01 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            //获取当前正在执行的线程的名称
            //Thread.currentThread() - 返回当前正在运行的线程的引用
            String name=Thread.currentThread().getName();
            System.out.println(name+"->"+i);
        }
    }
}

class TestThreadDemo01{
    public static void main(String[] args) {
        Thread t1=new ThreadDemo01();
        Thread t2=new ThreadDemo01();
        
        t1.setName("线程one");
        t2.setName("线程two");

        //设置线程的优先级别1~10
        //数字越大,优先级越高
        //并不是优先级越高,就一定会优先执行,只是尽可能提高先执行的概率
        t1.setPriority(Thread.MIN_PRIORITY);//最小值为1

        t2.setPriority(Thread.MAX_PRIORITY);//最大值为10

        //当两个线程都调用了run方法而不是start的时候,这时候就是在mian线程中调用,不会启动新的线程,结果就是main线程的输出
//        t1.run();
//
//        t2.run();

        t1.start();
        t2.start();

        //处于main线程里面
        System.out.println("--stop---");
    }
}

实现接口VS继承Thread

实现接口会更好一些,因为:

  • java不支持多重继承,因此继承了Thread类就无法继承其他类,但是可以实现多个接口
  • 类可能只要求可执行就行,继承整个Thread类开销过大

死锁

package tech.aistar.day16.dead;

/**
 * 本类功能:死锁
 *
 * @author cxylk
 * @date 2020/8/14 18:01
 */
public class DeadLockDemo {
    //静态域更加容易出现死锁 - 死锁 -争抢的是"临界资源"
    //不加static 是不会出现死锁的,因为产生死锁就是去抢同一种资源
    //而static能够保证当前对象只有一份
    public static Object oo1=new Object();
    public static Object oo2=new Object();

    public static void main(String[] args) {
        Thread t1=new T1();
        Thread t2=new T2();

        t1.start();

        t2.start();
    }
}

class T1 extends Thread{
    private DeadLockDemo lock=new DeadLockDemo();

    @Override
    public void run() {
        synchronized (lock.oo1){
            System.out.println("=====1=====");

            //是为了让第二个线程有足够的时间去争抢oo2(T2中的oo2)
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            synchronized (lock.oo2){
                System.out.println("======2=====");
            }
        }
    }
}

class T2 extends Thread{
    private DeadLockDemo lock=new DeadLockDemo();

    @Override
    public void run() {
        synchronized (lock.oo2){
            System.out.println("=====3=====");

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

            synchronized (lock.oo1){
                System.out.println("======4======");
            }
        }
    }
}

生产者和消费者

wait和notify必须放在synchronized中,不然会抛出java.lang.IllegalMonitorStateException异常

package tech.aistar.day16.advice;

/**
 * 本类功能:synchronized实现 生产者和消费者 (线程之间的通信)
 *
 * 不共享代码,但是希望共享资源
 *
 * @author cxylk
 * @date 2020/8/14 14:19
 */
public class AdviceDemo01 {
    public static void main(String[] args) {
        Box box=new Box();

        Thread t1=new Product(box);

        Thread t2=new Customer(box);

        t1.setName("p");
        t2.setName("c");

        t1.start();
        t2.start();
    }
}

//临界资源
class Box{
    private int content;//临界资源

    //核心的代码
    public boolean flag;//默认是false。如果box中有i[设置了值],flag=true,没有生产东西,flag=false
    
    //线程通信的方法wait以及notify或者notifyAll方法的使用一定是出现在线程同步代码块中
    //生产者调用的
    public synchronized void setContent(int content) {
        if(flag){//flat=true,盘中有东西,就不要继续生成,自己会叫醒消费者的线程
            //然后自己进入阻塞状态
            try {
                wait();//当前线程就会进入到阻塞状态,必须是通过其他线程调用notify或者notifyALL
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //flag=false,生产的东西全部被消费了
        //生产者要正常生产
        flag=true;
        this.content=content;
        //通知消费者来消费
        notify();//随机唤醒等待队列上的阻塞线程,1个
    }

    public synchronized int getContent() {
        if(flag==false){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        //当消费完毕
        flag=false;
        
        //唤醒一下生产者线程
        notify();
        
        //返回消费的数量
        return content;
    }
}

//生产者线程
class Product extends Thread{
    private Box box;

    public Product(Box box) {
        this.box = box;
    }

    private int i;

    @Override
    public void run() {
        while(true){
            System.out.println(Thread.currentThread().getName()+"->"+(++i));
            box.setContent(i);

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

//消费者线程
class Customer extends Thread {
    private Box box;

    public Customer(Box box) {
        this.box = box;
    }

    @Override
    public void run() {
        while (true){
            System.out.println(Thread.currentThread().getName()+"->"+box.getContent());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

交替打印字母和数字

  • 按照顺序依次输出a1b2c3d4,俩个线程,一个负责输出字母,一个负责输出数字.
package tech.aistar.day16.numletter;

/**
 * 本类功能:
 *
 * 本类用来演示: 练习 - a1b2c3...
 * 俩个线程,一个负责输出小写,一个负责输出数字,但是要求是依次循环打印.
 *
 * @author cxylk
 * @date 2020/8/14 14:44
 */
public class NumLetter {
    private boolean flag;//初值为false
    public synchronized void printLetter(){
        for (char i = 'a'; i <= 'z'; i++) {
            if(flag){
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.print(i);

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            flag=true;
            notify();
        }
    }

    public synchronized void printNumber(){
        for (int i = 1; i <=26 ; i++) {
            if(flag==false){
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.print(i);

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            flag=false;
            notify();
        }
    }

    public static void main(String[] args) {
        NumLetter n=new NumLetter();

//        Thread t1=new Thread(new Runnable() {
//            @Override
//            public void run() {
//                n.printLetter();
//            }
//        });

        Thread t1=new Thread(()->{
            n.printLetter();
        });

//        Thread t2=new Thread(new Runnable() {
//            @Override
//            public void run() {
//                n.printNumber();
//            }
//        });

        Thread t2=new Thread(()->{
            n.printNumber();
        });

        t1.start();
        t2.start();
    }
}
  • a12b34c56
package tech.aistar.day16.numletter;

/**
 * 本类功能:a12b34c56d78
 *
 * @author cxylk
 * @date 2020/8/14 20:26
 */
public class NumLetter02 {
    private boolean flag;

    public synchronized void printLetter(){
        for (char i = 'a'; i <='z' ; i++) {
            if(flag){
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            //刚进来,flag为false,执行下面
            System.out.print(i);

            try {
                Thread.sleep(1000);//睡1s,cpu去执行第二个线程,然后接着回来执行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            flag=true;

            notify();//唤醒下面阻塞的线程
        }
    }

    public synchronized void printNumber(){
        for (int i = 1; i <=26 ; i+=2) {
            if(flag==false){//第一次进来走这
                try {
                    wait();//睡眠
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.print(i+""+(i+1));//两个数字连着打印

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

            flag=false;

            notify();
        }
    }

    public static void main(String[] args) {
        NumLetter02 n=new NumLetter02();

        Thread t1=new Thread(()->{
            n.printLetter();
        });

        Thread t2=new Thread(()->{
            n.printNumber();
        });

        t1.start();
        t2.start();
    }
}
  • abcABC123abcABC123...
package tech.aistar.day16.locks;

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

/**
 * 本类功能:Lock重新做
 * abcABC123abcABC123abcABC123 - 三个线程依次执行
 *
 * @author cxylk
 * @date 2020/8/14 20:58
 */
public class LockDemo02 {
    private Lock lock=new ReentrantLock();

    //有三个线程,创建三个队列
    //特点 - 很方便进行选择性通知某个等待队列的线程

    //abc
    private Condition c1=lock.newCondition();
    //ABC
    private Condition c2=lock.newCondition();
    //123
    private Condition c3=lock.newCondition();

    //定义一个变量
    private int count=0;

	//打印小写字母
    public void printLower(){
        lock.lock();//手动申请锁

        //lock.lock();//手动申请锁,将这句话放到循环外面提高性能
        try {
            //代码放到try里面
            while (true) {
                if (count != 0) {
                    //等待
                    c1.await();
                }
                System.out.print("abc");
                Thread.sleep(500);
                //打印完后加1
                count = 1;
                //唤醒c2这个等待队列上的线程
                c2.signal();
            }
        } catch(InterruptedException e){
            e.printStackTrace();
        }finally{
            lock.unlock();//手动释放锁
        }
    }

	//打印大写字母
    public void printUpper(){
        lock.lock();//手动申请锁

        //lock.lock();//手动申请锁,将这句话放到循环外面提高性能
        try {
            //代码放到try里面
            while (true) {
                if (count != 1) {
                    //等待
                    c2.await();
                }
                System.out.print("ABC");
                Thread.sleep(500);
                //打印完后加1
                count = 2;
                //唤醒c3这个等待队列上的线程
                c3.signal();
            }
        } catch(InterruptedException e){
            e.printStackTrace();
        }finally{
            lock.unlock();//手动释放锁
        }
    }

	//打印数字
    public void printNum(){
        lock.lock();//手动申请锁

        //lock.lock();//手动申请锁,将这句话放到循环外面提高性能
        try {
            //代码放到try里面
            while (true) {
                if (count != 2) {
                    //等待
                    c3.await();
                }
                System.out.print("123");
                Thread.sleep(500);
                count = 0;
                //唤醒c1这个等待队列上的线程
                c1.signal();
            }
        } catch(InterruptedException e){
            e.printStackTrace();
        }finally{
            lock.unlock();//手动释放锁
        }
    }

    public static void main(String[] args) {
        LockDemo02 demo=new LockDemo02();

        //分别创建三个线程
        Thread t1=new Thread(()->{
            demo.printLower();
        });

        Thread t2=new Thread(()->{
            demo.printUpper();
        });

        Thread t3=new Thread(()->{
            demo.printNum();
        });

        t1.start();

        t2.start();

        t3.start();
    }
}

Lock和synchronized的区别

  • lock是接口,synchronized它是一个关键字
  • lock锁是一个显示锁(手动申请锁,手动释放锁),synchronized隐式锁(自动释放锁)
  • lock手动申请锁(对象锁)
  • lock是锁代码块
  • lock出现异常的时候,是不会主动释放资源的

什么是线程池

顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。

1.Java 5+中的Executor接口(位于java.util.concurrent包下)定义一个执行线程的工具。它的子类型即线程池接口是

ExecutorService.

public interface Executor {
    void execute(Runnable command);
}

public interface ExecutorService extends Executor {
    void shutdown();
    List<Runnable> shutdownNow();
    boolean isTerminated();
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task);
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException; 
     <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;
     <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

2.要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,因此在工具类Executors(java.util.concurrent包下)里面提供了一些静态工厂方法,生成了一些常用的线程池(这四个线程池返回的是一个new ThreadPoolExecutor对象,而ThreadPoolExecutor又是继承于AbstractExecutorService抽象类,AbstractExecutorService又实现了ExecutorService接口,ExecutorService接口又继承了Executor接口)

例:return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
public class ThreadPoolExecutor extends AbstractExecutorService                           

public abstract class AbstractExecutorService implements ExecutorService

public interface ExecutorService extends Executor

public interface Executor

如下所示

  • Executors.newCacheThreadPool:创建一个可缓存的线程池。如果没有可用的现有线程,则将创建一个新的线程并将其添加到池中。60秒未使用的线程被终止并从缓存中删除。因此,保持空闲时间足够长的池将不会消耗任何资源。当任务数增加时,此线程池又可以智能的添加新线程来。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
package tech.aistar.day16.pool;

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

/**
 * 本类功能:
 * 可缓存线程池,先查看池中有没有以前建立的线程
 * 如果有,就直接使用.如果没有,就建一个新的线程加入池中,
 * 缓存型池子通常用于执行一些生存期很短的异步型任务
 *
 * @author cxylk
 * @date 2020/8/15 20:55
 */
public class CacheThreadPoolDemo {
    public static void main(String[] args) {
        //创建一个可缓存池
        ExecutorService executor= Executors.newCachedThreadPool();

        for (int i = 0; i < 10; i++) {
            //会在应用程序中创建大量的线程对象 - 堆里面 - 占系统资源
//            new Thread(()->{
//                System.out.println(Thread.currentThread().getName());
//            }).start();

            //提交任务
            executor.submit(()->{
                System.out.println(Thread.currentThread().getName());
            });

//            try {
//                Thread.sleep(1000);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
        }
        //关闭线程池
        executor.shutdown();
    }
}

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
    	60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}

public class ThreadPoolExecutor extends AbstractExecutorService;

public abstract class AbstractExecutorService implements ExecutorService;

public interface ExecutorService extends Executor
  • Executors.newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程
package tech.aistar.day16.pool;

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

/**
 * 本类功能:创建一个**可重用**固定个数的线程池,以共享的无界队列方式来运行这些线程
 *
 * @author cxylk
 * @date 2020/8/15 21:57
 */
public class FixedThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService executorService= Executors.newFixedThreadPool(3);//参数代表池中的线程数

        for (int i = 0; i < 3; i++) {
            //提交任务
            executorService.submit(()->{
                System.out.println(Thread.currentThread().getName());
            });
        }
        executorService.shutdown();
    }
}


public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
    	0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}

public class ThreadPoolExecutor extends AbstractExecutorService;

public abstract class AbstractExecutorService implements ExecutorService;

public interface ExecutorService extends Executor
  • Executors.newScheduledThreadPool(int n):
 为什么与前面不一样?ScheduledExecutorService executorService= Executors.newScheduledThreadPool(2);
 
new ScheduledThreadPoolExecutor(corePoolSize)->public class ScheduledThreadPoolExecutor
extends ThreadPoolExecutor implements ScheduledExecutorService

public class ThreadPoolExecutor extends AbstractExecutorService
public abstract class AbstractExecutorService implements ExecutorService
public interface ExecutorService extends Executor

public interface ScheduledExecutorService extends ExecutorService
public interface ExecutorService extends Executor
 

package tech.aistar.day16.pool;

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

/**
 * 本类功能:创建一个定长线程池,支持定时及周期性任务执行
 *
 * @author cxylk
 * @date 2020/8/15 22:32
 */
public class ScheduledThreadDemo {
    public static void main(String[] args) {
        ScheduledExecutorService executorService= Executors.newScheduledThreadPool(2);
    }
}


//command:要执行的任务,initialDelay:延迟首次执行的时间,period:两次执行之间的时间间隔
//unit:initialDelay和period参数的时间单位
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit);
  • Executors.newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有的任务按照指定顺序(FIFO,LIFO,优先级)执行
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService(可终结的委托执行任务)
    (new ThreadPoolExecutor(1, 1,
    0L, TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue<Runnable>()));
}

static class FinalizableDelegatedExecutorService
    extends DelegatedExecutorService {
    FinalizableDelegatedExecutorService(ExecutorService executor) {
    	super(executor);
    }
    protected void finalize() {
    super.shutdown();
    }
}

//super:->
static class DelegatedExecutorService extends AbstractExecutorService {
    ...
    //委托执行员任务
	DelegatedExecutorService(ExecutorService executor) { e = executor; }
	...
}

package tech.aistar.day16.pool;

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

/**
 * 本类功能:创建一个单线程化的线程池
 * 它只会用唯一的工作线程来执行任务,保证所有的任务
 * 按照指定顺序(FIFO,LIFO,优先级)执行
 *
 * @author cxylk
 * @date 2020/8/15 20:51
 */
public class SingleThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService executorService= Executors.newSingleThreadExecutor();

        for (int i = 0; i < 10; i++) {
            executorService.submit(()->{
                System.out.println(Thread.currentThread().getName());
            });
        }
        executorService.shutdown();
    }
}
1
https://gitee.com/cxylk/Java-Notes.git
git@gitee.com:cxylk/Java-Notes.git
cxylk
Java-Notes
Java-Notes
main

搜索帮助

53164aa7 5694891 3bd8fe86 5694891