声明:观看尚硅谷李贺飞的java juc的视频笔记。
1、内存可见性问题:
多个线程在操作共享数据时,彼此不可见。
volatile使得多线程中彼此的操作可见。跟同步来说,它是一种轻量级的同步策略。
但是volatile不具备互斥性,不能保证变量的原子性。
以下例子:
public class TestVolatile { public static void main(String[] args) { MyThread mt = new MyThread(); new Thread(mt).start(); while(true) { if(mt.isFlag()) { System.out.println("****"); break; } } } } class MyThread implements Runnable{ private boolean flag=false; public boolean isFlag() { return flag; } public void setFlag(boolean flag) { this.flag = flag; } @Override public void run() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } flag = true; System.out.println("flag:"+isFlag()); } }通过以上代码,我测试了好几次,当子线程中的睡眠时间去掉的话,出现正常执行的概率要大点,如果是睡眠的话,几乎100%都是无限循环。
如果加了volatile,就不会出现这个问题。
2、原子变量:
1)volatile保证内存可见性
2)CAS算法保证数据的原子性(硬件对于并发操作共享数据的支持)
内存值 v
预估值 A
更新值 B
当V=A时,V=B,否则不做处理。
例子:i++的操作有三步:读-操作-写回
public class TestAutomic {
public static void main(String[] args) {
MyThread1 mt = new MyThread1();
for(int i=0;i<10;i++) {
new Thread(mt).start();
}
}
}
class MyThread1 implements Runnable{
public int i;
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i++);
}
}
使用原子类型可以解决这个问题:
public class TestAutomic { public static void main(String[] args) { MyThread1 mt = new MyThread1(); for(int i=0;i<10;i++) { new Thread(mt).start(); } } } class MyThread1 implements Runnable{ public AtomicInteger i = new AtomicInteger(0); @Override public void run() { try { Thread.sleep(200); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(i.getAndIncrement()); } }
3、ConcurrentHashMap的使用:
采用锁分段机制:每个段都是独立的锁。16个段,每个段一个表,表存链表数据。
4、对几个集合类的使用
public class TestCopyAndWrite {
public static void main(String[] args) {
MyThread3 mt = new MyThread3();
for(int i=0;i<10;i++) {
new Thread(mt).start();
}
}
}
class MyThread3 implements Runnable{
private static List<String> list = Collections.synchronizedList(new ArrayList<>());
static {
list.add("AA");
}
@Override
public void run() {
Iterator<String> it = list.iterator();
while(it.hasNext()) {
System.out.println(it.next());
list.add("BB");
}
}
}
运行上述的代码会出现:java.util.ConcurrentModificationException的异常。
将
private static List<String> list = Collections.synchronizedList(new ArrayList<>());
private static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();便可以解决这个异常。
注意:如果是比较多写入操作的list,不建议使用CopyOnwriteArrayList,因为每次写都要复制一份,效率比较低。
CopyOnwriteArrayList比较适合迭代比较多的情况。
5、CountDownLatch的使用(闭锁)
public class TestCountdown { //开始4个人,来一个减一,为0时,继续执行。 private static CountDownLatch latch = new CountDownLatch(4); public static void main(String[] args) throws InterruptedException { //四个人相约爬山,全部人齐了才开始 TestCountdown tc = new TestCountdown(); //因为到达目的地是并发的过程,这里开启四个线程 //我的线程 new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } tc.meArrive(); latch.countDown(); } }).start(); //严的线程 new Thread(new Runnable() { @Override public void run() { tc.yanArrive(); latch.countDown(); } }).start(); //俊金的线程 new Thread(new Runnable() { @Override public void run() { tc.junjinArrive(); latch.countDown(); } }).start(); //仲源的线程 new Thread(new Runnable() { @Override public void run() { tc.zhongyanArrive(); latch.countDown(); } }).start(); //人数不齐,继续等 latch.await(); //开始爬山 tc.climb(); } public void meArrive() { System.out.println("我到了。。。"); } public void yanArrive() { System.out.println("严到了。。。"); } public void junjinArrive() { System.out.println("俊金到了。。。"); } public void zhongyanArrive() { System.out.println("仲源到了。。。"); } public void climb() { System.out.println("开始爬山。。。"); } }5、多线程的第三种方式:
实现Callable接口,有返回值和抛出异常。需求FutureTask的支持。用于接受返回接口。FutureTask也可用于闭锁。
例子:
public class TestCallable { public static void main(String[] args) throws InterruptedException, ExecutionException { MyThread4 mt = new MyThread4(); FutureTask<Integer> ft = new FutureTask<>(mt); new Thread(ft).start(); System.out.println(Thread.currentThread().getName()+':'); //打印返回结果 Integer in = ft.get(); System.out.println(in); } } class MyThread4 implements Callable<Integer>{ @Override public Integer call() throws Exception { int temp = 0; for(int i=0;i<100;i++) { temp += i; } System.out.println(Thread.currentThread().getName()+':'); return temp; } }5、解决多线程安全问题的方法:
1)同步代码快
2)同步方法
3)同步锁(jdk 5.0后)(锁的释放问题,一旦没放,会出问题,所以建议放在finally中)
例子:public class TestLock { public static void main(String[] args) { MyThread5 mt = new MyThread5(); new Thread(mt,"窗口1").start(); new Thread(mt,"窗口2").start(); new Thread(mt,"窗口3").start(); } } class MyThread5 implements Runnable{ //卖一百张票 private int tickets=100; //使用同步锁 private Lock lock = new ReentrantLock(); @Override public void run() { lock.lock();//加锁 try { while(tickets>0) { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"余票为:"+--tickets); } } catch (Exception e) { e.printStackTrace(); }finally { lock.unlock(); } } }
6、生产者和消费者案例:
1)这部分代码有个问题是,当产品缺货或者产品已满时,都会继续执行相应的操作,比如产品缺货了,还在一直卖,所以
打印多个缺货。这个是不太合理的,缺货后,应该等着,有货了才执行卖的操作。
public class TestCosumerProduct { public static void main(String[] args) { Clerk clerk = new Clerk(); Productor pd = new Productor(clerk); Consumer cs = new Consumer(clerk); new Thread(pd,"生产者").start(); new Thread(cs,"消费者").start(); } } //店员 class Clerk{ private int product=0; //进货 public synchronized void get() { if(product>=10) { System.out.println("产品已满!"); }else { System.out.println(Thread.currentThread().getName()+" : "+ ++product); } } //出货 public synchronized void sale() { if(product<=0) { System.out.println("缺货!"); }else { System.out.println(Thread.currentThread().getName()+" : "+ --product); } } } class Productor implements Runnable{ private Clerk clerk; public Productor(Clerk clerk) { super(); this.clerk = clerk; } @Override public void run() { for (int i = 0; i < 20; i++) { clerk.get(); } } } class Consumer implements Runnable{ private Clerk clerk; public Consumer(Clerk clerk) { super(); this.clerk = clerk; } @Override public void run() { for(int i = 0; i < 20; i++) { clerk.sale(); } } }2)采用等待唤醒机制来处理上述问题:这里确实是解决了上述的问题,但是也有问题:通过3)中演示。
public class TestCosumerProduct {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Productor pd = new Productor(clerk);
Consumer cs = new Consumer(clerk);
new Thread(pd,"生产者").start();
new Thread(cs,"消费者").start();
}
}
//店员
class Clerk{
private int product=0;
//进货
public synchronized void get() {
if(product>=10) {
System.out.println("产品已满!");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
System.out.println(Thread.currentThread().getName()+" : "+ ++product);
this.notifyAll();
}
}
//出货
public synchronized void sale() {
if(product<=0) {
System.out.println("缺货!");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
System.out.println(Thread.currentThread().getName()+" : "+ --product);
this.notifyAll();
}
}
}
class Productor implements Runnable{
private Clerk clerk;
public Productor(Clerk clerk) {
super();
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
clerk.get();
}
}
}
class Consumer implements Runnable{
private Clerk clerk;
public Consumer(Clerk clerk) {
super();
this.clerk = clerk;
}
@Override
public void run() {
for(int i = 0; i < 20; i++) {
clerk.sale();
}
}
}
3)此时会发现循环次数执行完后,代码还是没有停止,因为在产品已满或者缺货中一直等待着。
解决方式:将else的部分去掉即可(其实也有问题,因为两个的循环次数是一样的,所以没有,如果不一样,也会有)
public class TestCosumerProduct { public static void main(String[] args) { Clerk clerk = new Clerk(); Productor pd = new Productor(clerk); Consumer cs = new Consumer(clerk); new Thread(pd,"生产者").start(); new Thread(cs,"消费者").start(); } } //店员 class Clerk{ private int product=0; //进货 public synchronized void get() { if(product>=1) { System.out.println("产品已满!"); try { Thread.sleep(200); } catch (InterruptedException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }else { System.out.println(Thread.currentThread().getName()+" : "+ ++product); this.notifyAll(); } } //出货 public synchronized void sale() { if(product<=0) { System.out.println("缺货!"); try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }else { System.out.println(Thread.currentThread().getName()+" : "+ --product); this.notifyAll(); } } } class Productor implements Runnable{ private Clerk clerk; public Productor(Clerk clerk) { super(); this.clerk = clerk; } @Override public void run() { for (int i = 0; i < 20; i++) { clerk.get(); } } } class Consumer implements Runnable{ private Clerk clerk; public Consumer(Clerk clerk) { super(); this.clerk = clerk; } @Override public void run() { for(int i = 0; i < 20; i++) { clerk.sale(); } } }
4)增加线程,一共有4个线程,wait用while进行循环,如果是if的话,存在虚假唤醒的问题,所以建议wait的方法总是在条件的
循环中比较好。
public class TestCosumerProduct { public static void main(String[] args) { Clerk clerk = new Clerk(); Productor pd = new Productor(clerk); Consumer cs = new Consumer(clerk); new Thread(pd,"生产者1").start(); new Thread(cs,"消费者1").start(); new Thread(pd,"生产者2").start(); new Thread(cs,"消费者2").start(); } } //店员 class Clerk{ private int product=0; //进货 public synchronized void get() { while(product>=1) { System.out.println("产品已满!"); try { Thread.sleep(200); } catch (InterruptedException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } try { //虚假唤醒 this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+" : "+ ++product); this.notifyAll(); } //出货 public synchronized void sale() { while(product<=0) { System.out.println("缺货!"); try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+" : "+ --product); this.notifyAll(); } } class Productor implements Runnable{ private Clerk clerk; public Productor(Clerk clerk) { super(); this.clerk = clerk; } @Override public void run() { for (int i = 0; i < 20; i++) { clerk.get(); } } } class Consumer implements Runnable{ private Clerk clerk; public Consumer(Clerk clerk) { super(); this.clerk = clerk; } @Override public void run() { for(int i = 0; i < 20; i++) { clerk.sale(); } } }
5)使用同步锁:
public class TestCosumerProduct2 { public static void main(String[] args) { Clerk2 clerk = new Clerk2(); Productor2 pd = new Productor2(clerk); Consumer2 cs = new Consumer2(clerk); new Thread(pd,"生产者1").start(); new Thread(cs,"消费者2").start(); new Thread(pd,"生产者3").start(); new Thread(cs,"消费者4").start(); } } //店员 class Clerk2{ private int product=0; //使用同步锁 private Lock lock = new ReentrantLock(); private Condition con = lock.newCondition(); //进货 public void get() { lock.lock(); try { while(product>=1) { System.out.println("产品已满!"); try { Thread.sleep(200); } catch (InterruptedException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } try { //虚假唤醒 con.await(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+" : "+ ++product); con.signalAll(); } finally { lock.unlock(); } } //出货 public synchronized void sale() { lock.lock(); try { while(product<=0) { System.out.println("缺货!"); try { con.await(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+" : "+ --product); con.signal(); } finally { lock.unlock(); } } } class Productor2 implements Runnable{ private Clerk2 clerk; public Productor2(Clerk2 clerk) { super(); this.clerk = clerk; } @Override public void run() { for (int i = 0; i < 20; i++) { clerk.get(); } } } class Consumer2 implements Runnable{ private Clerk2 clerk; public Consumer2(Clerk2 clerk) { super(); this.clerk = clerk; } @Override public void run() { for(int i = 0; i < 20; i++) { clerk.sale(); } } }7、交替打印的实现:
public class TestLoopPrint { public static void main(String[] args) { Loop loop = new Loop(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 3; i++) { loop.loopA(i); } } },"A").start(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 3; i++) { loop.loopB(i); } } },"B").start(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 3; i++) { loop.loopC(i); } } },"C").start(); } } class Loop{ private int flag = 1; private Lock lock = new ReentrantLock(); private Condition condition1 = lock.newCondition(); private Condition condition2 = lock.newCondition(); private Condition condition3 = lock.newCondition(); public void loopA(int totalLoop) { try { lock.lock(); //当flag不为1时,等待 if(flag != 1) { try { condition1.await(); } catch (InterruptedException e) { e.printStackTrace(); } } //打印结果 for(int i=0;i<=3;i++) { System.out.println(Thread.currentThread().getName()+"->"+totalLoop); } //换新B flag = 2; condition2.signal(); } finally { lock.unlock(); } } public void loopB(int totalLoop) { try { lock.lock(); //当flag不为1时,等待 if(flag != 2) { try { condition2.await(); } catch (InterruptedException e) { e.printStackTrace(); } } //打印结果 for(int i=0;i<=3;i++) { System.out.println(Thread.currentThread().getName()+"->"+totalLoop); } //换新c flag = 3; condition3.signal(); } finally { lock.unlock(); } } public void loopC(int totalLoop) { try { lock.lock(); //当flag不为1时,等待 if(flag != 3) { try { condition3.await(); } catch (InterruptedException e) { e.printStackTrace(); } } //打印结果 for(int i=0;i<=3;i++) { System.out.println(Thread.currentThread().getName()+"->"+totalLoop); } //换新A flag = 1; condition1.signal(); } finally { lock.unlock(); } } }
8、读写锁:ReadWritelock
读写和写写要互斥,读读不用互斥。
public class TestReadWriteLock { public static void main(String[] args) { ReadWriteLockDemo rwld = new ReadWriteLockDemo(); for(int i=0;i<100;i++) { new Thread(new Runnable() { @Override public void run() { rwld.getNumber(); } }).start(); } new Thread(new Runnable() { @Override public void run() { rwld.setNumber(12); } }).start(); } } class ReadWriteLockDemo{ private int number; private ReadWriteLock rwl = new ReentrantReadWriteLock(); public void getNumber() { rwl.readLock().lock(); try { System.out.println(Thread.currentThread().getName()+"读"+number); } finally { rwl.readLock().unlock(); } } public void setNumber(int number) { rwl.writeLock().lock(); try { this.number = number; System.out.println(Thread.currentThread().getName()+":写"); } finally { rwl.writeLock().unlock(); } } }9、线程8锁:
1)非静态方法的锁,默认为this,静态方法默认为当前的Class实例
2)某一个时刻只能有一个线程持有锁,无论几个方法。
10、线程池:(底层提供一个线程队列)
为什么要用线程池:如果频繁的创建和销毁线程,性能会有所下降。
线程池的体系结构:Executor 负责线程的使用与调度的根接口
ExecutorService:子接口:线程池的主要接口
A ThreadPoolExecutor:线程池的主要实现类
B ScheduledExecutorService:子接口:负责线程的调度
ScheduledThreadPoolExecutor:继承了A实现了B
工具类:Executors
newFixedThreadPool :创建固定的线程池
newCachedThreadPool :缓存线程池,数量不固定,可以根据需求自动的更改数量
newSingleThreadExecutor:创建单个线程,线程池只有一个线程。
newScheduledThreadPool:创建固定大小的线程,可以延迟和定时执行任务。
例子:
public class TestPool { //创建线程池 public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService es = Executors.newFixedThreadPool(5); List<Future<Integer>> list = new ArrayList<>(); for(int i=0;i<3;i++) { Future<Integer> fu = es.submit(new Callable<Integer>() { @Override public Integer call() throws Exception { int sum=0; for(int i=0;i<33;i++) { sum +=i; } return sum; } }); list.add(fu); } //关闭线程池 es.shutdown(); //获取返回的结果 for (Future<Integer> future : list) { System.out.println(future.get()); } } }
例子2:
public static void main(String[] args) throws InterruptedException, ExecutionException { //创建任务 MyPoolThread mpt = new MyPoolThread(); ExecutorService es = Executors.newFixedThreadPool(5); //三十个任务,由5个线程处理 for(int i=0;i<30;i++) { es.submit(mpt);//提交三十次 } //关闭线程池 es.shutdown(); //获取返回的结果 } class MyPoolThread implements Runnable{ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+" : "+i); } } }线程调度:newScheduledThreadPool
public class TestScheduled { public static void main(String[] args) throws InterruptedException, ExecutionException { //创建调度的线程池 ScheduledExecutorService es = Executors.newScheduledThreadPool(1); for(int i=0;i<5;i++) { Future<Integer> fu = es.schedule(new Callable<Integer>() { @Override public Integer call() throws Exception { int temp = new Random().nextInt(100); System.out.println(Thread.currentThread().getName()+" : "+temp); return temp; } }, 1, TimeUnit.SECONDS); System.out.println(fu.get());//调用了该方法才会有定时的效果。 } es.shutdown(); }