Java多线程安全(synchronized和Lock)及线程池

线程安全

定义:当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替运行,并且在主调试代码中不需要任何额外的同步或者协同,这个类都能表现出正确的行为,则称这个类时线程安全的。线程安全类中封装了必要的同步机制,因此客户端无须进一步采取同步措施。

线程安全产生的原因:正确性取决于多个线程的交替执行时序,产生了竞态条件。

原子类: 应尽量使用原子类,这样会让你分析线程安全时更加方便,但需要注意的是用线程安全类构建的类并不能保证线程安全。例如,一个AtomicInteger get() 和 AtomicInteger set() 是线程安全的,在一个类的一个方法 f()中同时用到了这两个方法,此时的f()就是线程不安全的,因为你不能保证这个复合操作中的get 和 set同时更新。 参考文献

出现线程安全问题的原因:在多个线程并发环境下,多个线程共同访问同一共享内存资源时,其中一个线程对资源进行写操作的中途(写⼊入已经开始,但还没 结束),其他线程对这个写了一半的资源进⾏了读操作,或者对这个写了一半的资源进⾏了写操作,导致此资源出现数据错误。

如何避免线程安全问题:保证共享资源在同一时间只能由一个线程进行操作(原子性,有序性)。
将线程操作的结果及时刷新,保证其他线程可以立即获取到修改后的最新数据(可见性)。

那么线程安全的定义是什么?
我认为如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样 的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题,Java中提供了同步机制 (synchronized或Lock)来解决。

怎样解决代码被多个线程同步执行?
方法一:使用synchronized同步代码块自动上锁机制让线程有序的使用资源,使用同步代码块。
Synchronized主要是通过对象锁的方式来实现线程安全(这边的锁是指虚拟机实现的锁,和Java Lock类库不是一个概念),修饰静态方法获取的是Class对象的锁,修饰方法是获取当前对象this的锁,修饰括号内容是获取括号内对象的锁。

    @Override
    public void run() {
        while(true){
            synchronized (obj){
                if(ticket>0){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
            System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                    ticket--;
                }
            }
        }
    }

注意:
1.通过代码块中的锁对象,可以使用任意的对象
2.但是必须保证多个线程使用的锁对象是同一个
3.锁对象作用:
把同步代码块锁住,只让一个线程在同步代码块中执行

方法二 :使用同步方法
使用步骤:
1.把访问了共享数据的代码抽取出来,放到一个方法中
2.在方法上添加synchronized修饰符
这样就可以不用主动的去看那个代码块是需要加同步代码块啦~

  public  synchronized  void run() {
        System.out.println(this);
        while (true)
        {
            if(ticket>0)
            {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println(Thread.currentThread().getName()+"--->正在卖第"+ticket--+"张票");

            }
        }
    }

问题来了锁对象在那?锁对象就是这个类本身也就是this(默认是)。
注意:静态的同步方法锁对象不能是this,this是创建对象之后产生的,静态方法优先于对象,静态方法的锁对象是本类的class属性–>class文件对象(反射)

方法三 :使用Lock锁

Lock 也是 java.util.concurrent 包下的一个接口,定义了一系列的锁操作方法。Lock 接口主要有 ReentrantLock,ReentrantReadWriteLock.ReadLock,ReentrantReadWriteLock.WriteLock 实现类。与 Synchronized 不同是 Lock 提供了获取锁和释放锁等相关接口,使得使用上更加灵活,同时也可以做更加复杂的操作

package Thread.Threadsafe.lock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class threadsafe  implements  Runnable{
      private int ticket=100;
      Lock lo=new ReentrantLock();
    @Override
    public void run() {
        while (true)
        {
            lo.lock();
            if(ticket>0)
            {
                try {
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName()+"--->正在卖第"+ticket--+"张票");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    lo.unlock();
                }

            }
        }
    }
}

注意Lock需要自己手动的去上锁和关锁

synchronized和Lock的区别?
个人认为各自都有利弊,Lock锁在使用方面比synchronized更加明细但比较麻烦需要自己手动操作,synchronized比较简洁不需要程序员自己手动的去上锁解锁只需要加上synchronized就好(个人比较喜欢用synchronized贴在方法上),两者在操作方面都差不多都是先让一个进程拿锁进入代码块执行然后释放锁在性能和效率方面都差不多。
线程池
我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间。
那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?
在Java中可以通过线程池来达到这样的效果
当我们一次又一次的新建一个线程
线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作, 无需反复创建线程而消耗过多资源
一张图了解线程池创建线程和回收线程机制:
在这里插入图片描述

Runnable实现类:

package Thread.threadpool;

public class runnableimpl implements  Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"创建了一个新的线程");
    }
}

线程池的简单创建:

public class threadpool {
    public static void main(String[] args) {
        ExecutorService es= Executors.newFixedThreadPool(2);
         es.submit(new runnableimpl());
        es.submit(new runnableimpl());
        es.submit(new runnableimpl());
        es.shutdown();//销毁线程池
    }
}

线程池:JDK1.5之后提供的
java.util.concurrent.Executors:线程池的工厂类,用来生成线程池
Executors类中的静态方法:
static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池
参数:
int nThreads:创建线程池中包含的线程数量
返回值:
ExecutorService接口,返回的是ExecutorService接口的实现类对象,我们可以使用ExecutorService接口接收(面向接口编程)
java.util.concurrent.ExecutorService:线程池接口
用来从线程池中获取线程,调用start方法,执行线程任务
submit(Runnable task) 提交一个 Runnable 任务用于执行
关闭/销毁线程池的方法
void shutdown()

运行结果:
在这里插入图片描述
可以看到线程在执行完pool-1-thread-1后马上回收给新的线程使用,这样就可以大大减少线程资源的浪费和提高程序执行的效率。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值