如何创建线程?如何保证线程安全?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何创建线程?如何保证线程安全?相关的知识,希望对你有一定的参考价值。

互斥同步是最常见的一种并发正确性保障手段。同步是指在多线程并发访问共享数据时,保证共享数据在同一时刻只被一个线程使用(同一时刻,只有一个线程在操作共享数据)。而互斥是实现同步的一种手段,临界区、互斥量和信号量都是主要的互斥实现方式。因此,在这4个字里面,互斥是因,同步是果;互斥是方法,同步是目的。
随着硬件指令集的发展,出现了基于冲突检测的乐观并发策略,通俗地说,就是先进行操作,如果没有其他线程争用共享数据,那操作就成功了;如果共享数据有争用,产生了冲突,那就再采用其他的补偿措施。(最常见的补偿错误就是不断地重试,直到成功为止),这种乐观的并发策略的许多实现都不需要把线程挂起,因此这种同步操作称为非阻塞同步。
要保证线程安全,并不是一定就要进行同步,两者没有因果关系。同步只是保证共享数据争用时的正确性的手段,如果一个方法本来就不涉及共享数据,那它自然就无需任何同步操作去保证正确性,因此会有一些代码天生就是线程安全的。
参考技术A 一般情况下的话,我觉得如果说创建县城的话,像这种情况下,可以直接继续从三个方式进行下使用就可以 参考技术B 2020年6月9日 线程的创建 1.继承thread类 2.实现Runnable接口 3.实现callable接口 4.通过线程池 线程安全 线程具有三大特性 1.原子性 一组线程要么同时执行成功,要么同时执行... 参考技术C java中,线程安全的解决方法或过程:1.如果对象是immutable,则是线程安全的,例如:String,可以放心使用。2. 如果对象是线程安全的,则放心使用。 参考技术D 如果如果说想要进行创建线程的话想要保证线程安全的话可以从三个方向进行进使用

为什么会有多线程?什么是线程安全?如何保证线程安全?


本文将会回答这几个问题:

  1. 为什么会有多线程?

  2. 什么是线程安全?

  3. 怎么样保证线程安全?

为什么会有多线程

什么是线程安全

在谈什么是线程安全的问题之前,先给大家举一个线程不安全的例子,直接上代码

public class Test {
    private static int count;
    private static class Thread1 extends Thread {
        public void run() {
            for (int i = 0; i < 1000; i++) {
                count ++;
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread1  t1 = new Thread1();
        Thread1  t2 = new Thread1();
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }
}

这段代码实现的逻辑很简单,首先定义了一个int型的count变量,然后开启了两个线程,每个线程执行1000次循环,循环中对count进行加1操作。等待两个线程都执行完成后,打印count的值。那么这段代码的输出结果是多少呢?可能很多人会说是2000。但是程序运行后却发现结果大概率不是2000,而是一个比2000略小的数,比如1998这样,而且每次运行的结果可能都不相同。
那么这是为什么呢?这就是线程不安全。线程安全是指在多线程环境下,程序可以始终执行正确的行为,符合预期的逻辑。比如我们刚刚的程序,共两个线程,每个线程对count变量累加1000次,预期的逻辑是count被累加了2000次,而代码执行的结果却不是2000,所以它是线程不安全的。
为什么是不安全的呢?因为count++的指令在实际执行的过程中不是原子性的,而是要分为读、改、写三步来进行;即先从内存中读出count的值,然后执行+1操作,再将结果写回内存中,如下图所示。

线程交替执行

这就是线程在计算机中真实的执行过程,看起来好像没问题啊,别急,再看一张图
不安全的线程交替执行

看出来问题了么?上图中线程1执行了两次自加操作,而线程2执行了一次自加操作,但是count却从6变成了8,只加了2.我们看一下为什么会出现这种情况。当线程1读取count的值为6完成后,此时切换到了线程2执行,线程2同样读取到了count的值为6,而后进行改和写操作,count的值变为了7;此时线程又切回了线程1,但是线程1中count的值依然是线程2修改前的6,这就是问题所在!!!即线程2修改了count的值,但是这种修改对线程1不可见,导致了程序出现了线程不安全的问题,没有符合我们预期的逻辑。
相信大家现在已经对线程不安全已经有了一定的认识了。现在我们总结一下导致线程不安全的原因,主要有三点:
  • 原子性:一个或者多个操作在 CPU 执行的过程中被中断

  • 可见性:一个线程对共享变量的修改,另外一个线程不能立刻看到

  • 有序性:程序执行的顺序没有按照代码的先后顺序执行

前两点前面已经举例了,现在在解释一下第三点。为什么程序执行的顺序会和代码的执行顺序不一致呢?java平台包括两种编译器:静态编译器(javac)和动态编译器(jit:just in time)。静态编译器是将.java文件编译成.class文件(二进制文件),之后便可以解释执行。动态编译器是将.class文件编译成机器码,之后再由jvm运行。问题一般会出现在动态编译器上,因为动态编译器为了程序的整体性能会对指令进行重排序,虽然重排序可以提升程序的性能,但是重排序之后会导致源代码中指定的内存访问顺序与实际的执行顺序不一样,就会出现线程不安全的问题。

如何保证线程安全

下面简单谈谈针对以上的三个问题,java程序如何保证线程安全呢?
针对问题1:JDK里面提供了很多atomic类,比如AtomicInteger, AtomicLong, AtomicBoolean等等,这些类本身可以通过CAS来保证操作的原子性;另外Java也提供了各种锁机制,来保证锁内的代码块在同一时刻只能有一个线程执行,比如刚刚的例子我们就可以加锁,如下:

synchronized (Test.class){
    count ++;
}

这样,就能够保证一个线程在多count值进行读、改、写操作时,其他线程不可对count进行操作,从而保证了线程的安全性。
针对问题2:同样可以通过synchronized关键字加锁来解决。与此同时,java还提供了一种轻量级的锁,即volatile关键字,要优于synchronized的性能,同样可以保证修改对其他线程的可见性。volatile一般用于对变量的写操作不依赖于当前值的场景中,比如状态标记量等。
 针对问题3:可以通过synchronized关键字定义同步代码块或者同步方法保障有序性,另外也可以通过Lock接口保障有序性。
怎么样?现在是不是对线程安全有了更加深入的理解了呢?

以上是关于如何创建线程?如何保证线程安全?的主要内容,如果未能解决你的问题,请参考以下文章

如何创建线程?如何保证线程安全?

如何创建线程?如何保证线程安全?

如何创建线程?如何保证线程安全?

如何保证ArrayList线程安全

如何保证集合是线程安全的?

如何保证线程安全?