- 外文名
- thread
- 作 用
- 保证各线程正常且正确的执行
- 中文名
- 线程安全
-
线程安全
2020-01-12 23:49:03线程安全先检查再执行:if(condition(a){handle(a);}
1 线程安全性
定义:当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的
- 原子性:提供了互斥访问,同一时刻只能有一个线程来对它进行操作
- 可见性:一个线程对主内存的修改可以及时的被其他线程观察到
- 有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序
2 什么情况下会出现线程安全问题,怎么避免?
- 运行结果错误:a++多线程下出现消失的请求现象
- 活跃性问题:死锁、活锁、饥饿
- 对象发布和初始化的时候的安全问题
3 运行结果错误:a++多线程下出现消失的请求现象
/** * 第一种:运行结果出错。 演示计数不准确(减少),找出具体出错的位置。 */ public class MultiThreadsError implements Runnable { static MultiThreadsError instance = new MultiThreadsError(); int index = 0; //真正所加的数量 static AtomicInteger realIndex = new AtomicInteger(); //错误相加的数量 static AtomicInteger wrongCount = new AtomicInteger(); final boolean[] marked = new boolean[10000000]; public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(instance); Thread thread2 = new Thread(instance); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println("表面上结果是" + instance.index); System.out.println("真正运行的次数" + realIndex.get()); System.out.println("错误次数" + wrongCount.get()); } @Override public void run() { for (int i = 0; i < 10000; i++) { index++; realIndex.incrementAndGet(); synchronized (instance) { if (marked[index]) { System.out.println("发生错误" + index); wrongCount.incrementAndGet(); } marked[index] = true; } } } }
/** * 第一种:运行结果出错。 演示计数不准确(减少),找出具体出错的位置。 */ public class MultiThreadsError implements Runnable { static MultiThreadsError instance = new MultiThreadsError(); int index = 0; static AtomicInteger realIndex = new AtomicInteger(); static AtomicInteger wrongCount = new AtomicInteger(); static volatile CyclicBarrier cyclicBarrier1 = new CyclicBarrier(2); static volatile CyclicBarrier cyclicBarrier2 = new CyclicBarrier(2); final boolean[] marked = new boolean[10000000]; public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(instance); Thread thread2 = new Thread(instance); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println("表面上结果是" + instance.index); System.out.println("真正运行的次数" + realIndex.get()); System.out.println("错误次数" + wrongCount.get()); } @Override public void run() { marked[0] = true; for (int i = 0; i < 10000; i++) { try { cyclicBarrier2.reset(); cyclicBarrier1.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } index++; try { cyclicBarrier1.reset(); cyclicBarrier2.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } realIndex.incrementAndGet(); synchronized (instance) { if (marked[index] && marked[index - 1]) { System.out.println("发生错误" + index); wrongCount.incrementAndGet(); } marked[index] = true; } } } }
4 活跃性问题:死锁
/** * 线程安全问题,演示死锁。 */ public class MultiThreadError implements Runnable { static Object lockA = new Object(); static Object lockB = new Object(); public static void main(String[] args) throws InterruptedException { MultiThreadError run = new MultiThreadError(); new Thread(run,"thread-1").start(); //Thread.sleep(1000); new Thread(run,"thread-2").start(); } @Override public void run() { System.out.println(Thread.currentThread().getName()); if ("thread-1".equals(Thread.currentThread().getName())) { synchronized (lockA) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lockB) { System.out.println("1"); } } } if ("thread-2".equals(Thread.currentThread().getName())) { synchronized (lockB) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lockA) { System.out.println("0"); } } } } }
5 对象发布和初始化的时候的安全问题
5.1 发布和逸出问题
什么是发布
- 发布对象:使一个对象能够被当前范围之外的代码所使用
什么是逸出
1. 方法返回一个private对象(private的本意是不让外部访问)
2. 还未完成初始化(构造函数没完全执行完毕)就把对象提供给外界
比如
- 在构造函数中未初始化完毕就this赋值
- 隐式逸出——注册监听事件
- 构造函数中运行线程
对象逸出:一种错误的发布。当一个对象还没有构造完成时,就使它被其他线程所见
5.2 发布逸出
/** * 发布逸出 */ public class MultiThreadsError { private Map<String, String> states; public MultiThreadsError() { states = new HashMap<>(); states.put("1", "周一"); states.put("2", "周二"); states.put("3", "周三"); states.put("4", "周四"); } //逸出 //方法返回一个private对象(private的本意是不让外部访问) public Map<String, String> getStates() { return states; } //通过返回拷贝修复bug public Map<String, String> getStatesImproved() { return new HashMap<>(states); } public static void main(String[] args) { MultiThreadsError multiThreadsError3 = new MultiThreadsError(); //这是一个很严重的问题 //Map<String, String> states = multiThreadsError3.getStates(); //System.out.println(states.get("1")); //states.remove("1"); //System.out.println(states.get("1")); //修复 System.out.println(multiThreadsError3.getStatesImproved().get("1")); multiThreadsError3.getStatesImproved().remove("1"); System.out.println(multiThreadsError3.getStatesImproved().get("1")); } }
5.3 在构造函数中未初始化完毕就this赋值
/** * 初始化未完毕,就this赋值 */ public class MultiThreadsError { static Point point; public static void main(String[] args) throws InterruptedException { Runnable pointMaker = ()->{ try { new Point(1, 1); } catch (InterruptedException e) { e.printStackTrace(); } }; new Thread(pointMaker).start(); Thread.sleep(10); //Thread.sleep(105); if (point != null) { System.out.println(point); } } } //一个点 class Point { private final int x; private final int y; public Point(int x, int y) throws InterruptedException { this.x = x; MultiThreadsError.point = this; Thread.sleep(100); this.y = y; } @Override public String toString() { return x + "," + y; } }
/** * 非线程安全 */ @Slf4j public class Escape { private int thisCanBeEscape = 0; public Escape () { new InnerClass(); } private class InnerClass { public InnerClass() { log.info("{}", Escape.this.thisCanBeEscape); } } public static void main(String[] args) { new Escape(); } }
5.4 隐式逸出——注册监听事件
/** * 观察者模式 */ public class MultiThreadsError { int count; public MultiThreadsError(MySource source) { source.registerListener(e -> System.out.println("\n我得到的数字是" + count)); for (int i = 0; i < 10000; i++) { System.out.print(i); } count = 100; } public static void main(String[] args) { MySource mySource = new MySource(); new Thread(() -> { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } mySource.eventCome(new Event() { }); }).start(); MultiThreadsError multiThreadsError = new MultiThreadsError(mySource); } static class MySource { private EventListener listener; void registerListener(EventListener eventListener) { this.listener = eventListener; } void eventCome(Event e) { if (listener != null) { listener.onEvent(e); } else { System.out.println("还未初始化完毕"); } } } interface EventListener { void onEvent(Event e); } interface Event { } }
用工厂模式修复问题
/** * 用工厂模式修复刚才的初始化问题 */ public class MultiThreadsError { int count; private EventListener listener; private MultiThreadsError(MySource source) { listener = e -> System.out.println("\n我得到的数字是" + count); for (int i = 0; i < 10000; i++) { System.out.print(i); } count = 100; } public static MultiThreadsError getInstance(MySource source) { MultiThreadsError safeListener = new MultiThreadsError(source); source.registerListener(safeListener.listener); return safeListener; } public static void main(String[] args) { MySource mySource = new MySource(); new Thread(() -> { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } mySource.eventCome(new MultiThreadsError5.Event() { }); }).start(); MultiThreadsError multiThreadsError = new MultiThreadsError(mySource); } static class MySource { private EventListener listener; void registerListener(EventListener eventListener) { this.listener = eventListener; } void eventCome(MultiThreadsError5.Event e) { if (listener != null) { listener.onEvent(e); } else { System.out.println("还未初始化完毕"); } } } interface EventListener { void onEvent(MultiThreadsError5.Event e); } interface Event { } }
5.5 构造函数中运行线程
/** * 构造函数中新建线程 */ public class MultiThreadsError { private Map<String, String> states; public MultiThreadsError() { new Thread(() -> { states = new HashMap<>(); states.put("1", "周一"); states.put("2", "周二"); states.put("3", "周三"); states.put("4", "周四"); }).start(); } public Map<String, String> getStates() { return states; } public static void main(String[] args) throws InterruptedException { MultiThreadsError multiThreadsError = new MultiThreadsError(); //Thread.sleep(1000); System.out.println(multiThreadsError.getStates().get("1")); } }
5.6 安全发布对象的四种方法
- 在静态初始化函数中初始化一个对象引用
- 将对象的引用保存到volatile类型域或者AtomicReference对象中
- 将对象的引用保存到某个正确构造对象的final类型域中
- 将对象的引用保存到一个由锁保护的域中
6 不可变对象
不可变对象需要满足的条件
- 对象创建以后其状态就不能修改
- 对象所有域都是final类型
- 对象是正确创建的(在对象创建期间,this引用没有逸出)
Collections.unmodifiableXXX: Collection、List、Set、Map...
Guava: ImmutableXXX: Collection、List、set、Map…
//线程安全 @Slf4j public class ImmutableExample { private static Map<Integer, Integer> map = Maps.newHashMap(); static { map.put(1, 2); map.put(3, 4); map.put(5, 6); //初始化完成后设置为不可变 map = Collections.unmodifiableMap(map); } public static void main(String[] args) { map.put(1, 3);//UnsupportedOperationException log.info("{}", map.get(1)); } }
//线程安全 public class ImmutableExample { private final static ImmutableList<Integer> list = ImmutableList.of(1, 2, 3); private final static ImmutableSet set = ImmutableSet.copyOf(list); private final static ImmutableMap<Integer, Integer> map = ImmutableMap.of(1, 2, 3, 4); private final static ImmutableMap<Integer, Integer> map2 = ImmutableMap.<Integer, Integer>builder().put(1, 2).put(3, 4).put(5, 6).build(); public static void main(String[] args) { System.out.println(map2.get(3)); } }
7 线程封闭
- Ad-hoc 线程封闭:程序控制实现,最糟糕,忽略
- 堆栈封闭:局部变量,无并发问题(方法中的局部变量都会拷贝一份到线程的栈中)
- ThreadLocal线程封闭:特别好的封闭方法
public class RequestHolder { private final static ThreadLocal<Long> requestHolder = new ThreadLocal<>(); public static void add(Long id) { requestHolder.set(id); } public static Long getId() { return requestHolder.get(); } public static void remove() { requestHolder.remove(); } }
@Slf4j public class ThreadLocalHttpFilter implements Filter { @Override public void init(FilterConfig filterConfig) {} @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; log.info("do filter, {}, {}", Thread.currentThread().getId(), request.getServletPath()); RequestHolder.add(Thread.currentThread().getId()); filterChain.doFilter(servletRequest, servletResponse); } @Override public void destroy() {} }
@Slf4j public class HttpInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { log.info("preHandle"); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { RequestHolder.remove(); log.info("afterCompletion"); } }
/** * https://www.jianshu.com/p/b3b74cd8af34 */ @Configuration public class FilterConfig implements WebMvcConfigurer { @Bean public FilterRegistrationBean httpFilter() { FilterRegistrationBean registrationBean = new FilterRegistrationBean(); registrationBean.setFilter(new ThreadLocalHttpFilter()); registrationBean.addUrlPatterns("/threadLocal/*"); return registrationBean; } /** * 注册拦截器 */ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new HttpInterceptor()).addPathPatterns("/**"); } }
@Controller @RequestMapping("/threadLocal") public class ThreadLocalController { /** * http://localhost:8080/threadLocal/test */ @RequestMapping("/test") @ResponseBody public Long test() { return RequestHolder.getId(); } }
8 线程不安全类与写法
8.1 StringBuilder和StringBuffer
/** * 非线程安全 */ @Slf4j public class SecurityVerification { // 请求总数 public static int clientTotal = 5000; // 同时并发执行的线程数 public static int threadTotal = 200; //非线程安全 //public static StringBuilder stringBuilder = new StringBuilder(); //线程安全 //public static StringBuffer stringBuffer = new StringBuffer(); public static void main(String[] args) throws Exception { ExecutorService threadPool = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for (int i = 0; i < clientTotal ; i++) { threadPool.execute(() -> { try { semaphore.acquire(); update(); semaphore.release(); } catch (Exception e) { log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); threadPool.shutdown(); //log.info("size:{}", stringBuilder.length()); //log.info("size:{}", stringBuffer.length()); } private static void update() { //stringBuilder.append("1"); //stringBuffer.append("1"); } }
8.2 SimpleDateFormat和JodaTime
//非线程安全 @Slf4j public class DateFormatExample { private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd"); // 请求总数 public static int clientTotal = 5000; // 同时并发执行的线程数 public static int threadTotal = 200; public static void main(String[] args) throws Exception { ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for (int i = 0; i < clientTotal ; i++) { executorService.execute(() -> { try { semaphore.acquire(); update(); semaphore.release(); } catch (Exception e) { log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); } private /*synchronized*/ static void update() { try { //SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd"); simpleDateFormat.parse("20180208"); } catch (Exception e) { log.error("parse exception", e); } } }
<!-- https://mvnrepository.com/artifact/joda-time/joda-time --> <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>2.10.5</version> </dependency>
//线程安全 @Slf4j public class DateFormatExample { // 请求总数 public static int clientTotal = 5000; // 同时并发执行的线程数 public static int threadTotal = 200; private static DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyyMMdd"); public static void main(String[] args) throws Exception { ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for (int i = 0; i < clientTotal; i++) { final int count = i; executorService.execute(() -> { try { semaphore.acquire(); update(count); semaphore.release(); } catch (Exception e) { log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); } private static void update(int i) { log.info("{}, {}", i, DateTime.parse("20180208", dateTimeFormatter).toDate()); } }
8.3 ArrayList,HashSet,HashMap等Collections
/** * 非线程安全 */ @Slf4j public class NotSecurity { // 请求总数 public static int clientTotal = 5000; // 同时并发执行的线程数 public static int threadTotal = 200; //private static List<Integer> list = new ArrayList<>(); //private static Map<Integer, Integer> map = new HashMap<>(); //private static Set<Integer> set = new HashSet<>(); public static void main(String[] args) throws Exception { ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for (int i = 0; i < clientTotal; i++) { final int count = i; executorService.execute(() -> { try { semaphore.acquire(); update(count); semaphore.release(); } catch (Exception e) { log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); //log.info("size:{}", list.size()); //log.info("size:{}", map.size()); //log.info("size:{}", set.size()); } private static void update(int i) { //list.add(i); //map.put(i, i); //set.add(i); } }
9 安全共享对象策略
- 线程限制:一个被线程限制的对象,由线程独占,并且只能被占有它的线程修改
- 共享只读:一个共享只读的对象,在没有额外同步的情况下,可以被多个线程并发访问,但是任何线程都不能修改它
- 线程安全对象:一个线程安全的对象或者容器,在内部通过同步机制来保证线程安全,所以其他线程无需额外的同步就可以通过公共接口随意访问它
- 被守护对象:被守护对象只能通过获取特定的锁来访问
10 多线程并发最佳实践
- 使用本地变量
- 使用不可变类
- 最小化锁的作用域范围:S=1/(1-a+a/n)
- 使用线程池的Executor,而不是直接new Thread执行
- 宁可使用同步也不要使用线程的wait和notify
- 使用BlockingQueue实现生产-消费模式
- 使用并发集合而不是加了锁的同步集合
- 使用Semaphore创建有界的访问
- 宁可使用同步代码块,也不使用同步的方法
- 避免使用静态变量
11 各种需要考虑线程安全的情况
- 访问共享的变量或资源,会有并发风险,比如对象的属性、静态变量、共亨缓存、数据库等
- 所有依赖时序的操作,即使每一步操作都是线程安全的,还是存在并发问题:read-modify-write、check-then-act
- 不同的数据之间存在捆绑关系的时候
- 我们使用其他类的时候,如果对方没有声明自己是线程安全的
12 并发的优势与风险
-
【2】多线程线程安全
2019-11-22 20:58:01知识点1:什么是线程安全? 1、为什么有线程安全问题? 知识点2:线程安全解决办法 1、内置的锁 2、同步代码块synchronized 3、同步方法 (1)什么是同步方法? (2)同步方法使用的是什么锁? (3)静态...目录
知识点1:什么是线程安全?
1、为什么有线程安全问题?
当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作是不会发生数据冲突问题。
案例:需求现在有100张火车票,有两个窗口同时抢火车票,请使用多线程模拟抢票效果。
代码:
public class ThreadTrain implements Runnable { private int trainCount = 100; @Override public void run() { while (trainCount > 0) { try { Thread.sleep(50); } catch (Exception e) { } sale(); } } public void sale() { if (trainCount > 0) { System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainCount + 1) + "张票"); trainCount--; } } public static void main(String[] args) { ThreadTrain threadTrain = new ThreadTrain(); Thread t1 = new Thread(threadTrain, "①号"); Thread t2 = new Thread(threadTrain, "②号"); t1.start(); t2.start(); } }
运行结果:
一号窗口和二号窗口同时出售火车第九九张,部分火车票会重复出售。
结论发现,多个线程共享同一个全局成员变量时,做写的操作可能会发生数据冲突问题。
知识点2:线程安全解决办法
问:如何解决多线程之间线程安全问题
答:使用多线程之间同步synchronized或使用锁(lock)。
问:为什么使用线程同步或使用锁能解决线程安全问题呢?
答:将可能会发生数据冲突问题(线程不安全问题),只能让当前一个线程进行执行。代码执行完成后释放锁,让后才能让其他线程进行执行。这样的话就可以解决线程不安全问题。
问:什么是多线程之间同步
答:当多个线程共享同一个资源,不会受到其他线程的干扰。
问:什么是多线程同步
答:当多个线程共享同一个资源,不会受到其他线程的干扰。
1、内置的锁
Java提供了一种内置的锁机制来支持原子性
每一个Java对象都可以用作一个实现同步的锁,称为内置锁,线程进入同步代码块之前自动获取到锁,代码块执行完成正常退出或代码块中抛出异常退出时会释放掉锁
内置锁为互斥锁,即线程A获取到锁后,线程B阻塞直到线程A释放锁,线程B才能获取到同一个锁
内置锁使用synchronized关键字实现,synchronized关键字有两种用法:
1.修饰需要进行同步的方法(所有访问状态变量的方法都必须进行同步),此时充当锁的对象为调用同步方法的对象
2.同步代码块和直接使用synchronized修饰需要同步的方法是一样的,但是锁的粒度可以更细,并且充当锁的对象不一定是this,也可以是其它对象,所以使用起来更加灵活
2、同步代码块synchronized
就是将可能会发生线程安全问题的代码,给包括起来。 synchronized(同一个数据){ 可能会发生线程冲突问题 } 就是同步代码块 synchronized(对象)//这个对象可以为任意对象 { 需要被同步的代码 }
对象如同锁,持有锁的线程可以在同步中执行
没持有锁的线程即使获取CPU的执行权,也进不去
同步的前提:
1,必须要有两个或者两个以上的线程
2,必须是多个线程使用同一个锁 ,必须保证同步中只能有一个线程在运行
好处:解决了多线程的安全问题
弊端:多个线程需要判断锁,较为消耗资源、抢锁的资源。
代码样例:
public void sale() { synchronized (this) { if (trainCount > 0) { System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainCount + 1) + "张票"); trainCount--; } } }
3、同步方法
(1)什么是同步方法?
答:在方法上修饰synchronized 称为同步方法
代码样例
public synchronized void sale() { if (trainCount > 0) { System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainCount + 1) + "张票"); trainCount--; } }
(2)同步方法使用的是什么锁?
答:同步函数使用this锁。
证明方式: 一个线程使用同步代码块(this明锁),另一个线程使用同步函数。如果两个线程抢票不能实现同步,那么会出现数据错误。
代码:
class Thread009 implements Runnable { private int trainCount = 100; private Object oj = new Object(); public boolean flag = true; public void run() { if (flag) { while (trainCount > 0) { synchronized (this) { try { Thread.sleep(10); } catch (Exception e) { // TODO: handle exception } if (trainCount > 0) { System.out.println(Thread.currentThread().getName() + "," + "出售第" + (100 - trainCount + 1) + "票"); trainCount--; } } } } else { while (trainCount > 0) { sale(); } } } public synchronized void sale() { try { Thread.sleep(10); } catch (Exception e) { // TODO: handle exception } if (trainCount > 0) { System.out.println(Thread.currentThread().getName() + "," + "出售第" + (100 - trainCount + 1) + "票"); trainCount--; } } } public class Test009 { public static void main(String[] args) throws InterruptedException { Thread009 threadTrain = new Thread009(); Thread t1 = new Thread(threadTrain, "窗口1"); Thread t2 = new Thread(threadTrain, "窗口2"); t1.start(); Thread.sleep(40); threadTrain.flag = false; t2.start(); } }
(3)静态同步函数
答:什么是静态同步函数?
方法上加上static关键字,使用synchronized 关键字修饰 或者使用类.class文件。
静态的同步函数使用的锁是 该函数所属字节码文件对象
可以用 getClass方法获取,也可以用当前 类名.class 表示。
代码样例:
public static void sale() { synchronized (ThreadTrain3.class) { if (trainCount > 0) { System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainCount + 1) + "张票"); trainCount--; } } }
总结:
synchronized 修饰方法使用锁是当前this锁。
synchronized 修饰静态方法使用锁是当前类的字节码文件
知识点3:多线程死锁
1、什么是多线程死锁?
答:同步中嵌套同步,导致锁无法释放
代码:
class Thread009 implements Runnable { private int trainCount = 100; private Object oj = new Object(); public boolean flag = true; public void run() { if (flag) { while (trainCount > 0) { synchronized (oj) { try { Thread.sleep(10); } catch (Exception e) { // TODO: handle exception } sale(); } } } else { while (trainCount > 0) { sale(); } } } public synchronized void sale() { synchronized (oj) { try { Thread.sleep(10); } catch (Exception e) { } if (trainCount > 0) { System.out.println(Thread.currentThread().getName() + "," + "出售第" + (100 - trainCount + 1) + "票"); trainCount--; } } } } public class Test009 { public static void main(String[] args) throws InterruptedException { Thread009 threadTrain = new Thread009(); Thread t1 = new Thread(threadTrain, "窗口1"); Thread t2 = new Thread(threadTrain, "窗口2"); t1.start(); Thread.sleep(40); threadTrain.flag = false; t2.start(); } }}
知识点4:Threadlocal
1、什么是Threadlocal
ThreadLocal提高一个线程的局部变量,访问某个线程拥有自己局部变量。
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
ThreadLocal的接口方法
ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:
• void set(Object value)设置当前线程的线程局部变量的值。
• public Object get()该方法返回当前线程所对应的线程局部变量。
• public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
• protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
案例:创建三个线程,每个线程生成自己独立序列号。
代码:
class Res { // 生成序列号共享变量 public static Integer count = 0; public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() { protected Integer initialValue() { return 0; }; }; public Integer getNum() { int count = threadLocal.get() + 1; threadLocal.set(count); return count; } } public class ThreadLocaDemo2 extends Thread { private Res res; public ThreadLocaDemo2(Res res) { this.res = res; } @Override public void run() { for (int i = 0; i < 3; i++) { System.out.println(Thread.currentThread().getName() + "---" + "i---" + i + "--num:" + res.getNum()); } } public static void main(String[] args) { Res res = new Res(); ThreadLocaDemo2 threadLocaDemo1 = new ThreadLocaDemo2(res); ThreadLocaDemo2 threadLocaDemo2 = new ThreadLocaDemo2(res); ThreadLocaDemo2 threadLocaDemo3 = new ThreadLocaDemo2(res); threadLocaDemo1.start(); threadLocaDemo2.start(); threadLocaDemo3.start(); } }
2、ThreadLoca实现原理
ThreadLoca通过map集合
Map.put(“当前线程”,值);
知识点5:多线程有三大特性
原子性、可见性、有序性
1、什么是原子性
即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
一个很经典的例子就是银行账户转账问题:
比如从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。这2个操作必须要具备原子性才能保证不出现一些意外的问题。我们操作数据也是如此,比如i = i+1;其中就包括,读取i的值,计算i,写入i。这行代码在Java中是不具备原子性的,则多线程运行肯定会出问题,所以也需要我们使用同步和lock这些东西来确保这个特性了。
原子性其实就是保证数据一致、线程安全一部分
2、什么是可见性
当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到这就是可见性问题。
3、什么是有序性
程序执行的顺序按照代码的先后顺序执行。
一般来说处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。如下:
int a = 10; //语句1
int r = 2; //语句2
a = a + 3; //语句3
r = a*a; //语句4
则因为重排序,他还可能执行顺序为 2-1-3-4,1-3-2-4
但绝不可能 2-1-4-3,因为这打破了依赖关系。
显然重排序对单线程运行是不会有任何问题,而多线程就不一定了,所以我们在多线程编程时就得考虑这个问题了。 -
String,StringBuffer与StringBuilder的区别|线程安全与线程不安全
2018-11-16 12:38:45最新的详细测试 https://www.cnblogs.com/shangxiaofei/p/10465031.html ... String 字符串常量 StringBuffer 字符串变量(线程安全) StringBuilder 字符串变量(非线程安全) 简要的说, String 类型...最新的详细测试
https://www.cnblogs.com/shangxiaofei/p/10465031.html
转载自https://www.cnblogs.com/xingzc/p/6277581.html侵权删
String 字符串常量
StringBuffer 字符串变量(线程安全)
StringBuilder 字符串变量(非线程安全)
简要的说, String 类型和 StringBuffer 类型的主要性能区别其实在于 String 是不可变的对象, 因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,那速度是一定会相当慢的。
而如果是使用 StringBuffer 类则结果就不一样了,每次结果都会对 StringBuffer 对象本身进行操作,而不是生成新的对象,再改变对象引用。所以在一般情况下我们推荐使用 StringBuffer ,特别是字符串对象经常改变的情况下。而在某些特别情况下, String 对象的字符串拼接其实是被 JVM 解释成了 StringBuffer 对象的拼接,所以这些时候 String 对象的速度并不会比 StringBuffer 对象慢,而特别是以下的字符串对象生成中, String 效率是远要比 StringBuffer 快的:
String S1 = “This is only a” + “ simple” + “ test”;
StringBuffer Sb = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”);
你会很惊讶的发现,生成 String S1 对象的速度简直太快了,而这个时候 StringBuffer 居然速度上根本一点都不占优势。其实这是 JVM 的一个把戏,在 JVM 眼里,这个
String S1 = “This is only a” + “ simple” + “test”; 其实就是:
String S1 = “This is only a simple test”; 所以当然不需要太多的时间了。但大家这里要注意的是,如果你的字符串是来自另外的 String 对象的话,速度就没那么快了,譬如:
String S2 = “This is only a”;
String S3 = “ simple”;
String S4 = “ test”;
String S1 = S2 +S3 + S4;
这时候 JVM 会规规矩矩的按照原来的方式去做
在大部分情况下 StringBuffer > String
StringBuffer
Java.lang.StringBuffer线程安全的可变字符序列。一个类似于 String 的字符串缓冲区,但不能修改。虽然在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。
可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。
StringBuffer 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的点添加字符。
例如,如果 z 引用一个当前内容是“start”的字符串缓冲区对象,则此方法调用 z.append("le") 会使字符串缓冲区包含“startle”,而 z.insert(4, "le") 将更改字符串缓冲区,使之包含“starlet”。
在大部分情况下 StringBuilder > StringBufferjava.lang.StringBuilde
java.lang.StringBuilder一个可变的字符序列是5.0新增的。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。两者的方法基本相同。关于线程和线程不安全:
概述
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。
若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
安全性
类要成为线程安全的,首先必须在单线程环境中有正确的行为。如果一个类实现正确(这是说它符合规格说明的另一种方式),那么没有一种对这个类的对象的操作序列(读或者写公共字段以及调用公共方法)可以让对象处于无效状态,观察到对象处于无效状态、或者违反类的任何不可变量、前置条件或者后置条件的情况。
此外,一个类要成为线程安全的,在被多个线程访问时,不管运行时环境执行这些线程有什么样的时序安排或者交错,它必须仍然有如上所述的正确行为,并且在调用的代码中没有任何额外的同步。其效果就是,在所有线程看来,对于线程安全对象的操作是以固定的、全局一致的顺序发生的。
正确性与线程安全性之间的关系非常类似于在描述 ACID(原子性、一致性、独立性和持久性)事务时使用的一致性与独立性之间的关系:从特定线程的角度看,由不同线程所执行的对象操作是先后(虽然顺序不定)而不是并行执行的。
举例
比如一个 ArrayList 类,在添加一个元素的时候,它可能会有两步来完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。
在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;
而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0 (注意哦,我们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。
那好,我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了。
安全程度
线程安全性不是一个非真即假的命题。 Vector 的方法都是同步的,并且 Vector 明确地设计为在多线程环境中工作。但是它的线程安全性是有限制的,即在某些方法之间有状态依赖(类似地,如果在迭代过程中 Vector 被其他线程修改,那么由 Vector.iterator() 返回的 iterator会抛出ConcurrentModificationException)。
对于 Java 类中常见的线程安全性级别,没有一种分类系统可被广泛接受,不过重要的是在编写类时尽量记录下它们的线程安全行为。
Bloch 给出了描述五类线程安全性的分类方法:不可变、线程安全、有条件线程安全、线程兼容和线程对立。只要明确地记录下线程安全特性,那么您是否使用这种系统都没关系。这种系统有其局限性 -- 各类之间的界线不是百分之百地明确,而且有些情况它没照顾到 -- 但是这套系统是一个很好的起点。这种分类系统的核心是调用者是否可以或者必须用外部同步包围操作(或者一系列操作)。下面几节分别描述了线程安全性的这五种类别。
不可变
不可变的对象一定是线程安全的,并且永远也不需要额外的同步[1] 。因为一个不可变的对象只要构建正确,其外部可见状态永远也不会改变,永远也不会看到它处于不一致的状态。Java 类库中大多数基本数值类如 Integer 、 String 和 BigInteger 都是不可变的。
需要注意的是,对于Integer,该类不提供add方法,加法是使用+来直接操作。而+操作是不具线程安全的。这是提供原子操作类AtomicInteger的原因。
线程安全
线程安全的对象具有在上面“线程安全”一节中描述的属性 -- 由类的规格说明所规定的约束在对象被多个线程访问时仍然有效,不管运行时环境如何排列,线程都不需要任何额外的同步。这种线程安全性保证是很严格的 -- 许多类,如 Hashtable 或者 Vector 都不能满足这种严格的定义。
有条件的
有条件的线程安全类对于单独的操作可以是线程安全的,但是某些操作序列可能需要外部同步。条件线程安全的最常见的例子是遍历由 Hashtable 或者 Vector 或者返回的迭代器 -- 由这些类返回的 fail-fast 迭代器假定在迭代器进行遍历的时候底层集合不会有变化。为了保证其他线程不会在遍历的时候改变集合,进行迭代的线程应该确保它是独占性地访问集合以实现遍历的完整性。通常,独占性的访问是由对锁的同步保证的 -- 并且类的文档应该说明是哪个锁(通常是对象的内部监视器(intrinsic monitor))。
如果对一个有条件线程安全类进行记录,那么您应该不仅要记录它是有条件线程安全的,而且还要记录必须防止哪些操作序列的并发访问。用户可以合理地假设其他操作序列不需要任何额外的同步。
线程兼容
线程兼容类不是线程安全的,但是可以通过正确使用同步而在并发环境中安全地使用。这可能意味着用一个 synchronized 块包围每一个方法调用,或者创建一个包装器对象,其中每一个方法都是同步的(就像 Collections.synchronizedList() 一样)。也可能意味着用 synchronized 块包围某些操作序列。为了最大程度地利用线程兼容类,如果所有调用都使用同一个块,那么就不应该要求调用者对该块同步。这样做会使线程兼容的对象作为变量实例包含在其他线程安全的对象中,从而可以利用其所有者对象的同步。
许多常见的类是线程兼容的,如集合类 ArrayList 和 HashMap 、 java.text.SimpleDateFormat 、或者 JDBC 类 Connection 和 ResultSet 。
线程对立
线程对立类是那些不管是否调用了外部同步都不能在并发使用时安全地呈现的类。线程对立很少见,当类修改静态数据,而静态数据会影响在其他线程中执行的其他类的行为,这时通常会出现线程对立。线程对立类的一个例子是调用 System.setOut() 的类
-
java 多线程 线程安全及非线程安全的集合对象
2018-09-05 10:04:42线程安全:就是当多线程访问时,采用了加锁的机制;即当一个线程访问该类的某个数据时,会对这个数据进行保护,其他线程不能对其访问,直到该线程读取完之后,其他线程才可以使用。防止出现数据不一致或者数据被污染...一、概念:
- 线程安全:就是当多线程访问时,采用了加锁的机制;即当一个线程访问该类的某个数据时,会对这个数据进行保护,其他线程不能对其访问,直到该线程读取完之后,其他线程才可以使用。防止出现数据不一致或者数据被污染的情况。
- 线程不安全:就是不提供数据访问时的数据保护,多个线程能够同时操作某个数据,从而出现数据不一致或者数据污染的情况。
- 对于线程不安全的问题,一般会使用synchronized关键字加锁同步控制。
- 线程安全 工作原理: jvm中有一个main memory对象,每一个线程也有自己的working memory,一个线程对于一个变量variable进行操作的时候, 都需要在自己的working memory里创建一个copy,操作完之后再写入main memory。
当多个线程操作同一个变量variable,就可能出现不可预知的结果。
而用synchronized的关键是建立一个监控monitor,这个monitor可以是要修改的变量,也可以是其他自己认为合适的对象(方法),然后通过给这个monitor加锁来实现线程安全,每个线程在获得这个锁之后,要执行完加载load到working memory 到 use && 指派assign 到 存储store 再到 main memory的过程。才会释放它得到的锁。这样就实现了所谓的线程安全。
二、线程安全(Thread-safe)的集合对象:
- Vector 线程安全:
- HashTable 线程安全:
- StringBuffer 线程安全:
三、非线程安全的集合对象:
- ArrayList :
- LinkedList:
- HashMap:
- HashSet:
- TreeMap:
- TreeSet:
- StringBulider:
四、相关集合对象比较:
- Vector、ArrayList、LinkedList:
1、Vector:
Vector与ArrayList一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,访问它比访问ArrayList慢。
2、ArrayList:
a. 当操作是在一列数据的后面添加数据而不是在前面或者中间,并需要随机地访问其中的元素时,使用ArrayList性能比较好。
b. ArrayList是最常用的List实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要讲已经有数组的数据复制到新的存储空间中。当从ArrayList的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。
3、LinkedList:
a. 当对一列数据的前面或者中间执行添加或者删除操作时,并且按照顺序访问其中的元素时,要使用LinkedList。
b. LinkedList是用链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较慢。另外,他还提供了List接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。
Vector和ArrayList在使用上非常相似,都可以用来表示一组数量可变的对象应用的集合,并且可以随机的访问其中的元素。
ArryList和LinkedList的区别:
在处理一列数据项时,Java提供了两个类ArrayList和LinkedList,ArrayList的内部实现是基于内部数组Object[],所以从概念上说它更像数组;然而LinkedList的内部实现是基于一组连接的记录,所以,它更像一个链表结构;所以它们在性能上有很大的差别。
由上可知,在ArrayList的前面或者中间插入数据的时候,必须将其后的所有数据相应的后移,这样要花费较多的时间;所以,当操作是在一列数据的后面添加数据而不是在前面或者中间,并需要随机地访问其中的元素时,使用ArrayList性能比较好。
然而访问链表中的某个元素的时候,就必须从链表的一端开始,沿着连接的方向一个一个元素的去查找,直到找到所需的元素为止,所以,当对一列数据的前面或者中间执行添加或者删除操作时,并且按照顺序访问其中的元素时,要使用LinkedList。
如果在实际的操作中,前面两种情况交替出现,可以考虑使用List这样的通用接口,而不用关心具体的实现,再具体的情况下,它的性能由具体的实现来保证。-
HashTable、HashMap、HashSet:
HashTable和HashMap采用的存储机制是一样的,不同的是:
1、HashMap:
a. 采用数组方式存储key-value构成的Entry对象,无容量限制;
b. 基于key hash查找Entry对象存放到数组的位置,对于hash冲突采用链表的方式去解决;
c. 在插入元素时,可能会扩大数组的容量,在扩大容量时须要重新计算hash,并复制对象到新的数组中;
d. 是非线程安全的;
e. 遍历使用的是Iterator迭代器;2、HashTable:
a. 是线程安全的;
b. 无论是key还是value都不允许有null值的存在;在HashTable中调用Put方法时,如果key为null,直接抛出NullPointerException异常;
c. 遍历使用的是Enumeration列举;3、HashSet:
a. 基于HashMap实现,无容量限制;
b. 是非线程安全的;
c. 不保证数据的有序; -
TreeSet、TreeMap:
TreeSet和TreeMap都是完全基于Map来实现的,并且都不支持get(index)来获取指定位置的元素,需要遍历来获取。另外,TreeSet还提供了一些排序方面的支持,例如传入Comparator实现、descendingSet以及descendingIterator等。
1、TreeSet:
a. 基于TreeMap实现的,支持排序;
b. 是非线程安全的;2、TreeMap:
a. 典型的基于红黑树的Map实现,因此它要求一定要有key比较的方法,要么传入Comparator比较器实现,要么key对象实现Comparator接口;
b. 是非线程安全的; -
StringBuffer和StringBulider:
StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串。
1、在执行速度方面的比较:StringBuilder > StringBuffer ;
2、StringBuffer与StringBuilder,他们是字符串变量,是可改变的对象,每当我们用它们对字符串做操作时,实际上是在一个对象上操作的,不像String一样创建一些对象进行操作,所以速度就快了;
3、 StringBuilder:线程非安全的;
4、StringBuffer:线程安全的;
对于String、StringBuffer和StringBulider三者使用的总结:
1.如果要操作少量的数据用 = String
2.单线程操作字符串缓冲区 下操作大量数据 = StringBuilder
3.多线程操作字符串缓冲区 下操作大量数据 = StringBuffer -
Window线程安全与线程控制函数
2020-06-21 03:17:52线程安全问题 多线程同时访问全局变量:一个线程取值后失去CPU另一个线程取值后也失去CPU,此时它们保存的就是相同的值。也是是说,比如两个线程再次对全区变量做++操作时变量只会被修改为同样的值。 二. 临界... -
Java线程安全和非线程安全
2013-05-16 14:09:47ArrayList和Vector有什么区别?...面对这样的问题,回答是:ArrayList是非线程安全的,Vector是线程安全的;HashMap是非线程安全的,HashTable是线程安全的;StringBuilder是非线程安全的,StringBuff -
线程安全与线程不安全
2019-06-08 16:08:291、是线程安全与线程不安全 线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 ... -
# 线程安全 & 线程安全函数 & 线程不安全函数
2017-05-15 07:12:57线程安全 & 线程安全函数 & 线程不安全函数 线程安全 就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不... -
一文教会你什么线程安全以及如何实现线程安全
2020-12-16 13:59:49title: 一文教会你什么线程安全以及如何实现线程安全 tags: 线程安全 Java并发 Java内存模型 synchronized volatile Lock 文章目录一、线程安全的概念二、导致线程不安全的原因三、线程安全问题3.1 原子性3.2 可见... -
线程安全与非线程安全的区别
2019-07-09 17:45:44线程安全:是多线程访问时,采用加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 非线程安全:是多线程访问时,... -
C 语言编程 — 线程安全与线程非安全
2020-05-14 23:33:06文章目录目录线程安全与线程非安全C 语言的线程非安全函数(不可重入函数) 线程安全与线程非安全 多线程程序中,线程安全是必须要考虑的因素。 线程安全(Thread Safe)就是在多线程环境中,多个线程在同一时刻对同... -
Java线程(一):线程安全与不安全
2012-04-02 12:13:29作为一个Java web开发人员,很少也不需要去处理线程,因为服务器已经帮我们处理好了。记得大一刚学Java的时候,老师带着我们做了一个局域网聊天室,用到了AWT、Socket、多线程、I/O,编写的客户端和服务器,当时做... -
5个步骤,教你瞬间明白线程和线程安全
2018-09-01 16:23:22作者 |一个程序员的成长责编 | 胡巍巍记得今年3月份刚来杭州面试的时候,有一家公司的技术总监问了我这样一个问题:你来说说有哪些线程安全的类?我心里一想,这我早都背好了... -
什么是线程安全问题 及怎么解决线程安全问题
2020-08-26 10:29:231.什么是线程安全问题 就是 多线程环境中 , 且存在数据共享 , 一个线程访问的共享 数据被其他线程修改了, 那么就发生了线程安全问题 , 整个访问过程中 , 无一共享的数据被其他线程修改了 就是线程安全的 程序中如果... -
Java线程安全与不安全
2019-06-05 10:12:54Java非线程安全与线程安全 ArrayList和Vector的区别在哪里? HashMap和HashTable区别在哪里? StringBuilder和StringBuffer区别在哪里? 张口即答,区别在于前者是非线程安全的,后者是线程是线程安全的。 那么... -
线程安全和线程不安全
2018-05-21 16:03:07前言 线程安全,是当多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全,是不... -
多线程安全
2019-01-19 15:32:57什么是线程安全 当多个线程同时共享,同一个全局变量或静态变量,做写操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作时不会发生数据冲突问题。 public class ThreadDemo { public ... -
什么是线程安全?如何保证线程安全?
2019-05-27 23:22:44什么是线程安全 参考: 《Java并发编程实践》中对线程安全的定义: 当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作...
-
2020牛客多校暑期集训营第一场题解.pdf
-
【数据分析-随到随学】SPSS调查问卷统计分析
-
十年老码农深入浅出让你彻底明白sql注入攻击
-
Cocos Creator游戏开发-连连看 (接入腾讯优量汇广告)
-
AZ-900 -2021年1月最新.pdf
-
BinarySearch[4]153. Find Minimum in Rotated Sorted Array
-
day1 - 推荐系统碎碎念
-
2020牛客暑期多校集训营第五场题解.pdf
-
2021全网最详细【WEB前端】从零入门实战教程,全课程119节
-
iapp获取电量代码
-
作业四
-
基于X210的裸机时钟温度显示器-第3/3季
-
【数据分析-随到随学】Python语法强化与数据处理
-
CUnit CUnit CUnit CUnit CUnit CUnit
-
三维地图GIS大数据可视化
-
新闻列表页的制作
-
Selenium3分布式与虚拟化
-
754. 平方矩阵 II ( 模拟 )
-
Mi Classcal.zip
-
FPGA 之 SOPC 系列(六)Nios II 程序开发 II