二、线程并发基础之Java里的Thread

请添加图片描述

1. 线程简单实现的三种方法

1.1 直接extendsThread覆盖run()方法即可

/**
 * @Author KanLina
 * @Description 创建线程方式(一)
 * @Date 10/22/21 2:43 PM
 **/
public class ThreadA extends Thread {

    @Override
    public void run() {
        super.run();
        try {
            //模拟做事情执行500ms
            Thread.sleep(500);
        }catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("这是线程A");
    }
}```
```public class ThreadMain {
    //执行线程程序
    public static void main(String[] args) {
        ThreadA threadA = new ThreadA();
        //启动线程A
        threadA.start();
        System.out.println("这是主线程:");
    }
}

执行结果:
请添加图片描述
缺点:一个Java类职能继承一个父类

1.2 实现Runnable接口,实现run()方法:

/**
 * @Author KanLina
 * @Description 创建线程(二)
 * @Date 10/22/21 2:55 PM
 **/
public class ThreadB implements Runnable {

    @Override
    public void run() {
        try{
            //模拟做事情执行500ms
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("这是线程B");
    }
} ```
 ```public static void main(String[] args) {
//        ThreadA threadA = new ThreadA();
//        //启动线程A
//        threadA.start();
//        System.out.println("这是主线程:");

        //启动线程B
        ThreadB threadB = new ThreadB();
        //启动方式存在差异
        new Thread(threadB).start();
        System.out.println("这是主线程");
    }

运行结果:
请添加图片描述
优点:Java里面可以有多个接口,解决extends缺点

1.3 实现Callable,实现call()方法得到线程执行结果

/**
 * @Author KanLina
 * @Description 创建线程方式(三)
 * @Date 10/26/21 2:41 PM
 **/
public class ThreadC implements Callable<String> {

    @Override
    public String call() throws Exception {
        try {
            //模拟做事情500ms
            Thread.sleep(500);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("这是线程C");
        return "ThreadC";
    }
}
		//启动线程C
        ThreadC threadC = new ThreadC();
        FutureTask<String> futureTask = new FutureTask<String>(threadC);
        //这里启动方式不一样!
        new Thread(futureTask).start();
        System.out.println("这是主线程:begin!");
        //只有主线程get到才会继续执行主线程
        try {
            System.out.println("得到到返回结果为:"+futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println("这是主线程:end!");

执行结果:
请添加图片描述
启动方式不一样,只有在主线程get时才会继续执行线程。
总结:线程继承Thread时,启动线程直接threadA.start();实现 Runnable时需要 new Thread(threadB).start();实现 Callable需要创建 FutureTask,然后 new Thread(futureTask).start();

2. Thread里面的属性和方法

2.1 常用的构造方法: 请添加图片描述
2.2 线程优先级 getPriority()
调用Thread类的方法getPriority()和 setPriority()存取线程优先级,优先级界于1(MIN_PRIORITY)和10(MAX_PRIORITY)之间,缺省是5(NORM_PRIORITY)

2.3 线程属性的运用

package 线程的属性;

/**
 * @Author KanLina
 * @Description 线程属性的大概运用
 * @Date 10/26/21 3:13 PM
 **/
public class ThreadA implements Runnable {
    @Override
    public void run() {
        try {
            //模拟做事情100s,使监控工具控制到
            Thread.sleep(100000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //获取当前线程 Thread.currentThread()
        Thread currentThread = Thread.currentThread();

        //获取当前线程名称 currentThread.getName()
        String name = currentThread.getName();
        System.out.println("这是线程的名称:" + currentThread.getName());

        //当前线程的标识符 currentThread.getId()
        long id = currentThread.getId();
        System.out.println("返回当前线程:" + name + "的标识符:" + id);

        //线程的优先级 currentThread.getPriority()
        int priority = currentThread.getPriority();
        System.out.println("返回当前线程:" + name + "的优先级:" + priority);

        //线程的状态 currentThread.getState();
        Thread.State state = currentThread.getState();
        System.out.println("返回当前线程:" + name + "的状态:" + state);

        //线程的所属组 currentThread.getThreadGroup();
        ThreadGroup threadGroup = currentThread.getThreadGroup();
        System.out.println("返回当前线程:" + name + "的所属组:" + threadGroup);

        //线程是否处于活跃状态 currentThread.isAlive()
        boolean alive = currentThread.isAlive();
        System.out.println("返回当前线程:" + name + "活跃状态:" + alive);

        //当前线程是否为守护线程 currentThread.isDaemon();
        boolean daemon = currentThread.isDaemon();
        System.out.println("返回当前线程:" + name + "是否为守护线程:" + daemon);
    }
}
public class ThreadMain {
    public static void main(String[] args) {
        ThreadA threadA = new ThreadA();
        for (int i = 0; i < 5; i++) {
            new Thread(threadA, "线程名称:(" + i + ")").start();
        }
        //返回对当前对象的引用,获得主线程
        Thread threadMain = Thread.currentThread();
        System.out.println("这是主线程");
        System.out.println("返回当前线程的线程组中活动线程的数目:" + Thread.activeCount());
        System.out.println("主线程名称:" + threadMain.getName());
        System.out.println("返回该线程的标识符:" + threadMain.getId());
        System.out.println("返回线程的优先级:" + threadMain.getPriority());
        System.out.println("返回线程的状态:" + threadMain.getState());
        System.out.println("返回该线程所属的线程组:" + threadMain.getThreadGroup());
        System.out.println("测试该线程是否为守护线程" + threadMain.isDaemon());
        try {
            Thread.sleep(10000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:请添加图片描述
注意:主线程先结束
Thread使用注意如下几点:
开启一个新线程一定要起一个名字,方便追踪;
resume、stop、suspend等方法已被废除,建议使用信号量或interrupt方法来代替stop方法等。
Main方法主线程结束,新开启的子线程未必结束。

3. 关于线程的中断机制

3.1 方式一:Thread.stop()
强制停止线程,抛出ThreadDeath对象作为异常。stop不安全,已不建议使用。
3.2 方式二:Thread.interrupt()
线程t1想中断线程t2可以在t1中将线程t2对象中断标识设置为true,t2选择合适时间来处理,也可以不处理。请添加图片描述

package 线程中断;

/**
 * @Author KanLina
 * @Description 线程中断方法
 * @Date 10/26/21 6:45 PM
 **/
public class ThreadInterruptDemo implements Runnable {
    @Override
    public void run() {
        boolean flag = false;
        while (!flag){
            System.out.println("My Thread is running...");
            long l = System.currentTimeMillis();
            while ((System.currentTimeMillis() - l <1000)){
                //让循环持续一段时间,使上面打印次数减少
            }
            if (Thread.currentThread().isInterrupted()){
                //需要线程本身处理终止状态
                break;
            }
        }
        System.out.println("My Thread exting under request...");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new ThreadInterruptDemo(),"InterruptDemo Thread");
        System.out.println("Starting thread...");
        thread.start();
        Thread.sleep(3000);
        System.out.println("Interrupting thread...");
        //触发中断线程
        thread.interrupt();
        System.out.println("线程是否中断:"+thread.isInterrupted());
        Thread.sleep(3000);
        System.out.println("Stopping application...");

    }
}

注意:被终止的线程一定要对isInterrupted状态进行处理,否则如果代码是死循环的情况下,线程将永远都不会被结束。

4. 线程的生命周期

4.1 线程生命周期的五种状态,new、runnable、running、blocked、dead
新建(new Thread):创建对象时线程进入新建状态,分配内存但未运行。线程不是活的。
就绪(runnable):线程启动但不一定立即执行,等待被分配给CPU时间片。使用start()方法启动线程进入就绪状态。线程是活的。
运行(running):run(),线程获得CPU资源正在执行任务,除非线程放弃CPU资源或者有更高优先级的线程进入,线程将一直运行到结束。线程是活着的。
堵塞(blocked):让出CPU,sleep()-线程进入睡眠方式,指定时间可以进入就绪状态。wait()-调用notify回到就绪状态。suspend()-调用resume方法恢复。线程是活的。
死亡(dead):线程执行完毕或被杀死,无法再进入就绪状态。自然终止-正常运行run(),异常终止-stop()。线程不是活着的。
请添加图片描述
请添加图片描述

5. 守护线程

5.1 简单理解为后台运行线程,进程结束,守护线程也结束。Java垃圾回收、内存管理、数据库连接池等也是一个守护线程,不需要关心结束问题。启动线程前调用对象的setDaemon(true),可以设置为守护线程。

注意:前台线程结束,守护线程无论是否结束都会终止!

守护线程测试代码:

package 守护线程;

/**
 * @Author KanLina
 * @Description 主线程
 * @Date 10/28/21 11:21 AM
 **/
public class ThreadMain {
    public static void main(String[] args) {
        ThreadA threadA = new ThreadA();
        ThreadB threadB = new ThreadB();
        //设置为
        threadA.setDaemon(true);

        threadB.start();
        threadA.start();

        Thread threadMain = Thread.currentThread();
        System.out.println("线程A是不是守护线程:"+threadA.isDaemon());
        System.out.println("线程B是不是守护线程:"+threadB.isDaemon());
        System.out.println("线程main是不是守护线程:"+threadMain.isDaemon());

    }
}
package 守护线程;

/**
 * @Author KanLina
 * @Description 线程A
 * @Date 10/28/21 10:59 AM
 **/
public class ThreadA extends Thread {
    @Override
    public void run() {
        for (long i = 0; i < 999999L; i++) {
            System.out.println("后台线程A第" + i + "次执行");
            try {
                Thread.sleep(7);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
package 守护线程;

/**
 * @Author KanLina
 * @Description 线程B
 * @Date 10/28/21 11:02 AM
 **/
public class ThreadB extends Thread {
    @Override
    public void run() {
        for (long i = 0; i < 5; i++) {
            System.out.println("线程B第" + i + "次执行!");
            try {
                Thread.sleep(7);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

6. 线程组

6.1 方便管理线程,可以统一设定线程组的一些属性,例如setDaemon、未处理异常的处理方法、统一安全策略、获取线程信息。

Thread.currentThread().getThreadGroup()获取当前线程组

线程组和线程池的区别:前者管理线程,后者管理线程生命周期,复用线程,减少开销。

7. 当前线程副本:ThreadLocal

7.1 目标变量相当于线程的本地变量,每个线程可以独立改变自己的副本,而不会影响其他线程所对应的副本。
7.2 ThreadLocal类接口:
void set(T value),设置当前线程的线程局部变量的值;
public T get(),该方法返回当前线程所对应的线程局部变量;
public void remove(),当前线程局部变量的值删除,减少内存占用,线程结束后对应的局部变量自动回收,此方法可以加快回收速度。
protected T initialValue(),返回该线程局部变量的初始值,线程第一次调用get/set方法时才执行,仅执行一次。缺省直接返回null。

package 当前线程副本;

/**
 * @Author KanLina
 * @Description ThreadLocal本地线程副本
 * @Date 10/28/21 5:24 PM
 **/
public class ThreadMain {
    //通过匿名内部类覆盖ThreadLocal的initialValue方法,指定初始值
    private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {
        public Integer initialValue() {
            return 0;
        }
    };

    public ThreadLocal<Integer> getThreadLocl() {
        return seqNum;
    }

    //获取下一个序列值
    public int getNextNum() {
        seqNum.set(seqNum.get() + 1);
        return seqNum.get();
    }

    public static void main(String[] args) {
        ThreadMain threadMain = new ThreadMain();
        //三个线程共享一个main,各自产生序列号
        TestClient testClient = new TestClient(threadMain);
        TestClient testClient1 = new TestClient(threadMain);
        TestClient testClient2 = new TestClient(threadMain);
        testClient.start();
        testClient1.start();
        testClient2.start();

    }

    private static class TestClient extends Thread {
        private ThreadMain sn;

        public TestClient(ThreadMain sn) {
            this.sn = sn;
        }

        public void run() {
            for (int i = 0; i < 3; i++) {
                System.out.println("thread[" + Thread.currentThread().getName() + "] --> sn[" + sn.getNextNum() + "]");
            }
            //删除线程
            sn.getThreadLocl().remove();
        }
    }
}

ThreadLocal如何实现变量访问在不同线程之中隔离:
请添加图片描述

通过getMap方法获取和当前线程相关的ThreadLocalMap,然后将变量值设置到ThreadLocalMap中,为空时通过createMap方法创建,ThreadLocalMap为ThreadLocal静态内部类,键值对形式保证只能当前线程读取和修改值。不会发生并发错误。
ThreadLocal处理线程局部变量要比synchronized同步机制解决线程安全问题更简单,更方便,结果程序拥有更高的并发性。

8. 线程异常的处理

8.1 所有线程都不允许抛出未捕获的checked exception。线程是独立执行的代码片段,线程问题应该由线程解决,不委托外部。
8.2 在thread里,处理checked exception,使用try/catch块就可以。unchecked exception处理不一样。需要实现handle unchecked exception。
实现步骤:定义类实现UncaushtExeceptionHandler接口,在实现的方法里包含对异常处理的逻辑和步骤;

package 线程异常;
/**
 * @Author KanLina
 * @Description unchecked exceprion异常设置
 * @Date 10/28/21 7:49 PM
 **/
public class ExceptionHandlerThread implements Thread.UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        System.out.printf("An excepion has bean captured\n");
        System.out.printf("ThreadA: %s\n", t.getId());
        System.out.printf("Exception: %s: %s\n", e.getClass().getName(), e.getMessage());
        System.out.printf("Stack Trace: \n");
        e.printStackTrace(System.out);
        System.out.printf("ThreadA status: %s\n", t.getState());
    }
}

定义线程执行结构和逻辑。

package 线程异常;

/**
 * @Author KanLina
 * @Description 线程
 * @Date 10/28/21 7:55 PM
 **/
public class ThreadA implements Runnable {
    @Override
    public void run() {
        //使线程产生一个异常
        int num = Integer.parseInt("TTT");
    }
}

在创建和执行该子线程的方法中,在thread.start()语句前增加一个thread.setUncaughtExceptionHandler语句来实现处理逻辑的注册。

package 线程异常;

/**
 * @Author KanLina
 * @Description 主线程
 * @Date 10/28/21 7:56 PM
 **/
public class ThreadMain {
    public static void main(String[] args) {
        ThreadA threadA = new ThreadA();
        Thread thread = new Thread(threadA);
        thread.setUncaughtExceptionHandler(new ExceptionHandlerThread());
        thread.start();
    }
}

不使用事件注册机制运行结果:
请添加图片描述
使用事件注册机制运行结果:
请添加图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值