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则用于线程间的数据隔离。
实例:
说明:
图一中使用了ThreadLocal修饰成员变量,然后我模拟了两个不同的ip进行访问这个接口,第一个人赋值是1,第二个人赋值是3,但是第二个人赋值修改成员变量并不影响第一个人对成员变量赋的值,这就是ThreadLocal的作用;图二中,没有用ThreadLocal修饰成员变量,所以输出的都是3,因为第二个人修改成员变量之后影响了第一个人对成员变量赋的值。