多线程核心知识
- 线程通信
1.两个线程交替打印hello和world ,利用 synchronized + wait + notify 或者 lock
synchronized :只有拿到对应的锁才能执行里面方法
wait: 当前线程阻塞,等待被唤醒,并且释放锁
notify: 唤醒被阻塞的线程
package com.basic.work.thread.通信; /** * * * 这是线程通信的例子 * @author chenzhubing * */ public class Test { public boolean flag = false; /** * @param args */ public static void main(String[] args) { final Test test = new Test(); Thread t1 = new Thread(new Runnable() { @Override public void run() { while(true){ synchronized (test) { try { if(test.flag) test.wait(); System.out.println("hello"); test.flag = true; test.notify(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { while(true){ synchronized (test) { try { if(!test.flag) test.wait(); System.out.println("world"); test.flag = false; test.notify(); } catch (InterruptedException e) { e.printStackTrace(); } } } } }); t1.start(); t2.start(); } }
ReentrantLock:可重入锁,当前线程获取锁后,可以再次获取,增加了同步状态的值(计数器+1),在释放的过程中,不断减少同步状态,只为同步状态为0时,锁完全释放。
lock + condition.await + condition.signal 的原理和synchronized + wait + notify 类似,但是要注意lock需要手动上锁lock.lock()和释放锁lock.unlock()
package com.basic.work.thread.通信; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * * 这是线程通信的例子 * @author chenzhubing * */ public class TestLock { static int status = 0; public static void main(String[] args) { final Lock lock = new ReentrantLock(); final Condition condition1 = lock.newCondition(); Thread t1 = new Thread(new Runnable() { @Override public void run() { for(int i = 0;i<10;i++){ try { lock.lock(); if(status == 1){ condition1.await(); } System.out.println("hello"); status = 1; condition1.signal(); } catch (InterruptedException e) { }finally{ lock.unlock(); } } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { for(int i = 0;i<10;i++){ try { lock.lock(); if(status == 0){ condition1.await(); } System.out.println("world"); status = 0; condition1.signal(); } catch (InterruptedException e) { }finally{ lock.unlock(); } } } }); t1.start(); t2.start(); } }
- ThreadLocal关键字
每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本。底层是维护了一个ThreadLocalMap,ThreadLocalMap中key存放的是当前线程
Thread.currentThread(); value存放的是当前线程对应的变量。从而实现了线程间的数据隔离
package com.basic.work.thread.threadlocal; public class SequenceNumber { private static ThreadLocal<Integer> local = new ThreadLocal<Integer>(){ // 覆盖初始化方法 protected Integer initialValue() { return 0; } }; public int getNetxNum(){ local.set(local.get()+1); return local.get(); } public static void main(String[] args) { SequenceNumber sn = new SequenceNumber(); Thread t1 = new Thread(new ThreadLocalClient(sn)); Thread t2 = new Thread(new ThreadLocalClient(sn)); Thread t3 = new Thread(new ThreadLocalClient(sn)); t1.start(); t2.start(); t3.start(); } } class ThreadLocalClient implements Runnable{ private SequenceNumber seq; public ThreadLocalClient(SequenceNumber seq){ this.seq = seq; } @Override public void run() { for(int i=0;i<3;i++){ System.out.println("thread name="+Thread.currentThread().getName()+" sn="+seq.getNetxNum()); } } }
- volatile关键字
保证可见性:当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
保证有序性:禁止进行指令重排序
不能保证原子性:在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。volatile
不能保证原子性,保证原子性需要用到Actomic(基于CAS) 或者锁。下面这个例子能说明volatile 不能保证原子性
package com.basic.work.thread.actomic; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; public class Test { //volatile 具有可见性 public static volatile int num = 0; //AtomicInteger 具有原子性 public static AtomicInteger act = new AtomicInteger(); public static void main(String[] args) throws InterruptedException { final CountDownLatch latch = new CountDownLatch(10); for(int i = 0;i<10;i++){ new Thread(new Runnable() { @Override public void run() { for(int j = 0;j<1000;j++){ num++; act.incrementAndGet(); } latch.countDown(); } }).start(); } latch.await(); System.out.println(num); System.out.println(act.intValue()); } }
输出结果act:10000,但是num的值不固定
- CountDownLatch关键字
主线程main,需要在A线程和B线程完成后,打印耗时 CountDownLatch latch = new CountDownLatch(2); 计数器count为2
子线程调用latch.countDown(); //count-- 主线程调用latch.await(); 这是个阻塞方法,count=0的时候才能执行
package com.basic.work.thread.countdownlatch; import java.util.concurrent.CountDownLatch; public class Test { public static void main(String[] args) throws InterruptedException { final CountDownLatch latch = new CountDownLatch(2); new Thread(new Runnable() { @Override public void run() { for(int i =0;i<100;i++){ System.out.println("hello"); } latch.countDown(); } }).start(); new Thread(new Runnable() { @Override public void run() { for(int i =0;i<100;i++){ System.out.println("world"); } latch.countDown(); } }).start(); latch.await(); System.out.println("end.."); } }