对线程的理解总结

说到线程,咱们必定首先想到的是线程的建立,线程的建立通常有两种方式 一种是实现 Runnable 接口,另外一种就是 继承 Thread 类 ,由于Java的单继承多实现机制,因此,优先选择 实现 Runnable 接口。java

 1 package test;
 2 
 3 class ThreadTest extends Thread{
 4 
 5 public void run(){
 6 
 7 System.out.println("实现了Thread类");
 8 
 9 }
10 
11 }
12 
13 class RunnableTest implements Runnable{
14 
15 public void run(){
16 
17 System.out.println("实现了runnable接口");
18 
19 }
20 
21 }
22 
23  
24 
25 public class ThreadStartTest {
26 
27 public static void main(String[] args) {
28 
29 //直接继承了Thread ,建立一个Thread的实例
30 
31 ThreadTest t1 = new ThreadTest();
32 
33 //t1启动
34 
35 t1.start();
36 
37 //若是是实现了接口的线程类,须要用对象的实例做为Thread类构造方法的参数
38 
39 Thread t2 = new Thread(new RunnableTest());
40 
41 t2.start();
42 
43 }
44 
45 }

这儿就有一个我好久以前一直不了解的坑。那时由于不常用线程类,因此,对线程的开启仅停留在有两种方式上面。在使用继承的方式时,经过new xxxThread()的方式调用Start()方法,但使用接口的方式时 一直也是new xxxThread()d的方式,发现调不了start()方法,就调用了run()方法。.....其实这样是不对的,对于Java来讲,经过new的方式调用内部run()方法一点问题都没有,但并不会开启新线程,那样作只会使用main线程。。正确的方式为Thread t2 = new Thread(new RunnableTest()); 而后调用start()方法。安全

总之必定要调用start()方法的。多线程

一、那线程开启了就要考虑线程安全了并发

线程安全,说究竟是数据的安全,我可不认识线程是谁,它安不安全,跟我没有半毛钱的关系。但数据不能不安全。这里就要提到内存了,由于,形成数据不安全的就是内存。app

对于一个程序来讲,就是一个进程,一个线程是其中的一部分。当系统为进程分配空间的时候,就会有公共空间(堆,公共方法区),和栈等。而形成不安全的就是这块公共的内存空间。ide

当一个线程在数据处理的过程当中有另外一个线程对数据进行了修改,就会形成数据不安全,程序混乱。这样咱们就说这是线程不安全的。高并发

1.一、怎么解决线程安全问题性能

解决线程安全问题,就要找到线程究竟是怎么不安全的根本缘由。其次安全与不安全是相对的。若是你的系统只有一个线程运行,或同一时间段不可能有两个线程同时运行。那也就不存在线程安全问题了。this

那线程不安全是怎么形成的呢?spa

缘由一:

“每条线程有本身的工做内存,线程的工做内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对该变量的全部操做都必须在工做内存中进行,而不能直接读写主内存中的变量。不一样线程之间也没法直接访问对方工做内存中的变量,线程间变量值的传递均须要经过主内存来完成。”

缘由二:线程抢夺

根本缘由:线程内的关于外部变量的语境,与真实外部语境不一致。

针对这几个缘由,咱们来提出解决的方案。

解决方案一:拈轻怕重

对于缘由一中 线程的工做内存中保存了被该线程使用到的变量的主内存副本拷贝,那就不要拷贝,咱们全部的方法的参数都使用方法的局部变量,这样,就不会产生从主内存拷贝的问题。当每个线程来执行方法的时候,系统都会为该线程分配一块属于本身的栈内存,这样,每一个执行这个方法的线程都会有属于本身的局部变量,那么操做本身的局部变量就不会产生安全问题了。

解决方法二:只读不写

对于缘由一种的对主内存的拷贝,有时候是不能不拷贝的那,咱们就要看看能不能只容许它读取,不容许修改,也就是使用 final 修饰等...,这样,你只能看看个人数据,不能修改,就不会形成安全问题了。

解决方案三:人手一份

就是把变量分给每个线程,让他们独立运行。在是实际的开发当中咱们可能会遇到变量在线程中共享的需求。这时咱们可使用 ThreadLocal 定义线程的变量,使用ThreadLocal 定义的变量只在本线程中有效,这样也不会有安全问题。

 1 @RestController
 2 @RequestMapping("/test")
 3 public class TestController {
 4  8 
 9     static class MyThread implements Runnable {
10         Test test;
11 
12         public MyThread(Test test) {
13             this.test = test;
14         }
15 
16         @Override
17         public void run() {
18             for (int i = 0; i < 3; i++) {
19                 test.dd();
20             }
21 
22         }
23 
24     }
25 
26     static class Test {
27         ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
28 
29         public void dd() {
30             try {
31                 if (threadLocal.get() == null)
32                     threadLocal.set(0);
33                 Integer tt = threadLocal.get();
34                 tt += 60;
35                 threadLocal.set(tt);
36                 System.out.println(Thread.currentThread().getName() + "输出值为:" + threadLocal.get());
37 
38                 Thread.sleep(100);
39             } catch (InterruptedException e) {
40                 e.printStackTrace();
41             }
42         }
43     }
44 
45     public static void main(String[] args) {
46         Test test = new Test();
47         for (int i = 0; i < 3; i++) {
48             MyThread myThread = new MyThread(test);
49             new Thread(myThread).start();
50 
51         }
52     }
53 }

在本例中有三个线程,变量使用 ThreadLocal 定义,经过 Test test = new Test(); 对线程传入相同的 Test 实例。这样避免使用不一样的Test 实例 产生不一样的ThreadLocal 变量对象。进而在 每一个线程中循环3次进行 ThreadLocal 累加

上例运行结果以下:

能够看到线程之间没有产生累加,但同一线程中进行了累加。

 

解决方案四:加悲观锁

对于以上方法都不能知足咱们的需求,那咱们就只能采起更加严格的方式了,那就是加锁,只要加锁,那就要产生线程的阻塞,性能就会打折扣了。悲观锁就是悲观的认为只要我不加锁,那个人数据就会被其余线程修改,因此每次操做都要加锁,直到操做完成。

下面我将上面的代码修改一下,成为加悲观锁的状况:

 1 public class TestController {
 2 
 3     static class MyThread implements Runnable{
 4         Test test;
 5         public MyThread(Test test){
 6             this.test=test;
 7         }
 8         @Override
 9         public void run() {
10             for (int i = 0; i <3 ; i++) {
11                 test.dd();
12             }
13 
14         }
15 
16     }
17     static class Test{
18         Integer threadLocal=0;
19         Lock lock=new ReentrantLock();
20         public  void dd(){
21             try {
22                 lock.lock();
23 //            Integer tt=threadLocal.get();
24                 threadLocal+=60;
25 //            threadLocal.set(tt);
26                 System.out.println(Thread.currentThread().getName()+ "输出值为:"+    threadLocal);
27 
28                 Thread.sleep(100);
29             } catch (InterruptedException e) {
30                 e.printStackTrace();
31             }finally {
32                 lock.unlock();
33             }
34         }
35     }
36 
37     public static void main(String[] args) {
38         Test test=new Test();
39         for (int i = 0; i <3 ; i++) {
40             MyThread myThread=new MyThread(test);
41             new Thread(myThread).start();
42 
43         }
44     }
45 }

在这个例子中咱们把 ThreadLocal 替换为了普通的 Integer 变量,并使用了 Lock 进行加锁。咱们一样开启三个线程,并在线程中进行3次循环。并执行累加,没有ThreadLocal 全部的线程公用一个变量,结果以下:

能够看到线程执行的顺序不必定,但输出的结果,没有出现错误。

 解决方案五:加乐观锁

乐观锁与悲观锁相对,乐观锁认为,大几率没有线程会修改个人数据,若是修改了那就只能从新执行操做。若是在高并发状况下使用乐观锁,可能会更加浪费系统资源。那具体怎么操做呢?

  • synchronized是悲观锁,这种线程一旦获得锁,其余须要锁的线程就挂起的状况就是悲观锁。
  • CAS操做的就是乐观锁,每次不加锁而是假设没有冲突而去完成某项操做,若是由于冲突失败就重试,直到成功为止。

参考:Java:CAS(乐观锁)

volatile 关键字

咱们知道volatile关键字的做用是保证变量在多线程之间的可见性,它是java.util.concurrent包的核心,没有volatile就没有这么多的并发类给咱们使用。

相关文章
相关标签/搜索