java并发编程之线程

java并发编程之线程,第1张

java并发编程之线程 线程的几种创建方式

1、继承Thread类,作为线程对象存在(继承Thread对象)

	public class CreateThreadTest01 extends Thread{
    public CreateThreadTest01(String CreateName){
        super(CreateName);
    }
    @Override
    public void run(){
        while (!interrupted()){
            System.out.println(getName()+"线程执行了......");
            try{
                Thread.sleep(2000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        CreateThreadTest01 test01 = new CreateThreadTest01("Name1");
        CreateThreadTest01 test02 = new CreateThreadTest01("Name2");
        test01.start();
        test02.start();
        //test01.interrupt();//中断线程1
    }
}

interrupted方法,可以用来判断该线程是否被中断。(终止线程不允许用stop方法,该方法不会施放占用的资源。所以我们在设计程序的时候,要按照中断线程的思维去设计,就像实例代码一样)。

2、实现runnable接口,作为线程任务存在

public class CreateThreadTest02 implements Runnable{
    @Override
    public void run() {
        System.out.println("线程成功执行了");
    }

    public static void main(String[] args) {
        // 将线程任务传递给线程对象
        Thread thread = new Thread(new CreateThreadTest02());
        //启动线程
        thread.start();
    }
}

Runnable 只是来修饰线程所执行的任务,它不是一个线程对象。想要启动Runnable对象,必须将它放到一个线程对象里。

3、匿名内部类创建线程对象

public class CreateThreadTest03 extends Thread{
    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run(){
                System.out.println("无参数线程创建成功!");
            }
        }.start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("带线程任务的线程对象执行了");
            }
        }).start();
        // 创建带线程任务并且重写的run方法的线程对象
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("runnable run 线程执行了。。。。");
            }
        }){
            @Override
            public void run(){
                System.out.println("Override run 执行了");
            }
        }.start();
        // 调用的重写的方法应该是Thread类的run方法。而不是Runnable接口的run方法。
    }
}

4、利用接口Callable创建带返回值的线程

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

public class CreateThreadTeat04 implements Callable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CreateThreadTeat04 teat04 = new CreateThreadTeat04();
        // 最终实现的是Runnable的接口
        FutureTask task = new FutureTask(teat04);
        Thread thread = new Thread(task);
        thread.start();
        System.out.println("哈哈哈哈哈");
        String str = task.get();
        System.out.println("在线程的结果是:"+str);
    }
    @Override
    public Object call() throws Exception {
        Thread.sleep(2000);
        return "ABCD";
    }
}

Callable的接口的代码如下:

@FunctionalInterface
public interface Callable {
    
    V call() throws Exception;
}

5、定时器Timer

import java.util.Timer;
import java.util.TimerTask;

public class CreateThreadTest05 {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("定时器的线程执行了......");
            }
        },0,1000);
    }
}

6、线程池创建线程
线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。
Java 5版本或者更高的版本中的Executor接口定义一个执行线程的工具。它的子类型即线程池接口是ExecutorService。要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,因此在工具类Executors面提供了一些静态工厂方法,生成一些常用的线程池,如下所示:
newSingleThreadExecutor: 创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

public class SingleThreadExecutor {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            executorService.execute(new FixedThreadPoolExecutor.Task());
        }
    }
}

newFixedThreadPool: 创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

public class FixedThreadPoolExecutor {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            executorService.execute(new Task());
        }
        // 手动关闭线程
        executorService.shutdown();
    }
    static class Task implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }
}

newCachedThreadPool: 创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于 *** 作系统(或者说JVM)能够创建的最大线程大小。

public class CachedThreadPoolExecutor {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            executorService.execute(new FixedThreadPoolExecutor.Task());
        }
    }
}

newScheduledThreadPool: 创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

public class ScheduledThreadPoolExecutor {
    public static void main(String[] args) throws InterruptedException {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
        //scheduledExecutorService.schedule(new FixedThreadPoolExecutor.Task(), 1, TimeUnit.SECONDS);
        // 每隔一秒一执行
        scheduledExecutorService.scheduleAtFixedRate(new FixedThreadPoolExecutor.Task(), 1 , 1, TimeUnit.SECONDS);
        Thread.sleep(5000);
        scheduledExecutorService.shutdown();
    }
}

newSingleThreadExecutor: 创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CreateThreadTest06 {
    public static void main(String[] args) {
        // 创建具有5个线程的线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(5);
        long threadPoolStart= System.currentTimeMillis();
        for (int i = 0; i < 5; i++) {
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"线程执行了......");
                }
            });
        }
        long threadPoolEnd = System.currentTimeMillis();
        System.out.println("创建5个线程用时:"+(threadPoolEnd-threadPoolStart));
        // 销毁线程池
        threadPool.shutdown();
    }
}

停止线程池

shutdown
isShutdown:是否停止的状态,开始运行就是true
isTerminated:线程是否完全停止
awaitTerminated:在一定时间内线程是否停止 返回boolean
stutdownNow:立即停止,返回被中断的列表。

线程池的状态

RUNNING:接受新任务并处理排第任务。
SHUTDOWN:不接受新的任务,但处理排队任务。
STOP:不接受新任务,也不处理排队任务,并中断正在进行的任务。
TIDYING:所有任务都已终止,workCont为零时,线程会转换到TIDTYING状态,并将运行terminate()的钩子方法。
TERMINATED:terminate()运行完成。

守护线程 ThreadLocal

从名字可以看出这叫线程变量,意思是ThreadLocal中填充的的变量输入当前线程,该变量对其他线程而言是隔离的,ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
作用: 让某个需要用到的对象在线程间隔离(每个线程都有自己独立的对象)在任何方法中都可以轻松获取到该对象。
使用场景:

1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
2、线程间数据隔离
3、进行事务 *** 作,用于存储线程事务信息。
4、数据库连接,Session会话管理。

场景举例1:每个线程需要一个独享的对象(通常是工具类如:SimpleDateFormat和Random)

public class ThreadLocal01 {
    private static ExecutorService executorService = Executors.newFixedThreadPool(10);
    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            int index = i;
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    String result = new ThreadLocal01().data(index);
                    System.out.println(result);
                }
            });
        }
        executorService.shutdown();
    }
    public String data(int index){
        Date date = new Date(1000 * index);
        SimpleDateFormat simpleDateFormat = ThreadSafeSimpleDateFormat.threadLocal2.get();
        return simpleDateFormat.format(date);
    }
}
class ThreadSafeSimpleDateFormat {
    public static ThreadLocal threadLocal = new ThreadLocal(){
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        }
    };
    public static ThreadLocal threadLocal2 = ThreadLocal.withInitial(()-> new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"));
}

场景举例2:每个线程内需要保持全局变量(如:拦截器中获取用户信息)可以让不同方法直接使用,避免参数传递的麻烦

public class ThreadLocal02 {
    public static void main(String[] args) {
        Server1 server1 = new Server1();
        server1.process();
    }
}
class Server1{
    public void process(){
        User user = new User("张三");
        UserContextHolder.userThreadLocal.set(user);
        Server2 server2 = new Server2();
        server2.process();
    }
}
class Server2{
    public void process(){
        User user = UserContextHolder.userThreadLocal.get();
        System.out.println("Server2 获取用户名字:" + user.getName());
        UserContextHolder.userThreadLocal.remove();
        User newUser = new User("李四");
        UserContextHolder.userThreadLocal.set(newUser);
        Server3 server3 = new Server3();
        server3.process();
    }
}
class Server3{
    public void process(){
        User user = UserContextHolder.userThreadLocal.get();
        System.out.println("Server3 获取新用户名字:" + user.getName());
        // 用完后一定要remove
        UserContextHolder.userThreadLocal.remove();
    }
}
class User{
    private String name;
    public String getName() {
        return name;
    }
    public User(String name) {
        this.name = name;
    }
}
class UserContextHolder{
    public static ThreadLocal userThreadLocal = new ThreadLocal<>();
}
线程的基本状态

Java线程运行的生命周期中只可能是6种不同的状态之一,在给定的一个时刻,线程只能处于其中的一个状态。

1、New状态:初始状态,线程被构建出来,但是还没有调用start()方法
2、 Runnable状态:运行状态,java线程把 *** 作系统的就绪和运行两种状态统称为“运行中”。
3、 Blocked状态:阻塞状态,表示线程阻塞与锁。
4、 Waiting状态:等待状态,表示线程进入等待状态,进入该状态表示线程需要等待其他线程做出一些特定的动作或通知。
5、TIME WAITING状态:超时等待状态,该状态不同于WAITING,它是可以在指定的时间自行返回的
6、 TIME WAITING状态:终止状态,表示当前线程已经执行完毕

Java线程在自身的生命周期中,并不是固定地处于某个状态,而是随着代码的执行在不同的状态之间进行切换,Java线程状态变迁如下图所示:

线程创建之后,调用start()方法开始运行。当线程执行wait()方法之后,线程进入等待状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而超时等待状态相当于在等待状态的基础上增加了超时限制,也就是超时时间到达时将会返回到运行状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到阻塞状态。线程在执行Runnable的run()方法之后将会进入到终止状态。

实例测试:

public class ThreadState {
    public static void main(String[] args) {
        new Thread(new TimeWaiting(),"TimeWaitingThread").start();
        new Thread(new Waiting(),"WaitingThread").start();

        new Thread(new Blocked(),"Blocked-1").start();
        new Thread(new Blocked(),"Blocked-2").start();
    }
    // 这个线程不断地休眠
    static class TimeWaiting implements Runnable{
        @Override
        public void run() {
            while (true){
                SleepUtils.second(2000);
            }
        }
    }
    // 这个线程在Waiting.Class上等待
    static class Waiting implements Runnable{
        @Override
        public void run() {
            while (true){
                synchronized (Waiting.class){
                    try {
                        Waiting.class.wait();
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    // 该线程在Blocked.class实例上加锁后,不会释放该锁
    static class Blocked implements Runnable{
        @Override
        public void run() {
            synchronized (Blocked.class){
                while (true){
                    SleepUtils.second(1000);
                }
            }
        }
    }
}

代码2(放在同一个文件夹下运行)

import java.util.concurrent.TimeUnit;

public class SleepUtils {
    public static final void second(long second){
        try {
            TimeUnit.SECONDS.sleep(second);
        } catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

运行代码,打开终端或者命令提示符,键入“jps”,输出如下:

然后就可以看到运行实例对应的进程ID是177428,然后在命令行终端输入jstack 17748,部分终端输出如下:



停止运行的线程

线程在运行的过程中退出有如下几种方法:

1、使用退出标志,使得线程正常退出,就是说当run方法执行完毕后线程终止。
2、使用stop方法强制退出,但是不推荐这个方法
3、使用interrupt方法中断线程

public class ThreadTest extends Thread{
    volatile boolean stop = false;
    public void run(){
        while (!stop){
            System.out.println(getName()+"线程正在运行中......");
            try{
                sleep(1000);
            }catch (InterruptedException e){
                System.out.println("wake up from block ......");
                stop = true;
                e.printStackTrace();
            }
        }
        System.out.println(getName()+"线程已经退出了......");
    }
}
class InterruptThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        ThreadTest threadTest = new ThreadTest();
        System.out.println("线程开始运行......");
        threadTest.start();
        Thread.sleep(3000);
        System.out.println("出现Interrupt的线程为:"+threadTest.getName());
        threadTest.stop = true;
        threadTest.interrupt();
        Thread.sleep(3000);
        System.out.println("主线程已经停止了......");
    }
}

运行结果为:

start()方法和run()方法的区别

每个线程都是通过某个特定 Thread 对象所对应的方法 run() 来完成其 *** 作的,方法 run() 称为线程体。通过调用 Thread 类的 start() 方法来启动⼀一个线程;

start() 方法来启动一个线程,真正实现了多线程运行。这时无需等待 run() 方法体代码执行完毕,可以直接继续执行下面的代码;这时此线程是处于就绪状态,并没有运行。然后通过此 Thread 类调用方法 run() 来完成其运行状态;

run()方法称为线程体,它包含了要执行的这个线程的内容,线程就进入了运行状态,开始运行 run 函数当中的代码。 Run 方法运行结束, 此线程终止。然后 CPU 再调度其它线程。run() 方法是在本线程里的,只是线程里的一个函数,而不是多线程的。如果直接调用 run(),其实就相当于是调用了一个普通函数而已,直接用 run() 方法必须等待 run() 方法执行完毕才能执行下面的代码,所以执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用 start() 方法而不是 run() 方法。

sleep()方法和wait()方法的区别

首先sleep()方法可以在任何地方使用,而wait()方法只能在同步方法或者同步块中运行。对于 sleep()方法,我们首先要知道该方法是属于 Thread 类中的。而 wait()方法,则是属于Object 类中的。sleep()方法导致了程序暂停执行指定的时间,让出 cpu 给其他线程(其他CPU可以执行其他任务),但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。注意在调用 sleep()方法的过程中, 线程不会释放对象锁。当调用 wait()方法的时候,当前线程会暂时退出同步资源锁,同时会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用 notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。

Runnable接口和Callable接口的区别

首先查看一下Runnable接口的源代码,反正Runnable接口中只有一个方法,Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;

其次再查看一下Callable接口中的源码,Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。

这其实是很有意义的,首先多线程的运行和单线程运行相比有很多复杂困难的问题,因为多线程运行充满了未知性,比如说某条线程是否运行,运行了多少时间,线程所期望的数据是否已经赋值完毕,Callable接口的泛型的Future/FutureTask却可以获取多线程运行的结果,可以在等待时间太长没获取到需要的数据的情况下取消该线程的任务。这是一个优势。

线程安全

什么是线程安全?当多线程运行了同一代码的时候,如果产生了不同的结果会怎么样?就好比如家里养的鸡下的蛋结果却孵出来一个老鹰,这怎么也显得不合适了,所以线程安全说白了就一句话,当多线程运行同一代码,不会产生不一样的结果。即代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

在多线程环境中,当各线程不共享数据的时候,即都是私有(private)成员,那么一定是线程安全的。因为共享数据没有,那线程之间就互不影响。但这种情况并不多见,在多数情况下需要共享数据,这时就需要进行适当的同步控制了。线程安全一般都涉及到synchronized, 就是一段代码同时只能有一个线程来 *** 作 不然中间过程可能会产生不可预制的结果。

举一个简单的例子,本来你在银行存有1000块钱,你要取1500块钱,你去银行办理两个业务,取钱和存钱,假如你的两个业务是同步进行的,这个时候是不是你的钱就取不出来了?因为你的银行了就只有1000块钱,你取不了1500块钱,但是你的钱却是可以存进去。钱取不了,是不是你去银行办理业务的这个进程就废掉了?因为没有完全运行成功嘛。

确保线程安全

通过合理的时间调度,避开共享资源的存取冲突。另外,在并行任务设计上可以通过适当的策略,保证任务与任务之间不存在共享资源,设计一个规则来保证一个客户的计算工作和数据访问只会被一个线程或一台工作机完成,而不是把一个客户的计算工作分配给多个线程去完成。
确保多线程安全的方法:

对非安全的代码进行加锁控制
使用线程安全的类
多线程并发情况下,线程共享的变量改为方法级的局部变量

线程安全在三个方面体现:

原子性:提供互斥访问,同一时刻只能有一个线程对数据进行 *** 作(atomic, synchronized);
可见性:一个线程对主内存的修改可以及时地被其他线程看到,(synchronized、 volatile);
有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱⽆无序,(happensbefore 原则)

共享数据

第一种:将共享数据封装到一个对象中,把这个共享数据所在的对象传递给不同的Runnable。

第二种:将这些Runnable对象作为某一个类的内部类,共享的数据作为外部类的成员变量,对共享数据的 *** 作分配给外部类的方法来完成,以此实现对 *** 作共享数据的互斥和通信,作为内部类的Runnable来 *** 作外部类的方法,实现对数据的 *** 作。

class shareData {
    private int x = 0;
    public synchronized void addX(){
        x++;
        System.out.println("X++:"+x);
    }
    public synchronized void subX(){
        x--;
        System.out.println("X--:"+x);
    }
}
public class ThreadsVisitData{
    public static shareData share = new shareData();
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    share.addX();
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    share.subX();
                }
            }
        }).start();
    }
}

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5692548.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存