关于多线程的一些小知识

来源:互联网 发布:游戏原画 知乎 编辑:程序博客网 时间:2024/06/05 17:16

1.线程是操作系统能运行调度的最小单位,是进程的子集。
2.不同线程使用不同的内存空间,而所有的线程共享一片相同的内存空间。
3.创建线程的目的是为了建立程序单独执行路径,让大部分代码实现同时执行。
4.当执行线程的任务结束了,线程自动在栈内存中释放出来,当所有的线程都结束时,进程才算结束。
5.jvm启动后,必然有一个执行路径(线程)mian方法开始的,一直执行到main方法结束,这个线程在java中称之为主线程。
6.实现线程的三种方法:
- 继承Thread类
- 实现Runnable接口(为了避免java中的单继承的缺陷)
- 实现Callable接口
7.线程调用run和start的区别:
线程调用run方法是不重新开启线程的,仅是在原来的线程中进行对象调用方法;线程调用start开启线程,并让jvm调用run方法在新开启的线程中执行。

public class  extends Thread {    @Override    public void run() {        for (int i = 0; i < 10; i++) {        //Thread.currentThread().getName()获取当前线程的名字            System.out.println(Thread.currentThread().getName() + "----" + i);        }    }}public class ThreadTest {    public static void main(String[] args) {        MyThread myThread = new MyThread();        myThread.run();//只是在main这个主线程中调用了run方法,并没有开启线程        myThread.start();//开启了一个新的线程    }}

运行结果如图所示:

这里写图片描述

8.线程池:如果每个请求都要创建一个新的线程,那样开销是很大的,线程池能减少创建个销毁线程的次数,线程池就是一个能放多个线程的容器,里面的线程可以反复使用。
9.使用线程池的好处:
(1)通过重复使用已创建的线程来减低资源的开销。
(2)不再需要等到线程创建出来再去完成任务,提高了效率。

10.线程池中的runnable接口:

public class ThreadTest {    public static void main(String[] args) {        //线程池对象(2表示可以放2个线程)----但是不能超过int的上限        ExecutorService executorService = Executors.newFixedThreadPool(2);        MyRunnable myRunnable = new MyRunnable();        Thread thread = new Thread(myRunnable);        System.out.println("新建线程对象执行start");        thread.start();        System.out.println("从线程池中获取线程对象执行start");//submit方法执行后,程序的结束是依靠线程池控制线程关闭,将用完的线程有回归到了线程池中        executorService.submit(myRunnable);//关闭线程池executorService.shutdown();    }}public class MyRunnable implements Runnable {    @Override    public void run() {        for (int i = 0; i < 3; i++) {            System.out.println("aaa" + i);        }    }

运行结果如图所示:
第一次运行结果:

这里写图片描述

第二次运行结果:

这里写图片描述

第三次运行结果:

这里写图片描述

11.线程池中的Callable接口:

public class ThreadTest {    public static void main(String[] args) throws Exception {        MyCallable myCallable = new MyCallable();        //FutureTask通过接受Callable来创建,它同时实现了Future和Runnable接口        FutureTask futureTask = new FutureTask(myCallable);        Thread thread = new Thread(futureTask);        thread.start();         //获取返回值----需要注意的是:get方法会阻塞,直到任务返回结果        int i = (int) futureTask.get();        System.out.println(i);    }}public class MyCallable implements Callable {    @Override    public Object call() throws Exception {        int a = 0;        for (int i = 0; i < 3; i++) {            System.out.println("aaa" + i);        }        return a;    }}

运行结果如图所示:
第一次运行结果为:

这里写图片描述

第二次运行结果为:

这里写图片描述

12.实现Runnable接口和实现Callable接口的区别:
- Runnable是自从java1.1就有了,而Callab是1.5之后才加上去的。
- Callable规定的方法是call(),Runnable规定的方法是run()。
- Callable的任务执行后可返回值,而Runnable的任务是不能返回值(是void)。
- call方法可以抛出异常,run方法不可以。
- 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
- 加入线程池运行,Runnable使用ExecutorService的execute方法,Callable使用submit方法。
13.ThreadLocal:
刚开始培训java的时候,喜欢在controller中定义成员变量存前台发送到后台的值,目的就是后台再次用到这个值时前台就不需要在发送了,可以直接从成员变量中读取。开始觉得很对,因为所有的测试只有一台电脑。就是本机,所有的测试人员只有一个,就是本人。后来一直在思考一个问题,如果多个人同时访问controller层,结果会怎么样?显然第一个人进来改变属性的值后,第二个人访问到的就是改变后的值,之前的值不复存在,从而使第二个人操作失败。
解决方法有三种:
(1)不在controller层定义赋值的成员变量(依赖注入显然不算,依赖注入的成员变量在controller层中并不会被赋值)
(2)使controller从单列模型改为非单列模型,每次访问controller都创建一个对象
(3)第三种就是使用ThreadLocal
定义一个Integer类型的变量a,这个a在controller中就能对其进行赋值。因为ThreaLocal实现的是使变量仅用于每个独立的线程,即多个线程之间不受影响!!!从而实现线程安全。这与Synchronized实现线程安全有着本质的不同。
ThreadLocal以空间换取时间,提供了一种非常简便的多线程实现方式。因为多个线程并发访问无需进行等待,所以使用ThreadLocal 会获得更大的性能。虽然使用ThreadLocal会带来更多的内存开销,但这点开销是微不足道的。因为保存在ThreadLocal中的对象,通常都是比较小的对象。另外使用ThreadLocal不能使用原子类型,只能使用Object类型。ThreadLocal的使用比synchronized要简单得多。
synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。
Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。

private ThreadLocal<Integer> a = new ThreadLocal<Integer>();
原创粉丝点击