对线程的理解总结

说到线程,我们一定首先想到的是线程的创建,线程的创建一般有两种方式 一种是实现 Runnable 接口,另一种就是 继承 Thread 类 ,因为Java的单继承多实现机制,所以,优先选择 实现 Runnable 接口。

 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()方法的。

1、那线程开启了就要考虑线程安全了

线程安全,说到底是数据的安全,我可不认识线程是谁,它安不安全,跟我没有半毛钱的关系。但数据不能不安全。这里就要提到内存了,因为,造成数据不安全的就是内存。

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

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

1.1、怎么解决线程安全问题

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

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

原因一:

“每条线程有自己的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对该变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。”

原因二:线程抢夺

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

针对这几个原因,我们来提出解决的方案。

解决方案一:避重就轻

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

解决方法二:只读不写

对于原因一种的对主内存的拷贝,有时候是不能不拷贝的那,我们就要看看能不能只允许它读取,不允许修改,也就是使用 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就没有这么多的并发类给我们使用。

 

转载于:https://www.cnblogs.com/hxz-nl/p/11099040.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值