声明:观看尚硅谷李贺飞的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();
}