Java 线程

来源:互联网 发布:oppo商城软件下载 编辑:程序博客网 时间:2024/05/21 02:33

synchronized

保证被包含的代码段具有原子性,可见性
原子性:防止多个线程同时修改变量
可见性:确保在同步结束之前,使所有线程都能看见修改

注意:
volatile只保证可见性
static变量的修改,默认JVM会自动帮你同步

ReentrantLock

效率比synchronized要高

private static Lock lock = new ReentrantLock();public static void main(String[] args) {        lock.lock();      // 得到锁            try {                for(int i = 0; i < 10; i++) {                    System.out.print(i);                }            } finally {                lock.unlock();// 释放锁            }    }   

ReentrantReadWriteLock

读锁:一旦加锁,只允许读,不允许修改
写锁:一旦加锁,不允许其它线程操作

public void set(int data) {            rwl.writeLock().lock();// 取到写锁            try {                System.out.println(Thread.currentThread().getName() + "准备写入数据");                try {                    Thread.sleep(20);                } catch (InterruptedException e) {                    e.printStackTrace();                }                this.data = data;                System.out.println(Thread.currentThread().getName() + "写入" + this.data);            } finally {                rwl.writeLock().unlock();// 释放写锁            }        }           public void get() {            rwl.readLock().lock();// 取到读锁            try {                System.out.println(Thread.currentThread().getName() + "准备读取数据");                try {                    Thread.sleep(20);                } catch (InterruptedException e) {                    e.printStackTrace();                }                System.out.println(Thread.currentThread().getName() + "读取" + this.data);            } finally {                rwl.readLock().unlock();// 释放读锁            }        }    

相对于ReentrantLock拥有更高的并发效率

Condition

用于进程之间的通信:
参考

import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class Main {    private static final Lock lock=new ReentrantLock();    private static final Condition notFull=lock.newCondition();    private static final Condition notEmpty=lock.newCondition();    private static final int MAXN=100;    private static int good_num=0;    private static class Producer implements Runnable {        @Override        public void run() {            while(true){                lock.lock();                try{                    while(good_num>=MAXN){                        try {                            notFull.await();//否则阻塞                        } catch (InterruptedException e) {                            e.printStackTrace();                        }                    }                    good_num++;                    System.out.println("增加了一个:"+good_num);                    notEmpty.signal();//是则唤醒                }catch (Exception e){                    e.printStackTrace();                }finally{                    lock.unlock();                }            }        }    }    private static class Consumer implements Runnable {        @Override        public void run() {            while(true){                try{                    lock.lock();                    while(good_num<=0){                        try {                            notEmpty.await();//否则阻塞                        } catch (InterruptedException e) {                            e.printStackTrace();                        }                    }                    good_num--;                    System.out.println("消耗了一个:"+good_num);                    notFull.signal();//是则唤醒                }catch(Exception e){                    e.printStackTrace();                }finally{                    lock.unlock();                }            }        }    }    public static void main(String[] args) {        new Thread(new Producer()).start();        new Thread(new Consumer()).start();    }}

Java锁分类:
1、自旋锁
2、自旋锁的其他种类
3、阻塞锁
4、可重入锁
5、读写锁
6、互斥锁
7、悲观锁
8、乐观锁
9、公平锁
10、非公平锁
11、偏向锁
12、对象锁
13、线程锁
14、锁粗化
15、轻量级锁
16、锁消除
17、锁膨胀
18、信号量

线程池

newCachedThreadPool()
创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能

newFixedThreadPool(int nThreads)
创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。

newSingleThreadExecutor()
创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。

volatile 变量

volatile 变量用来确保将变量的更新操作通知到其他线程,保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新. 当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的.
原理
使用volatile和不使用volatile的区别在于 JVM内存主存和线程工作内存的同步之上。volatile保证变量在线程工作内存和主存之间一致。
其实是告诉处理器, 不要将我放入工作内存, 请直接在主存操作我. (解决的是存储上的一致问题)

package annotation;import java.util.concurrent.atomic.AtomicInteger;  /**  * 测试原子性的同步  * @author polarbear 2009-3-14  *  */  public class ActomTest{      public static AtomicInteger astom_i = new AtomicInteger();        public static volatile Integer v_integer_i = 0;      public static volatile int v_i = 0;       public static Integer integer_i = 0;      public static int i = 0;      public static int endThread = 0;      public static void main(String[] args) {          new ActomTest().testAtomic();      }      public void testAtomic() {          for(int i=0; i<100; i++) {              new Thread(new IntegerTestThread()).start();          }          try {              for(;;) {  //              Thread.sleep(500);                  if(ActomTest.endThread == 100) {                      System.out.println(">>Execute End:");                      System.out.println(">>AtomicInteger astom_i: \t"+ActomTest.astom_i);                      System.out.println(">>volatile Integer v_integer_i: \t"+ActomTest.v_integer_i);                      System.out.println(">>Integer integer_i: \t"+ActomTest.integer_i);                      System.out.println(">>int i: \t"+ActomTest.i);                      System.out.println(">>volatile int v_i: \t"+ActomTest.v_i);                      break;                  }              }          } catch (Exception e) {              e.printStackTrace();          }      }  }  class IntegerTestThread implements Runnable {      public void run() {          int x = 0;          while(x<1000) {              ActomTest.astom_i.incrementAndGet();              ActomTest.v_integer_i++;              ActomTest.integer_i++;              ActomTest.i++;              ActomTest.v_i++;              x++;          }          ++ActomTest.endThread;      }  }  //      >>Execute End://      >>AtomicInteger astom_i:     100000//      >>volatile Integer v_integer_i: 87330//      >>Integer integer_i:   91696//      >>int i:                  99299//      >>volatile int v_i:         99459

1:为什么会产生错误的数据?
多线程引起的,因为对于多线程同时操作一个整型变量在大并发操作的情况下无法做到同步,而Atom提供了很多针对此类线程安全问题的解决方案,因此解决了同时读写操作的问题。
2:为什么会造成同步问题?
Java多线程在对变量进行操作的时候,实际上是每个线程会单独分配一个针对i值的拷贝(独立内存区域),但是申明的i值确是在主内存区域中,当对i值修改完毕后,线程会将自己内存区域块中的i值拷贝到主内存区域中,因此有可能每个线程拿到的i值是不一样的,从而出现了同步问题。
3:为什么使用volatile修饰integer变量后,还是不行?
因为volatile仅仅只是解决了存储的问题,即i值只是保留在了一个内存区域中,但是i++这个操作,涉及到获取i值、修改i值、存储i值(i=i+1),这里的volatile只是解决了存储i值得问题,至于获取和修改i值,确是没有做到同步。
4:既然不能做到同步,那为什么还要用volatile这种修饰符?
主要的一个原因是方便,因为只需添加一个修饰符即可,而无需做对象加锁、解锁这么麻烦的操作。但是本人不推荐使用这种机制,因为比较容易出问题(脏数据),而且也保证不了同步。
5:那到底如何解决这样的问题?
第一种:采用同步synchronized解决,这样虽然解决了问题,但是也降低了系统的性能。
第二种:采用原子性数据Atomic变量。
6:Atomic的实现基本原理?
首先Atomic中的变量是申明为了volatile变量的,这样就保证的变量的存储和读取是一致的,都是来自同一个内存块,然后Atomic提供了getAndIncrement方法,该方法对变量的++操作进行了封装,并提供了compareAndSet方法,来完成对单个变量的加锁和解锁操作。

i++分为三个阶段:

内存到寄存器
寄存器自增
写回内存
这三个阶段中间都可以被中断分离开

面试题目:i++在两个线程里边分别执行100次,能得到的最大值和最小值分别是多少?
首先a执行99次,i为99,在未被写入内存时,b取i=0时执行1次,写入内存后i=1,此时覆盖掉了i=99的值;
然后a取i=1执行1次,b取i=1执行99次,当a比b后写入内存时,a覆盖掉b,此时i=2
所以最小为2,最大为200

同步机制的比较

ThreadLocal和线程同步机制相比有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
  在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。
  而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
  由于ThreadLocal中可以持有任何类型的对象,低版本JDK所提供的get()返回的是Object对象,需要强制类型转换。但JDK 5.0通过泛型很好的解决了这个问题,在一定程度地简化ThreadLocal的使用
  概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

设计开发

无状态:无状态方法的好处之一,就是它在各种环境下,都可以安全的调用。衡量一个方法是否是有状态的,就看它是否改动了其它的东西,比如全局变量,比如实例的字段

format方法在运行过程中改动了SimpleDateFormat的calendar字段,所以,它是有状态的。

这也同时提醒我们在开发和设计系统的时候注意下一下三点:
1.自己写公用类的时候,要对多线程调用情况下的后果在注释里进行明确说明
2.对线程环境下,对每一个共享的可变变量都要注意其线程安全性
3.我们的类和方法在做设计的时候,要尽量设计成无状态

并发大时效率较高

public class ConcurrentDateUtil {    private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {        @Override        protected DateFormat initialValue() {            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");        }    };    public static Date parse(String dateStr) throws ParseException {        return threadLocal.get().parse(dateStr);    }    public static String format(Date date) {        return threadLocal.get().format(date);    }}

静态方法同步知识

在多线程中使用同一个静态方法时,每个线程使用各自的实例字段(instance field)的副本,而共享一个静态字段(static field)。所以说,如果该静态方法不去操作一个静态成员,只在方法内部使用实例字段(instance field),不会引起安全性问题。
在静态的方法里,定义的局部变量不是静态变量。
静态变量属于类不属于对象也不属于某个方法。

0 0
原创粉丝点击