线程

扫码查看


1. 进程与线程

进程:资源分配的基本单位

线程:资源调度的基本单位


1.1 有了进程为什么还需要线程呢?

为了进程能进行并发操作


1.2 线程的生命周期


2. 创建进程

创建进程有两种方法(一般推荐第二种,因为单继承问题)


先来看看线程的构造函数


2.1 继承Thread类,重写run()

public class Threadtest extends Thread {

    //设置名字
    public Threadtest(String name) {
        super(name);
    }

    //重写方法
    public void run(){
        for(int i = 0;i < 100;i++){
            System.out.println(Thread.currentThread().getName() + "-----" + i);
        }
    }

    public static void main(String[] args) {

        Threadtest t1 = new Threadtest("线程1");
        Threadtest t2 = new Threadtest("线程2");

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


2.2 实现Runnable接口,重写run()

public class Runnabletest implements Runnable {

    @Override
    public void run() {
        for(int i = 0;i < 100;i++){
            System.out.println(Thread.currentThread().getName() + ":::" + i);
        }
    }

    public static void main(String[] args) {

        Thread t1 = new Thread(new Runnabletest(),"线程1");
        Thread t2 = new Thread(new Runnabletest(),"线程2");

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




3.线程的方法


3.1 守护线程(setDaemon)

  • 守护进程是为其他线程服务的线程,存在于后台,一旦有线程就存在,线程全部消失而结束

  • 需要在进程启动前调用Thread.setDaemon(true)


3.2 优先级(setPriority)

  • 设置获取CPU时间片的几率,分0—10等级,默认为5
public static void main(String[] args) {

        Threadtest t1 = new Threadtest("线程1");
        Threadtest t2 = new Threadtest("线程2");

        //设置优先级
        t1.setPriority(10);

        t1.start();
        t2.start();
}
线程1:::97
线程1:::98
线程1:::99
线程2:::3
线程2:::4
线程2:::5

在前面实例中调用该函数,发现t1线程cpu执行时间片多于t2线程,t1完成了t2还在开头


3.3 sleep

让该线程休眠

try {
    Thread.sleep(500);      //休眠500ms
} catch (InterruptedException e) {
    e.printStackTrace();
}


3.4 join

使当前线程停下来等待,直到调用join()的线程结束,才恢复执行

pulic class extends Thread{
    Thread B;
    run(){
        B.join();       //在线程A中执行线程B
        ……
    }
}


3.5 wait和notify

wait使线程挂起,notify使线程唤醒

thread.wait();
thread.notify();




4. 线程同步


并发出现的问题,线程可以解决,这里引入锁机制

  • 可见性:一个线程对共享变量的修改,另一个线程能立刻看到
  • 原子性:执行多个操作,其中一个操作没执行的话,全部操作也不执行;否则全部执行
  • 有序性:代码的执行顺序按照代码的先后顺序执行,不用考虑重排序


4.1 synchronized锁

  • 它是java的关键字,可以修饰方法,代码块,类

  • synchronized锁一次只能允许一个线程进入被锁住的代码块,java每个对象都有内置锁,synchronized就是使用对象的内置锁来锁定的


4.1.1 方法锁

public class Synchronizedtest implements Runnable {

    //使用的是该类的锁
    @Override
    public synchronized void run() {
        for(int i = 0;i < 100;i++){
            System.out.println(Thread.currentThread().getName() + "------" + i);
        }
    }

    public static void main(String[] args) {

        Synchronizedtest st = new Synchronizedtest();
        Thread t1 = new Thread(st,"线程1");
        Thread t2 = new Thread(st,"线程2");

        t1.start();
        t2.start();
    }
}
线程1------96
线程1------97
线程1------98
线程1------99    //获得锁,执行完才释放,t2线程不能执行该方法
线程2------0
线程2------1
线程2------2
线程2------3
线程2------4


4.1.2 代码块锁

public void run() {

    //使用的也是该类的锁,打印结果是一致的
    synchronized(this){
        for(int i = 0;i < 100;i++){
            System.out.println(Thread.currentThread().getName() + "------" + i);
        }
    }
}


4.2 volatile

该关键字只修饰类变量和实例变量,禁止了指令重排序,当一个线程修改volatile修饰的变量,另一个线程会立即看到最新的变化,解决了可见性问题

public volatile int num;




5. 线程死锁

  • 互斥条件:线程使用的资源不共享
  • 请求与保持条件:一个线程有一个资源且等待获取一个被其他线程拥有的资源
  • 非剥夺条件:分配的资源不能从相应的线程中被强制剥夺
  • 循环等待条件:一个线程等待其他线程,其他线程又等待该线程




6. 线程池

线程池和数据库的连接池同样意思,把多个线程放在一个集合里,有任务时从集合里分配线程,当该线程完成任务后不是销毁,放入线程池等待下次任务,减少了创建和销毁线程的次数,提高系统效率

这里只讨论ThreadPoolExecutor连接池,有三个常见的实现池

  • newFixedThreadPool:返回corePoolSize和maximumPoolSize相等的线程池
  • newCachedThreadPool:如果线程池里没有空闲线程,线程池也会创建一条新的线程去处理这个任务
  • SingleThreadExecutor:使用单个worker线程的Executor


6.1 瞻要

  • corePoolSize:核心线程的大小
  • maximumPoolSize:线程池最大的线程数
  • keepAliveTime:允许线程空闲的时间
  • unit:时间单位
  • workQueue:阻塞队列
  • threadFactory:线程工厂
  • handler:拒绝策略
  • 生命周期:RUNNING,SHUTDOWN,STOP,TIDYING,TERMINATED
  • Callable和Future

主要方法

execute()       //线程池提交任务的
submit()        //线程池提交任务的,能够返回任务执行的结果
shutdown()      //线程池状态变为SHUTDOWN
shutdownNow()   //线程池状态立刻变为STOP


6.2 实现

public class ThreadPool {

    public static void main(String[] args) throws InterruptedException, ExecutionException {

        // 创建线程池
        ExecutorService pool = Executors.newFixedThreadPool(10);

        // 提交Callable事务,future拿结果
        Future f1 = pool.submit(new Callabletest("线程1"));
        Future f2 = pool.submit(new Callabletest("线程2"));

        // future的get方法返回结果
         Object oj1 = f1.get();
         Object oj2 = f2.get();

        System.out.println(oj1);
        System.out.println(oj2);

        // 关闭线程池
        pool.shutdown();
    }
}
public class Callabletest<V> implements Callable<V> {

    private V num;

    public Callabletest(V num) {
        this.num = num;
    }

    @Override
    public V call() throws Exception {
        return num;
    }
}
<!-- 打印 -->
线程1
线程2




7. ThreadLocal

ThreadLocal让线程有自己的局部变量,其中重要的方法有:

  • set()
  • get()
  • remove() //可以避免内存泄漏
public class DBUtil {
    //数据库连接池
    private static BasicDataSource source;

    //为不同的线程管理连接
    private static ThreadLocal<Connection> local;

    static {
        try {

            //省略配置操作
            ………………

            //初始化线程本地
            local = new ThreadLocal<>();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static Connection getConnection() throws SQLException {
        //获取Connection对象
        Connection connection = source.getConnection();

        //把Connection放进ThreadLocal里面
        local.set(connection);

        //返回Connection对象
        return connection;
    }

    //关闭数据库连接
    public static void closeConnection() {
        //从线程中拿到Connection对象
        Connection connection = local.get();

        try {
            if (connection != null) {
                //恢复连接为自动提交
                connection.setAutoCommit(true);

                //这里不是真的把连接关了,只是将该连接归还给连接池
                connection.close();

                //既然连接已经归还给连接池了,ThreadLocal保存的Connction对象也已经没用了
                local.remove();

            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

参考Java3y






02-13 05:26
查看更多