zoukankan      html  css  js  c++  java
  • 对线程的理解总结

    说到线程,我们一定首先想到的是线程的创建,线程的创建一般有两种方式 一种是实现 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就没有这么多的并发类给我们使用。

  • 相关阅读:
    ASP.NET验证控件的使用 拓荒者
    读书笔记:MFC单文档应用程序结构分析 拓荒者
    MFC单文档(SDI)全屏程序的实现 拓荒者
    jQuery的animate函数
    设备尺寸杂谈:响应性Web设计中的尺寸问题
    Yeoman学习与实践笔记
    IE对文档的解析模式及兼容性问题
    推荐给开发和设计人员的iPad应用
    几个移动应用统计平台
    颜色、网页颜色与网页安全色
  • 原文地址:https://www.cnblogs.com/hxz-nl/p/11099040.html
Copyright © 2011-2022 走看看