synchronized写法
//正确的所线程写法,不要通过实现Runnable接口,这样会使程序的耦合性增大
public class TestTicket {
public static void main(String[] args) {
//1.创建资源对象
Ticket ticket=new Ticket();
//2.创建线程,通过Lambda表达式 Runnable为函数式接口
new Thread(()->{
for (int i = 0; i < 20; i++) {
ticket.sale();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
ticket.sale();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
ticket.sale();
}
},"C").start();
}
}
//资源类 OOP 票
class Ticket{
//属性 方法
private int Number=30;
synchronized void sale(){
if(!(Number<=0)){
/* try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
System.out.println(Thread.currentThread().getName()+"获得了第"+Number+"票,还剩-----》"+(--Number));
}
}
}
Lock写法
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestTicket1 {
public static void main(String[] args) {
//1.创建资源对象
Ticket1 ticket=new Ticket1();
//2.创建线程,通过Lambda表达式 Runnable为函数式接口
new Thread(()->{
for (int i = 0; i < 20; i++) {
ticket.sale();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
ticket.sale();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
ticket.sale();
}
},"C").start();
}
}
//资源类 OOP 票
class Ticket1{
//属性 方法
private int Number=30;
private Lock lock=new ReentrantLock();
//lock.lock(); 放在方法外,不行
void sale(){
lock.lock();
try {
if(!(Number<=0)){
System.out.println(Thread.currentThread().getName()+"获得了第"+Number+"票,还剩-----》"+(--Number));
}
} catch (Exception e) {
e.printStackTrace();
}
finally {
lock.unlock();
}
}
}
package day8_2.PC;
public class PCtrad {
public static void main(String[] args) {
Data data=new Data();
//实现A B两个进程之间的通信
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
try {
for (int i = 0; i < 20; i++) {
data.decrement();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"B").start();
}
}
//pc问题的模板:判断是否等待 业务 唤醒
class Data{//资源类
private int number=0;
// +1
synchronized void increment() throws InterruptedException {
if(number!=0){
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"->"+number);
//通知唤醒另一进程
this.notifyAll();
}
// -1
synchronized void decrement() throws InterruptedException {
if(number==0){
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"->"+number);
//通知唤醒另一进程
this.notifyAll();
}
}
问题:如果存在A,B,C,D等多个线程时,可能产生的虚假唤醒的问题,何为虚假唤醒,即线程有可能在不被通知的情况下唤醒,虽然这种情况很少发生,但是还是要通过不断测试应该使线程唤醒的条件来防范问题的发生。即将if条件改为while条件。
package LockPC;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class A {
public static void main(String[] args) {
Data data=new Data();
//实现A B两个进程之间的通信
new Thread(()->{
for (int i = 0; i < 3; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
try {
for (int i = 0; i < 3; i++) {
data.decrement();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"B").start();
new Thread(()->{
try {
for (int i = 0; i < 3; i++) {
data.increment();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"C").start();
new Thread(()->{
try {
for (int i = 0; i < 3; i++) {
data.decrement();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"D").start();
}
}
//Condition作用替换wait和notifyall
//condition.await()代替wait()
//conditon.signalAll()代替notifyAll
class Data{//资源类
private int number=0;
Lock lock=new ReentrantLock();
Condition condition=lock.newCondition();
// +1
public void increment() throws InterruptedException {
lock.lock();
try {
//业务代码
while (number!=0){
//等待
condition.await();//理解为信号量的pv操作?
}
number++;
System.out.println(Thread.currentThread().getName()+"->"+number);
//通知唤醒另一进程
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放锁
lock.unlock();
}
}
// -1
public void decrement() throws InterruptedException {
lock.lock();
try {
while(number==0){
//等待
condition.await();//理解为信号量的pv操作?
}
number--;
System.out.println(Thread.currentThread().getName()+"->"+number);
//通知唤醒另一进程
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
package LockPC;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionPC {
public static void main(String[] args) {
Data1 data=new Data1();
new Thread(()->{
for (int i = 0; i < 2; i++) {
data.PrintA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 2; i++) {
data.PrintB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 2; i++) {
data.PrintC();
} },"C").start();
}
}
class Data1{
//A通知B执行,B通知C执行,C通知A执行
int number=1;
Lock lock=new ReentrantLock();
Condition condition1= lock.newCondition();
Condition condition2=lock.newCondition();
Condition condition3=lock.newCondition();
public void PrintA(){
lock.lock();
try {
//业务代码;
while (number!=1){
condition1.await();
}
number=2;
System.out.println(Thread.currentThread().getName()+"执行完,通知B");
condition2.signal();//通知B执行
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void PrintB(){
lock.lock();
try {
while (number!=2){
condition2.await();
}
number=3;
System.out.println(Thread.currentThread().getName()+"执行完,通知C");
condition3.signal();//通知C执行
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void PrintC(){
lock.lock();
try {
while (number!=3){
condition3.await();
}
number=1;
System.out.println(Thread.currentThread().getName()+"执行完,通知A");
condition1.signal();//唤醒A
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
list不安全
通过以下三种方式解决
List list1=new Vector();
List list2= Collections.synchronizedList(new ArrayList<>());
List<String> list3=new CopyOnWriteArrayList<>();
public class A {
public static void main(String[] args) throws InterruptedException {
// List list=new ArrayList<>(); //线程不安全
/List<String> list=new CopyOnWriteArrayList<>();
CountDownLatch countDownLatch=new CountDownLatch(5000);
for (int i = 0; i < 5000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
countDownLatch.countDown();
},String.valueOf(i)).start();
}
countDownLatch.await();//添加此方法让5000个子线程执行完之后再执行以下两句,否则子线程和主线程(main)并发执行,可能输出的大小不是5000
// System.out.println(list);
System.out.println("---->"+list.size());
}
}
set不安全
通过以下两种方式解决
Set <String> set =Collections.synchronizedSet(new HashSet<>());
Set <String> set=new CopyOnWriteArraySet<>();
HashSet底层是HashMap可以查看hashset的构造方法,里面创建了一个HashMap
HashMap不安全
Map<String,String> map=new ConcurrentHashMap<>();
call()方法有返回值,可以抛出异常
public class TestCallable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// new Thread(new FutureTask<>(callable)).start
// FutureTask为Runable实现类,构造方法的参数为Callable对象
Data data=new Data();
FutureTask futureTask=new FutureTask(data);
new Thread(futureTask,"A").start();
new Thread(futureTask,"B").start();
Integer o=(Integer) futureTask.get();
System.out.println(o);
}
}
class Data implements Callable<Integer> {//泛型的参数为call方法的返回值类型。
@Override
public Integer call() throws Exception {
System.out.println("call方法");
return 1024;
}
}
**简介:**可理解为减法计数器,当CountDown()方法被调用而导致当前计数值为零时,await()方法之后的预计才执行
public class TestCountDown {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);//设置减法计数器,初始值
for (int i = 0; i < 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"go out=========");
countDownLatch.countDown();
},String.valueOf(i)).start();
// countDownLatch.countDown(); 此语句不能写在线程run方法之外
}
countDownLatch.await();//当计数器为零时向下执行
System.out.println("执行结束");
}
}
简介:当指定个数的线程达到共同屏障点时,才向下执行。即可以形象的理解为人都到齐了,再行动。
//一个线程实现从1到100的相加,另一个线程实现101到200的相加
//最后将两个结果相加
//共同的屏障点:await
public class CyclicBarrierTest {
static CyclicBarrier barrier = new CyclicBarrier(3);
static int sum1 = 0; // 1 到 100 相加
static int sum2 = 0; // 101 到 200 相加
public static void main (String[] args) {
// 线程 1
new Thread(new Runnable() {
@Override
public void run() {
// 执⾏ 1 到 100相加
for (int i = 1; i <= 100; i++) {
sum1 += i;
}
// 进⼊等待
try {
barrier.await(); // 1
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
// 线程2
new Thread(new Runnable() {
@Override
public void run() {
// 执⾏ 101 到 200 相加
for (int i = 101; i <= 200; i++) {
// 假设每次执⾏相加都会花⼀些时间
sum1 += i;
}
// 进⼊等待
try {
barrier.await(); //2
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
// 等到两个线程都执⾏完,在把他们相加
try {
barrier.await(); //3
System.out.println("最终的结果:" + (sum1 + sum2));
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class TestCB {
public static void main(String[] args) {
//集齐七颗龙珠召唤神龙
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("龙珠集齐,召唤神龙!!!!");
});//当达到共同屏障点之后,执行lambda表达式书写的线程
for (int i = 0; i < 7; i++) {
final int m=i+1;
new Thread(()->{
System.out.println("正在收集第"+m+"龙珠");//lambda表达式中的变量必须是final类型
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
信号量,semaphore.acquire()和semaphore.release()相当于操作系统中的pv操作
package com.javademo.JUC;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class TestSemaphore {
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore(3);//某个资源最多可以被三个线程使用
for (int i = 0; i < 10; i++) {
new Thread(()->{
try {
semaphore.acquire(); //p
System.out.println(Thread.currentThread().getName()+"抢到车位");
TimeUnit.SECONDS.sleep(4);
System.out.println(Thread.currentThread().getName()+"退出车位");
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
semaphore.release(); //v
}
},String.valueOf(i)).start();
}
}
}
ReadWriteLock(接口)
实现类:ReentrantReadWriteLock
读可以多个线程读,写只能有一个线程写
public class TestReadWriteLock {
public static void main(String[] args) {
MyCacheLock myCacheLock=new MyCacheLock();
for (int i = 0; i < 10; i++) {//
final int tempt=i;
new Thread(()->{
myCacheLock.put(tempt+"",tempt);
},String.valueOf(i)).start();
}
for (int i = 0; i < 10; i++) {
final int tempt=i;
new Thread(()->{
myCacheLock.get(tempt+"");
},String.valueOf(i)).start();
}
}
}
class MyCacheLock{
private volatile Map<String,Object> map=new HashMap<>();
private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
//写
public void put(String key,Object value){
try {
readWriteLock.writeLock().lock();
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"写入ok");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
//读
public void get(String key){
try {
readWriteLock.readLock().lock();
System.out.println(Thread.currentThread().getName()+"开始读取"+key);
Object o=map.get(key);
System.out.println(Thread.currentThread().getName()+"读取成功"+o);
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
BlockingQueue
主要的实现类:LinkedBlockingQueue,ArrayBlockingQueue,SynchronousQueue
方式 | 抛出异常的方法 | 有返回值,不抛出异常 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add | offer | put 队列没有位置,一直阻塞 | offer |
移除 | remove | poll | take 没有元素可以取出,一直阻塞 | poll |
检测队首元素 | element | peek |
public class TestBlockingQueue {
public static void main(String[] args) {
ArrayBlockingQueue arrayBlockingQueue=new ArrayBlockingQueue(3);//队列大小为3
System.out.println(arrayBlockingQueue.add("a"));
System.out.println(arrayBlockingQueue.add("b"));
System.out.println(arrayBlockingQueue.add("c"));
//System.out.println(arrayBlockingQueue.add("e"));
System.out.println("======");
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
}
}
**线程池要点:**三大方法,七大参数,四种拒绝策略
程序运行的本质是占用系统资源,为了优化资源的使用出现池化技术。
池化技术:实现准备好一些资源,使用的时候就来,使用完之后归还
(不推荐使用,使用原生的创建方式)
public class TestPol {
public static void main(String[] args) {
//ExecutorService threadPool= Executors.newSingleThreadExecutor();//拥有单个线程的线程池
ExecutorService threadPool=Executors.newFixedThreadPool(5);//创建一个拥有固定线程数的线程池
// ExecutorService threadPool=Executors.newCachedThreadPool();//可伸缩的线程池,遇强则强,遇弱则弱
try {
for (int i = 0; i < 100; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"----》oooook");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
public ThreadPoolExecutor(int corePoolSize,
//核心线程池大小,类比银行窗口为始终开放的窗口
int maximumPoolSize,
//最大核心线程池大小,银行窗口的总数
long keepAliveTime,
//银行新开设的窗口,超时关闭,时间大小
TimeUnit unit,
//时间单位 TimeUnit.SECONDS
BlockingQueue<Runnable> workQueue,
//阻塞队列,银行候客区的大小
ThreadFactory threadFactory,
//线程工厂,一般使用默认的,Executors.defaultThreadFactory()
RejectedExecutionHandler handler
//拒绝策略,最大承载数为maximumPoolSize+workQueue,以用来对超出最大达承载数的线程采用的拒绝策略
)
new ThreadPoolExecutor.AbortPolicy()//银行满了,不处理新来的人,抛出异常
new ThreadPoolExecutor.CallerRunsPolicy()//哪来的去哪
new ThreadPoolExecutor.DiscardOldestPolicy()//队列满了尝试去和最早的竞争
new ThreadPoolExecutor.DiscardPolicy()//队列满了,丢掉任务,不抛出异常
使用
import java.util.concurrent.*;
public class TestThreadPool {
public static void main(String[] args) {
ExecutorService threadPool=new ThreadPoolExecutor(2,
Runtime.getRuntime().availableProcessors(),//cpu密集型的任务设置最大核心线程数为计算机的处理机个数
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(5),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy() );
try {
for (int i = 0; i < 100; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"--->ooook");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
只要是函数式接口就可用lambda表达式简化
1. Function函数式接口
**特点:**有一个输入参数,有一个输出
Function <T ,R>中的方法
R apply(T t)
package com.javademo.JUC;
import java.util.function.Function;
public class TestFuntion {
public static void main(String[] args) {
//第一个泛型参数为apply方法的参数,第二个泛型参数T为方法apply的返回值类型,
Function<String,String > function=(str)->{ return str;};//lambda表达式重写了Function接口并且创建了对象
System.out.println(function.apply("chenweibin 你好"));
}
}
2.Predicate断定型接口
**特点:**有一个输入参数,返回值只能是布尔值
Predicate<T>中的方法
boolean test(T t)
import java.util.function.Predicate;
public class TestFuntion {
public static void main(String[] args) {
Predicate<String> predicate=(Str)->{return Str.isEmpty();};
System.out.println(predicate.test(""));
}
}
3.Consumer消费型接口
**特点:**只有输入没有返回值
Consumer<T> 的方法
void accept(T t);
public class TestFuntion {
public static void main(String[] args) {
Consumer<String> consumer=(Str)->{
System.out.println("我是消费性接口,我消耗了====》"+Str);};
consumer.accept("chen");
}
}
4.Supplier供给型接口
**特点:**没有参数只有输出
Supplier<T>的方法
T get();
public class TestFuntion {
public static void main(String[] args) {
Supplier<String> supplier=()->{
return "chen";};
System.out.println(supplier.get());
}
}
注:也可以用匿名内部类重写接口
public class TestFuntion {
public static void main(String[] args) {
Supplier<String> supplier=new Supplier<String>() {
@Override
public String get() {
return "chen";
}
};
System.out.println(supplier.get());
}
}
什么是流式计算
1.大数据就是存储加计算
2.MySQL和集合的本质就是存储数据,而计算则交给流来做
public class TestStreamC {
public static void main(String[] args) {
User user1=new User(1,"chen",23);
User user2=new User(2,"su",21);
User user3=new User(3,"xin",19);
User user4=new User(4,"liang",23);
List<User> list= Arrays.asList(user1,user2,user3,user4);//把数组转化为集合
list.stream()
.filter(u->u.age>20)//有提示可知参数为Predicate接口的对象,可用lambda表达式
.map(u->{ return u.name.toUpperCase();})//map映射,参数为Function接口对象
.sorted((u1,u2)->{return u1.compareTo(u2);})//@FunctionalInterface public interface Comparator也是函数接口里面有方法叫compare u2与u1交换位置排序方式发生变化
.forEach(System.out::println);//::就是把方法当作参数传到stream内部,是stream的每个元素都传入到该方法里面执行一下 ::就是调用静态方法
}
}
ForkJoinTask<V>的子类有:
RecursiveAction//递归事件,没有返回值
RecursiveTask//递归任务,有返回值
如何使用ForkJoin
1.通过ForkJoinPool执行
2.提交计算任务forkjoinpool.submit(ForkJoinTask task)
3.计算类要继承ForkJoinTask
package com.javademo.JUC;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
import java.util.stream.LongStream;
public class ForkJoinDemo extends RecursiveTask<Long> {
private Long start;
private Long end;
public ForkJoinDemo(Long start,Long end){
this.end=end;
this.start=start;
}
private Long temp=10_000l;//临界值,超过这个值就分割
@Override
protected Long compute() {
if((end-start)<temp){
Long sum=0l;
for(Long i=start;i<=end;i++){
sum+=i;
}
return sum;
}
else{
Long middle=(start+end)/2;
ForkJoinDemo task1=new ForkJoinDemo(start,middle);
task1.fork();//拆分任务,把任务压入线程队列
ForkJoinDemo task2=new ForkJoinDemo(middle+1,end);//注意加一
task2.fork();
return task1.join()+task2.join();
}
}
public static void Test1(){//普通计算
Long sum=0l;
Long start=System.currentTimeMillis();
for(Long i=1l;i<=100_000_000l;i++){
sum+=i;
}
Long end=System.currentTimeMillis();
System.out.println("结果为:"+sum+"用时:"+(end-start));
}
public static void Test2() throws ExecutionException, InterruptedException {//ForkJoin
Long start=System.currentTimeMillis();
ForkJoinDemo task=new ForkJoinDemo(1l,100_000_000l);
ForkJoinPool forkJoinPool=new ForkJoinPool();
ForkJoinTask<Long> submit = forkJoinPool.submit(task);
Long sum=submit.get();
Long end=System.currentTimeMillis();
System.out.println("结果为:"+sum+"用时:"+(end-start));
}
public static void Test3(){//Stream并行流
Long start=System.currentTimeMillis();
long sum= LongStream.rangeClosed(0l,100_000_000l).parallel().reduce(0,Long::sum);//注意范围
Long end=System.currentTimeMillis();
System.out.println("结果为:"+sum+"用时:"+(end-start));
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
Test1();
Test2();
Test3();
}
}
public class TestAsyn {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// public static CompletableFuture runAsync(Runnable runnable) 方法参数传入一个线程
//没有返回值的异步回调用runAsync 泛型参数为Void
CompletableFuture completableFuture=CompletableFuture.runAsync(()->{
System.out.println(Thread.currentThread().getName()+"runAsync====>void");
});
System.out.println("main Thread execute");
//如果不显示的调用get方法,则无法输出异步回调的结果
completableFuture.get();
}
}
java memory model java内存模型
每个线程都有自己的工作内存,同时计算机都有主内存。线程操作一个数据的时候,都需要read 然后load到自己的工作内存。同时修改之后进行store 且write回内存。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rd55007i-1607248214303)(C:\Users\hello\AppData\Roaming\Typora\typora-user-images\image-20200824115657232.png)]
以上的内存模型会存在一个问题,此问题具体可见以下代码,初始num等于零,当num等于零时子线程一直循环,main线程,会将num值赋为1,按照普通想法我们会理所当然的认为,子线程会停止执行。然而各自线程都有自己的工作内存,子线程对内存变化不可知。Volatile可以保证可见性,通过对变量num添加volatile,可以使程序正常,是符合我们的普通想法。
public class TestVolatile {
private static int num=0;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
while(num==0);
}).start();
TimeUnit.SECONDS.sleep(2);//保证子线程先执行
num=1;
System.out.println(num);//输出1 程序还未停止
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jDzJGnNh-1607248214308)(C:\Users\hello\AppData\Roaming\Typora\typora-user-images\image-20200824191145148.png)]
1.保持可见性
public class TestVolatile {
private static volatile int num=0;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
while(num==0);
}).start();
TimeUnit.SECONDS.sleep(2);//保证子线程先执行
num=1;
System.out.println(num);//输出1 程序停止
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gI8ZreI8-1607248214310)(C:\Users\hello\AppData\Roaming\Typora\typora-user-images\image-20200824191026138.png)]
2.不保证原子性
例如:设置一个num,创建20个线程各自对其进行一千次的自加,添加了Volatile之后,内存中的num对每一个线程都是可见的,然而最后的num并不是理论上的两万,因为自加操作不是原子操作,且Volatile不保证原子性,通过原子类来解决原子性问题
public class TestAtomic {
private static volatile int num=0;
private static void add(){
num++;
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int i1 = 0; i1 < 1000; i1++) {
add();
}
}).start();
}
while (Thread.activeCount()>2)//保证所有的子线程执行完 不然会在某些线程之间输出num的值 程序始终存在mian线程和gc线程
Thread.yield(); //线程让步,将cpu的执行权让给其他线程或者继续让给自己
System.out.println(num);
}
}
执行结果:
19968
不是两万
注:反编译 通过javap -c Demo.class
解决方法使用原子类
将变量 用原子类进行声明和实例化,例如 int 对应AtomicInteger; boolean 对应AtomicBoolean;Long 对应AtomicLong
private volatile static AtomicInteger num =new AtomicInteger();
public class TestAtomic {
private static volatile AtomicInteger num=new AtomicInteger();
private static void add(){
num.getAndIncrement();//num加一
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int i1 = 0; i1 < 1000; i1++) {
add();
}
}).start();
}
while (Thread.activeCount()>2)//保证所有的子线程执行完 不然会在某些线程之间输出num的值 程序始终存在mian线程和gc线程
Thread.yield(); //线程让步,将cpu的执行权让给其他线程或者继续让给自己
System.out.println(num);
}
}
3.禁止指令重排
程序在执行的时候可能要进行指令重排
总结:
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
**意图:**保证一个类仅有一个实例,并提供一个访问它的全局访问点。
**主要解决:**一个全局使用的类频繁地创建与销毁。
**何时使用:**当您想控制实例数目,节省系统资源的时候。
**如何解决:**判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
**关键代码:**构造函数是私有的(只暴露接口来创建对象);静态方法;静态变量
final:
/*
final关键字可以用来修饰引用、方法和类。
1、用来修饰一个引用
如果引用为基本数据类型,则该引用为常量,该值无法修改;
如果引用为引用数据类型,比如对象、数组,则该对象、数组本身可以修改,但指向该对象或数组的地址的引用不能修改。
如果引用为类的成员变量,则必须当场赋值,否则编译会报错(在方法内可以不用当场赋值)
2.用来修饰一个方法
当使用final修饰方法时,这个方法将成为最终方法,无法被子类重写。但是,该方法仍然可以被继承。
3.用来修饰类
当用final修改类时,该类成为最终类,无法被继承。简称为“断子绝孙类”。
*/
final class Person {
String name ="zs"; //3. 此处不赋值会报错
//final int age;
final int age = 10;
}
public class Demo01 {
public static void main(String[] args) { //1. 基本数组类型为常量,无法修改
final int i = 9;
//i = 10; //2. 地址不能修改,但是对象本身的属性可以修改
Person p = new Person();
p.name = "lisi";
final int[] arr = {1,2,3,45};
arr[3] = 999;
//arr = new int[]{1,4,56,78};
}
}
static:方便在没有创建对象的情况下来进行调用(方法/变量)
static不能修饰外部类只能修饰内部类。
1.static修饰方法:
static方法一般称作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有this的,因为它不依附于任何对象,既然都没有对象,就谈不上this了。并且由于这个特性,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用。
2.static 修饰变量(只能修饰成员变量,不能修饰方法内的属性,即局部变量,语法错误,再者这样各对象无法共享此变量啊)
3.static修饰代码块
4.案例理解
public class Main {
static int value = 33;
public static void main(String[] args) throws Exception{
new Main().printValue();
}
private void printValue(){
int value = 3;
System.out.println(this.value);
}
}
/*
这里面主要考察队this和static的理解。this代表什么?this代表当前对象,那么通过new Main()来调用printValue的话,当前对象就是通过new Main()生成的对象。而static变量是被对象所享有的,因此在printValue中的this.value的值毫无疑问是33。在printValue方法内部的value是局部变量,根本不可能与this关联,所以输出结果是33。在这里永远要记住一点:静态成员变量虽然独立于对象,但是不代表不可以通过对象去访问,所有的静态方法和静态变量都可以通过对象访问(只要访问权限足够)。
*/
package com.javademo.JUC;
public class Hungry {
private Hungry(){//构造私有
}
private final static Hungry hungry =new Hungry();//static修饰变量:它当且仅当在类初次加载时会被初始化
public static Hungry getInstance(){//通过静态方法
return hungry;
}
}
public class MianApplication {
public static void main(String[] args) {
Hungry instance1 = Hungry.getInstance();
Hungry instance2 = Hungry.getInstance();
System.out.println(instance1);
System.out.println(instance2);
System.out.println(instance1.equals(instance2));
System.out.println(instance1==instance2);
}
}
com.javademo.JUC.Hungry@14ae5a5
com.javademo.JUC.Hungry@14ae5a5
true
true
饿汉式存在的问题:
如果在Hungry类中创建多个开销巨大的数组,在对象一上来就会加载这些数组,会耗费巨大的内存
package com.javademo.JUC;
public class LazyMan {
private LazyMan(){
}
private static LazyMan lazyMan;
public static LazyMan getInstance(){
if(lazyMan==null){//静态方法中只能引用静态成员变量
lazyMan=new LazyMan();
}
return lazyMan;
}
}
问题:
单线程下此懒汉式单例没有问题,但是并发多线程之下,是否能继续保证单例呢?进行验证
package com.javademo.JUC;
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName()+"--->ooook");//验证对象是否只创建了一次
}
private static LazyMan lazyMan;
public static LazyMan getInstance(){
if(lazyMan==null){//静态方法中只能引用静态成员变量
lazyMan=new LazyMan();
}
return lazyMan;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
Thread-0--->ooook
Thread-1--->ooook
此懒汉式并不是线程安全的,多线程下无法保证单例
解决方法:双重检测锁模式
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName()+"--->ooook");//验证对象是否只创建了一次
}
private static volatile LazyMan lazyMan;
public static LazyMan getInstance(){
if(lazyMan==null){//要事先判断,不然每次都会让线程持有锁,耗费资源,只有当对象为空的时候,才让线程持有锁
synchronized (LazyMan.class){//锁LayMan,保证这个类只有一个
if (lazyMan==null)
//new LayMan() 并不是一个原子性操作,其经过了三个步骤
//1.分配内存空间
//2.执行构造方法,初始化对象
//3.把这个对象指向这个空间
//当存在指令重排时 ,可能执行132,
//若A线程执行13
//B线程进入,此时Layman!=null但是并没有完成构造,而B线程直接执行了return lazyman
// 这样的话 所以应该用volatile来禁止指令重排
lazyMan=new LazyMan();
}
}
return lazyMan;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
public class Holder{
//构造器私有
private Holder(){
}
public static Holder getInstance(){
return InnnerClass.holder;
}
public static class InnerClass{//用静态内部类来创建外部类的对象
private static final Holder holder=new Holder();
}
}
package com.javademo.JUC;
public class Holder{
//构造器私有
private Holder(){
System.out.println(Thread.currentThread().getName()+"-->ook");
}
public static Holder getInstance(){
return InnerClass.holder;
}
public static class InnerClass{//用静态内部类来创建外部类的对象
private static final Holder holder=new Holder();
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
Holder.getInstance();
}).start();
}
}
}
有以上代码可测试静态内部类实现的单例是线程安全的
静态内部类的一些特点
静态内部类和非静态内部类一样,都不会在外部类加载的时候而加载,而静态内部类加载的时候,会加载外部类的静态代码块,非静态内部类加载的时候,除了加载外部类的静态代码块之外还要加载外部类的构造方法,因为创建非静态内部类对象时先要创建外部类的对象,即创建的形式是 外部类对象.new 非静态内部类()
但是以上代码都是不安全的,因为存在反射技术
package com.javademo.JUC;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName()+"--->ooook");//验证对象是否只创建了一次
}
private static volatile LazyMan lazyMan;
public static LazyMan getInstance(){
if(lazyMan==null){//要事先判断,不然每次都会让线程持有锁,耗费资源,只有当对象为空的时候,才让线程持有锁
synchronized (LazyMan.class){//锁LayMan,保证这个类只有一个
if (lazyMan==null)
lazyMan=new LazyMan();
}
}
return lazyMan;
}
//反射破坏单例
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor();//存在的是空参构造器,通过getDeclaredConstructor获得空参构造器
declaredConstructor.setAccessible(true);//无视私有构造器,从而通过反射来创建对象
LazyMan lazyMan = declaredConstructor.newInstance();
LazyMan lazyMan1=LazyMan.getInstance();
System.out.println(lazyMan);
System.out.println(lazyMan1);
System.out.println(lazyMan==lazyMan1);
}
main--->ooook
main--->ooook
com.javademo.JUC.LazyMan@14ae5a5
com.javademo.JUC.LazyMan@7f31245a
false
如何防止以上类型的破坏呢?因为是通过空参构造方法来创建对象,因此在空参构造方法中进行一次判断
public class LazyMan {
private LazyMan(){
synchronized (LazyMan.class){
if(lazyMan!=null){
throw new RuntimeException("不用试图用反射来破坏单例");
}
}
}
private static volatile LazyMan lazyMan;
public static LazyMan getInstance(){
if(lazyMan==null){//要事先判断,不然每次都会让线程持有锁,耗费资源,只有当对象为空的时候,才让线程持有锁
synchronized (LazyMan.class){//锁LayMan,保证这个类只有一个
if (lazyMan==null)
lazyMan=new LazyMan();
}
}
return lazyMan;
}
//反射破坏单例
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
LazyMan lazyMan1=LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor();//存在的是空参构造器,通过getDeclaredConstructor获得空参构造器
declaredConstructor.setAccessible(true);//无视私有构造器,从而通过反射来创建对象
LazyMan lazyMan = declaredConstructor.newInstance();
System.out.println(lazyMan);
System.out.println(lazyMan1);
System.out.println(lazyMan==lazyMan1);
}
}
**问题:**但是如果不首先用LazyMan lazyMan1=LazyMan.getInstance();来创建对象,而是一开始就用反射创建对象,也会破坏单例
**解决方法:**红绿灯,设标志位
反射就是获得类对象,而类对象可以操作:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NWyaqUts-1607248214313)(C:\Users\hello\AppData\Roaming\Typora\typora-user-images\image-20201117131425116.png)]
public class LazyMan {
private static Boolean flag=false;
private LazyMan(){//只能让对象创建一次,但是不管是谁创建第一次,都可以,如果反射第一次创建的依旧能破坏 因此确保不是反射创建对象
synchronized (LazyMan.class){
if(flag==false){
flag=true;
}else{
throw new RuntimeException("不用试图用反射来破坏单例");
}
}
}
private static volatile LazyMan lazyMan;
public static LazyMan getInstance(){
if(lazyMan==null){//要事先判断,不然每次都会让线程持有锁,耗费资源,只有当对象为空的时候,才让线程持有锁
synchronized (LazyMan.class){//锁LayMan,保证这个类只有一个
if (lazyMan==null)
lazyMan=new LazyMan();
}
}
return lazyMan;
}
//反射破坏单例
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor();//存在的是空参构造器,通过getDeclaredConstructor获得空参构造器
declaredConstructor.setAccessible(true);//无视私有构造器,从而通过反射来创建对象
LazyMan lazyMan = declaredConstructor.newInstance();
LazyMan lazyMan1=LazyMan.getInstance();//也会使用私有构造方法 是此处抛出的异常
System.out.println(lazyMan);
System.out.println(lazyMan1);
System.out.println(lazyMan==lazyMan1);
}
}
Exception in thread "main" java.lang.RuntimeException: 不要试图用反射来破坏单例
at com.javademo.JUC.LazyMan.<init>(LazyMan.java:13)
at com.javademo.JUC.LazyMan.getInstance(LazyMan.java:23)
at com.javademo.JUC.LazyMan.main(LazyMan.java:33)
道高一尺魔高一丈
同样的你也可以通过反射来获取flag字段,来修改其值,从而破坏单例
Field flag=LazyMan.class.getDeclaredField("flag");
//改变私有权限
flag.setAccessible(true);
flag.set(lazyMan,false);//lazyman是创建的第一个对象
枚举中的实例(枚举常量)默认为单例,
public enum EnumSingle {
singleTon;
public EnumSingle getSingleTon(){
return singleTon;
}
}
尝试使用反射来破坏枚举,其中获取的是空参构造器,出现NoSuchMethodException,而不是通过反射创建对象时抛出的Cannot reflectively create enum objects,说明要通过有参构造来尝试破坏。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xN3y6xt1-1607248214316)(C:\Users\hello\AppData\Roaming\Typora\typora-user-images\image-20200826141322519.png)]
使用jad来进行反编译,查看对应的有参构造方法,将jad放入对应的字节码文件夹下,然后执行一下代码
jad -sjava EnumSingle.class
文件下会产生一个Java文件,可以看到一下内容
private EnumSingle(String s, int i)
{
super(s, i);
}
package com.javademo.JUC;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public enum EnumSingle {
singleTon;
public EnumSingle getSingleTon(){
return singleTon;
}
}
//尝试用反射破坏单例
class TestSingle{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
System.out.println(declaredConstructor.newInstance());
System.out.println(declaredConstructor.newInstance());
}
}
结果:
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at com.javademo.JUC.TestSingle.main(EnumSingle.java:17)
分类:
按照是否加锁分为:
1.悲观锁:重量级锁,自旋锁,自适应自旋锁
2.乐观锁(采用CAS机制,compareAndSet方法):轻量级锁(每次进入和出去都需要用CAS改变标志状态),偏向锁(实现假设只会有他给一个线程执行方法,进入的时候和轻量级锁一样改变标志状态,同时记录线程的id。但是离开的时候不改变标志状态,再次进入只需比对id值如果id值相同,直接进入,不同说明已经有两个线程要使用此方法呢,偏向锁不在适用,应该升级为轻量级锁)
CAS机制:
例如i++,不是原子性操作,多线程下容易出错,以往的方法是给方法添加synchronized,进入时就加锁,可是这样开销太大,因为这其中会有大量的线程进行阻塞,唤醒,就绪等操作开销太大。于是就有了,CompareAndSet方法,例如CompareAndSet(i,k,j):k先记录i的初始值,然后j进行一些运算,最后比较i于k的值,如果相等,说明i的值没有线程对其修改,则将j值写回内存,如果发生了改变,则重新跳回,继续进行这样的工作。
虽然CompareAndSet方法,有三个步骤,但是他是一条硬件指令,故不存在原子性问题
按照是否可以插队分为:
1.公平锁:非常公平不能插队,必须先来后到
2.非公平锁:可以插队(默认为非公平锁)
**另外:**可重入锁:如果一个带锁的方法里面调用另一个方法,而这个方法也带有锁,那么拿到外面的锁之后,就可以拿到里面的锁,自动获取锁。
在IDEA的terminate中输入以下命令
1.使用jps -l定位进程号
2.使用jstack 进程号找到死锁问题