JUC详解

JUC

前言:
在Java中,线程部分是一个重点,本篇文章说的JUC也是关于线程的。JUC就是java.util .concurrent工具包的简称。这是一个处理线程的工具包,JDK 1.5开始出现的。下面一起来看看它怎么使用。

一、volatile关键字与内存可见性

1、内存可见性:
先来看看下面的一段代码:

public class TestVolatile {public static void main(String[] args){ //这个线程是用来读取flag的值的ThreadDemo threadDemo = new ThreadDemo();Thread thread = new Thread(threadDemo);thread.start();while (true){if (threadDemo.isFlag()){System.out.println("主线程读取到的flag = " + threadDemo.isFlag());break;}}}
}@Data
class ThreadDemo implements Runnable{ //这个线程是用来修改flag的值的public  boolean flag = false;@Overridepublic void run() {try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}flag = true;System.out.println("ThreadDemo线程修改后的flag = " + isFlag());}
}

这段代码很简单,就是一个ThreadDemo类继承Runnable创建一个线程。它有一个成员变量flag为false,然后重写run方法,在run方法里面将flag改为true,同时还有一条输出语句。然后就是main方法主线程去读取flag。如果flag为true,就会break掉while循环,否则就是死循环。按道理,下面那个线程将flag改为true了,主线程读取到的应该也是true,循环应该会结束。看看运行结果:

在这里插入图片描述

从图中可以看到,该程序并没有结束,也就是死循环。说明主线程读取到的flag还是false,可是另一个线程明明将flag改为true了,而且打印出来了,这是什么原因呢?这就是内存可见性问题。

  • 内存可见性问题:当多个线程操作共享数据时,彼此不可见。
    看下图理解上述代码:

在这里插入图片描述
要解决这个问题,可以加锁。如下:

while (true){synchronized (threadDemo){if (threadDemo.isFlag()){System.out.println("主线程读取到的flag = " + threadDemo.isFlag());break;}}}

加了锁,就可以让while循环每次都从主存中去读取数据,这样就能读取到true了。但是一加锁,每次只能有一个线程访问,当一个线程持有锁时,其他的就会阻塞,效率就非常低了。不想加锁,又要解决内存可见性问题,那么就可以使用volatile关键字。

2、volatile关键字:

  • 用法:
    volatile关键字:当多个线程操作共享数据时,可以保证内存中的数据可见。用这个关键字修饰共享数据,就会及时的把线程缓存中的数据刷新到主存中去,也可以理解为,就是直接操作主存中的数据。所以在不使用锁的情况下,可以使用volatile。如下:
public  volatile boolean flag = false;

这样就可以解决内存可见性问题了。

  • volatile和synchronized的区别:
    volatile不具备互斥性(当一个线程持有锁时,其他线程进不来,这就是互斥性)。
    volatile不具备原子性。

二、原子性

1、理解原子性
上面说到volatile不具备原子性,那么原子性到底是什么呢?先看如下代码:

public class TestIcon {public static void main(String[] args){AtomicDemo atomicDemo = new AtomicDemo();for (int x = 0;x < 10; x++){new Thread(atomicDemo).start();}}
}class AtomicDemo implements Runnable{private int i = 0;public int getI(){return i++;}@Overridepublic void run() {try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(getI());}
}

这段代码就是在run方法里面让i++,然后启动十个线程去访问。看看结果:
在这里插入图片描述
可以发现,出现了重复数据。明显产生了多线程安全问题,或者说原子性问题。所谓原子性就是操作不可再细分,而i++操作分为读改写三步,如下:

int temp = i;
i = i+1;
i = temp;

所以i++明显不是原子操作。上面10个线程进行i++时,内存图解如下:
在这里插入图片描述
看到这里,好像和上面的内存可见性问题一样。是不是加个volatile关键字就可以了呢?其实不是的,因为加了volatile,只是相当于所有线程都是在主存中操作数据而已,但是不具备互斥性。比如两个线程同时读取主存中的0,然后又同时自增,同时写入主存,结果还是会出现重复数据。

2、原子变量:
JDK 1.5之后,Java提供了原子变量,在java.util.concurrent.atomic包下。原子变量具备如下特点:

  • 有volatile保证内存可见性。
  • 用CAS算法保证原子性。

3、CAS算法:
CAS算法是计算机硬件对并发操作共享数据的支持,CAS包含3个操作数:

  • 内存值V
  • 预估值A
  • 更新值B

当且仅当V==A时,才会把B的值赋给V,即V = B,否则不做任何操作。就上面的i++问题,CAS算法是这样处理的:首先V是主存中的值0,然后预估值A也是0,因为此时还没有任何操作,这时V=B,所以进行自增,同时把主存中的值变为1。如果第二个线程读取到主存中的还是0也没关系,因为此时预估值已经变成1,V不等于A,所以不进行任何操作。

4、使用原子变量改进i++问题:
原子变量用法和包装类差不多,如下:

 //private int i = 0;AtomicInteger i = new AtomicInteger();public int getI(){return i.getAndIncrement();}

只改这两处即可。

三、锁分段机制

JDK 1.5之后,在java.util.concurrent包中提供了多种并发容器类来改进同步容器类的性能。其中最主要的就是ConcurrentHashMap。

1、ConcurrentHashMap:
ConcurrentHashMap就是一个线程安全的hash表。我们知道HashMap是线程不安全的,Hash Table加了锁,是线程安全的,因此它效率低。HashTable加锁就是将整个hash表锁起来,当有多个线程访问时,同一时间只能有一个线程访问,并行变成串行,因此效率低。所以JDK1.5后提供了ConcurrentHashMap,它采用了锁分段机制。

在这里插入图片描述

如上图所示,ConcurrentHashMap默认分成了16个segment,每个Segment都对应一个Hash表,且都有独立的锁。所以这样就可以每个线程访问一个Segment,就可以并行访问了,从而提高了效率。这就是锁分段。但是,java 8 又更新了,不再采用锁分段机制,也采用CAS算法了。

2、用法:
java.util.concurrent包还提供了设计用于多线程上下文中的 Collection 实现: ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、CopyOnWriteArrayList 和 CopyOnWriteArraySet。当期望许多线程访问一个给 定 collection 时,ConcurrentHashMap 通常优于同步的 HashMap, ConcurrentSkipListMap 通常优于同步的 TreeMap。当期望的读数和遍历远远 大于列表的更新数时,CopyOnWriteArrayList 优于同步的 ArrayList。下面看看部分用法:

public class TestConcurrent {public static void main(String[] args){ThreadDemo2 threadDemo2 = new ThreadDemo2();for (int i=0;i<10;i++){new Thread(threadDemo2).start();}}
}
//10个线程同时访问
class ThreadDemo2 implements Runnable{private static List<String> list = Collections.synchronizedList(new ArrayList<>());//普通做法static {list.add("aaa");list.add("bbb");list.add("ccc");}@Overridepublic void run() {Iterator<String> iterator = list.iterator();while (iterator.hasNext()){System.out.println(iterator.next());//读list.add("ddd");//写}}
}

10个线程并发访问这个集合,读取集合数据的同时再往集合中添加数据。运行这段代码会报错,并发修改异常。
在这里插入图片描述

将创建集合方式改成:

private static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

这样就不会有并发修改异常了。因为这个是写入并复制,每次生成新的,所以如果添加操作比较多的话,开销非常大,适合迭代操作比较多的时候使用。

四、闭锁

java.util.concurrent包中提供了多种并发容器类来改进同步容器的性能。ContDownLatch是一个同步辅助类,在完成某些运算时,只有其他所有线程的运算全部完成,当前运算才继续执行,这就叫闭锁。看下面代码:

public class TestCountDownLatch {public static void main(String[] args){LatchDemo ld = new LatchDemo();long start = System.currentTimeMillis();for (int i = 0;i<10;i++){new Thread(ld).start();}long end = System.currentTimeMillis();System.out.println("耗费时间为:"+(end - start)+"秒");}
}class LatchDemo implements Runnable{private CountDownLatch latch;public LatchDemo(){}@Overridepublic void run() {for (int i = 0;i<5000;i++){if (i % 2 == 0){//50000以内的偶数System.out.println(i);}}}
}

这段代码就是10个线程同时去输出5000以内的偶数,然后在主线程那里计算执行时间。其实这是计算不了那10个线程的执行时间的,因为主线程与这10个线程也是同时执行的,可能那10个线程才执行到一半,主线程就已经输出“耗费时间为x秒”这句话了。所有要想计算这10个线程执行的时间,就得让主线程先等待,等10个分线程都执行完了才能执行主线程。这就要用到闭锁。看如何使用:

public class TestCountDownLatch {public static void main(String[] args) {final CountDownLatch latch = new CountDownLatch(10);//有多少个线程这个参数就是几LatchDemo ld = new LatchDemo(latch);long start = System.currentTimeMillis();for (int i = 0; i < 10; i++) {new Thread(ld).start();}try {latch.await();//这10个线程执行完之前先等待} catch (InterruptedException e) {}long end = System.currentTimeMillis();System.out.println("耗费时间为:" + (end - start));}
}class LatchDemo implements Runnable {private CountDownLatch latch;public LatchDemo(CountDownLatch latch) {this.latch = latch;}@Overridepublic void run() {synchronized (this) {try {for (int i = 0; i < 50000; i++) {if (i % 2 == 0) {//50000以内的偶数System.out.println(i);}}} finally {latch.countDown();//每执行完一个就递减一个}}}
}

如上代码,主要就是用latch.countDown()和latch.await()实现闭锁,详细请看上面注释即可。

五、创建线程的方式 — 实现Callable接口

直接看代码:

public class TestCallable {public static void main(String[] args){CallableDemo callableDemo = new CallableDemo();//执行callable方式,需要FutureTask实现类的支持,用来接收运算结果FutureTask<Integer> result = new FutureTask<>(callableDemo);new Thread(result).start();//接收线程运算结果try {Integer sum = result.get();//当上面的线程执行完后,才会打印结果。跟闭锁一样。所有futureTask也可以用于闭锁System.out.println(sum);} catch (Exception e) {e.printStackTrace();}}
}class CallableDemo implements Callable<Integer>{@Overridepublic Integer call() throws Exception {int sum = 0;for (int i = 0;i<=100;i++){sum += i;}return sum;}
}

现在Callable接口和实现Runable接口的区别就是,Callable带泛型,其call方法有返回值。使用的时候,需要用FutureTask来接收返回值。而且它也要等到线程执行完调用get方法才会执行,也可以用于闭锁操作。

六、Lock同步锁

在JDK1.5之前,解决多线程安全问题有两种方式(sychronized隐式锁):

  • 同步代码块

  • 同步方法
    在JDK1.5之后,出现了更加灵活的方式(Lock显式锁):

  • 同步锁
    Lock需要通过lock()方法上锁,通过unlock()方法释放锁。为了保证锁能释放,所有unlock方法一般放在finally中去执行。

再来看一下卖票案例:

public class TestLock {public static void main(String[] args) {Ticket td = new Ticket();new Thread(td, "窗口1").start();new Thread(td, "窗口2").start();new Thread(td, "窗口3").start();}
}class Ticket implements Runnable {private int ticket = 100;@Overridepublic void run() {while (true) {if (ticket > 0) {try {Thread.sleep(200);} catch (Exception e) {}System.out.println(Thread.currentThread().getName() + "完成售票,余票为:" + (--ticket));}}}
}

多个线程同时操作共享数据ticket,所以会出现线程安全问题。会出现同一张票卖了好几次或者票数为负数的情况。以前用同步代码块和同步方法解决,现在看看用同步锁怎么解决。

class Ticket implements Runnable {private Lock lock = new ReentrantLock();//创建lock锁private int ticket = 100;@Overridepublic void run() {while (true) {lock.lock();//上锁try {if (ticket > 0) {try {Thread.sleep(200);} catch (Exception e) {}System.out.println(Thread.currentThread().getName() + "完成售票,余票为:" + (--ticket));}}finally {lock.unlock();//释放锁}}}
}

直接创建lock对象,然后用lock()方法上锁,最后用unlock()方法释放锁即可。

七、等待唤醒机制

1、虚假唤醒问题
生产消费模式是等待唤醒机制的一个经典案例,看下面的代码:

public class TestProductorAndconsumer {public static void main(String[] args){Clerk clerk = new Clerk();Productor productor = new Productor(clerk);Consumer consumer = new Consumer(clerk);new Thread(productor,"生产者A").start();new Thread(consumer,"消费者B").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 sell(){//卖货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){this.clerk = clerk;}@Overridepublic void run() {for (int i = 0;i<20;i++){clerk.get();}}
}
//消费者
class Consumer implements Runnable{private Clerk clerk;public Consumer(Clerk clerk){this.clerk = clerk;}@Overridepublic void run() {for (int i = 0;i<20;i++){clerk.sell();}}
}

这就是生产消费模式的案例,这里没有使用等待唤醒机制,运行结果就是即使是缺货状态,它也会不断的去消费,也会一直打印“缺货”,即使是产品已满状态,也会不断地进货。用等待唤醒机制改进:

//店员
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 sell(){//卖货if (product <= 0){System.out.println("缺货");try {this.wait();//缺货就等待} catch (InterruptedException e) {e.printStackTrace();}}else {System.out.println(Thread.currentThread().getName()+":"+ (--product));this.notifyAll();//不缺货就可以卖}}
}

这样就不会出现上述问题了。没有的时候就生产,生产满了就通知消费,消费完了再通知生产。但是这样还是有点问题,将上述代码做如下改动:

if(product >= 1){ //把原来的10改成1System.out.println("产品已满");......
public void run() {try {Thread.sleep(200);//睡0.2秒} catch (InterruptedException e) {e.printStackTrace();}for (int i = 0;i<20;i++){clerk.sell();}
}

就做这两处修改,再次运行,发现虽然结果没问题,但是程序却一直没停下来。出现这种情况是因为有一个线程在等待,而另一个线程没有执行机会了,唤醒不了这个等待的线程了,所以程序就无法结束。解决办法就是把get和sell方法里面的else去掉,不要用else包起来。但是,即使这样,如果再多加两个线程,就会出现负数了。

new Thread(productor, "生产者C").start();
new Thread(consumer, "消费者D").start();

运行结果:
在这里插入图片描述
一个消费者线程抢到执行权,发现product是0,就等待,这个时候,另一个消费者又抢到了执行权,product是0,还是等待,此时两个消费者线程在同一处等待。然后当生产者生产了一个product后,就会唤醒两个消费者,发现product是1,同时消费,结果就出现了0和-1。这就是虚假唤醒。解决办法就是把if判断改成while。如下:

 public synchronized void get() { //进货while (product >= 1) {System.out.println("产品已满");try {this.wait();//满了就等待} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + ":" + (++product));this.notifyAll();//没满就可以进货}public synchronized void sell() {//卖货while (product <= 0) {//为了避免虚假唤醒问题,wait方法应该总是在循环中使用System.out.println("缺货");try {this.wait();//缺货就等待} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + ":" + (--product));this.notifyAll();//不缺货就可以卖}

只需要把if改成while,每次都再去判断一下,就可以了。

2、用Lock锁实现等待唤醒

class Clerk {private int product = 0;//共享数据private Lock lock = new ReentrantLock();//创建锁对象private Condition condition = lock.newCondition();//获取condition实例public  void get() { //进货lock.lock();//上锁try {while (product >= 1) {System.out.println("产品已满");try {condition.await();//满了就等待} catch (InterruptedException e) {}}System.out.println(Thread.currentThread().getName() + ":" + (++product));condition.signalAll();//没满就可以进货}finally {lock.unlock();//释放锁}}public  void sell() {//卖货lock.lock();//上锁try {while (product <= 0) {System.out.println("缺货");try {condition.await();//缺货就等待} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + ":" + (--product));condition.signalAll();//不缺货就可以卖}finally {lock.unlock();//释放锁}}
}

使用lock同步锁,就不需要sychronized关键字了,需要创建lock对象和condition实例。condition的await()方法、signal()方法和signalAll()方法分别与wait()方法、notify()方法和notifyAll()方法对应。

3、线程按序交替
首先来看一道题:

编写一个程序,开启 3 个线程,这三个线程的 ID 分别为 ABC,
每个线程将自己的 ID 在屏幕上打印 10 遍,要求输出的结果必须按顺序显示。
如:ABCABCABC…… 依次递归

分析:

线程本来是抢占式进行的,要按序交替,所以必须实现线程通信,
那就要用到等待唤醒。可以使用同步方法,也可以用同步锁。

编码实现:

public class TestLoopPrint {public static void main(String[] args) {AlternationDemo ad = new AlternationDemo();new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 10; i++) {ad.loopA();}}}, "A").start();new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 10; i++) {ad.loopB();}}}, "B").start();new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 10; i++) {ad.loopC();}}}, "C").start();}
}class AlternationDemo {private int number = 1;//当前正在执行的线程的标记private Lock lock = new ReentrantLock();Condition condition1 = lock.newCondition();Condition condition2 = lock.newCondition();Condition condition3 = lock.newCondition();public void loopA() {lock.lock();try {if (number != 1) { //判断condition1.await();}System.out.println(Thread.currentThread().getName());//打印number = 2;condition2.signal();} catch (Exception e) {} finally {lock.unlock();}}public void loopB() {lock.lock();try {if (number != 2) { //判断condition2.await();}System.out.println(Thread.currentThread().getName());//打印number = 3;condition3.signal();} catch (Exception e) {} finally {lock.unlock();}}public void loopC() {lock.lock();try {if (number != 3) { //判断condition3.await();}System.out.println(Thread.currentThread().getName());//打印number = 1;condition1.signal();} catch (Exception e) {} finally {lock.unlock();}}
}

以上编码就满足需求。创建三个线程,分别调用loopA、loopB和loopC方法,这三个线程使用condition进行通信。

八、ReadWriterLock读写锁

我们在读数据的时候,可以多个线程同时读,不会出现问题,但是写数据的时候,如果多个线程同时写数据,那么到底是写入哪个线程的数据呢?所以,如果有两个线程,写写/读写需要互斥,读读不需要互斥。这个时候可以用读写锁。看例子:

public class TestReadWriterLock {public static void main(String[] args){ReadWriterLockDemo rw = new ReadWriterLockDemo();new Thread(new Runnable() {//一个线程写@Overridepublic void run() {rw.set((int)Math.random()*101);}},"write:").start();for (int i = 0;i<100;i++){//100个线程读Runnable runnable = () -> rw.get();Thread thread = new Thread(runnable);thread.start();}}
}class ReadWriterLockDemo{private int number = 0;private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();//读(可以多个线程同时操作)public void get(){readWriteLock.readLock().lock();//上锁try {System.out.println(Thread.currentThread().getName()+":"+number);}finally {readWriteLock.readLock().unlock();//释放锁}}//写(一次只能有一个线程操作)public void set(int number){readWriteLock.writeLock().lock();try {System.out.println(Thread.currentThread().getName());this.number = number;}finally {readWriteLock.writeLock().unlock();}}
}

这个就是读写锁的用法。上面的代码实现了一个线程写,一百个线程同时读的操作。

九、线程池

我们使用线程时,需要new一个,用完了又要销毁,这样频繁的创建销毁也很耗资源,所以就提供了线程池。道理和连接池差不多,连接池是为了避免频繁的创建和释放连接,所以在连接池中就有一定数量的连接,要用时从连接池拿出,用完归还给连接池。线程池也一样。线程池中有一个线程队列,里面保存着所有等待状态的线程。下面来看一下用法:

public class TestThreadPool {public static void main(String[] args) {ThreadPoolDemo tp = new ThreadPoolDemo();//1.创建线程池ExecutorService pool = Executors.newFixedThreadPool(5);//2.为线程池中的线程分配任务pool.submit(tp);//3.关闭线程池pool.shutdown();}
}class ThreadPoolDemo implements Runnable {private int i = 0;@Overridepublic void run() {while (i < 100) {System.out.println(Thread.currentThread().getName() + ":" + (i++));}}
}

线程池用法很简单,分为三步。首先用工具类Executors创建线程池,然后给线程池分配任务,最后关闭线程池就行了。

总结:

以上为本文全部内容,涉及到了JUC的大部分内容。 本人也是初次接触,如有错误,希望大佬指点一二!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/557228.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

抓包工具,知道手机app上面使用的接口是哪个

fiddler。大家可以百度上面好多选择一个安装。这里随便扔一个 在电脑上安装以后。你再配置手机上的一些设置。 首先保证手机和电脑在同一个局域网上&#xff0c;连得wifi域名前面一样的&#xff0c;在电脑的cmd输入ipconfig 然后打开手机的设置。wifi页面点开查看你连的wifi的…

munin mysql_munin 监控 mysql 2种方法

munin自带的有mysql监控功能&#xff0c;但是没有启用。试了二种方法&#xff0c;都可以监控mysql。一&#xff0c;安装munin mysql的perl扩展# yum install perl-Cache-Cache perl-IPC-ShareLite perl-DBD-MySQL二&#xff0c;为监控创建mysql用户mysql> CREATE USER munin…

使用fiddler实现手机抓包

使用fiddler实现手机抓包 手机上无法直接查看网络请求数据&#xff0c;需要使用抓包工具。Fiddler是一个免费的web调试代理&#xff0c;可以用它实现记录、查看和调试手机终端和远程服务器之间的http/https通信。 一、PC端fiddler配置 1. 安装HTTPS证书 手机上的应用很多涉及…

小米手机上安装https证书(例如pem证书,crt证书)详解

小米手机上安装https证书&#xff08;例如pem证书&#xff0c;crt证书&#xff09;关键三步&#xff1a; 1.使用第三方浏览器下载.pem 格式的文件 &#xff08;我使用的是QQ浏览器&#xff09; 2.将这个文件放入小米的 DownLoad 文件夹下 (这步也可以不做&#xff0c;只要在4…

python django图书管理系统_Python框架:Django写图书管理系统(LMS)

Django模版文件配置文件路径 test_site -- test_site -- settings.pyTEMPLATES [ { BACKEND: django.template.backends.django.DjangoTemplates, DIRS: [os.path.join(BASE_DIR, "template")], # template文件夹位置 APP_DIRS: True, OPTIONS: { context_processor…

springsecurity中session失效后怎样处理_结合Spring Security进行web应用会话安全管理

结合Spring Security进行web应用会话安全管理在本文中&#xff0c;将为大家说明如何结合Spring Security 管理web应用的会话。如果您阅读后觉得本文对您有帮助&#xff0c;期待您能关注、转发&#xff01;您的支持是我不竭的创作动力&#xff01;一、Spring Security创建使用se…

如何把数据库从sql变成mysql_如何将数据库从SQL Server迁移到MySQL

一、迁移Database Schema。首先使用Sybase Powerdesigner的逆向工程功能&#xff0c;逆向出SQL Server数据库的物理模型。具体操作是在Powerdesigner中选择“File”&#xff0c;“Reverse Engine”再选择Database&#xff0c;将DBMS选择为SQL Server&#xff0c;如图&#xff1…

linux转mysql_[转] linux下安装mysql服务器

[转自&#xff1a;http://www.extmail.org/forum/archive/2/0510/563.html]安装MySQL服务器你可以根据服务器的CPU类型&#xff0c;下载适合你所用CPU和操作系统的MySQL发行包。从下面的URL下载MySQL 4.1.16以tar.gz形式发布的二进制发行包&#xff1a;http://www.mysql.com增加…

HTTP 学习,程序员不懂网络怎么行,一篇HTTP入门 不收藏都可惜

文章目录&#x1f4e2;前言HTTP 必备干货学习&#xff0c;程序员不懂网络怎么行HTTP 协议五个特点&#xff1a;网络结构图解HTTP概述&#x1f3f3;️‍&#x1f308;基于 HTTP 的系统的组件客户端&#xff1a;用户代理网络服务器代理HTTP 的基本方面HTTP 很简单HTTP 是可扩展的…

Java面试——Redis系列总结

文章目录&#xff1a; 1.什么是Redis&#xff1f; 2.为什么要用 Redis / 为什么要用缓存&#xff1f; 3.Redis为什么这么快&#xff1f; 4.Redis都有哪些数据类型&#xff1f; 5.什么是Redis持久化&#xff1f;Redis 的持久化有哪些实现方式&#xff1f; 6.什么是Redis事…

java运行环境_Windows系统java运行环境配置 | 吴文辉博客

在进行java开发之前&#xff0c;我们最重要的步骤就是如何获取JDK版本及正确的安装、配置java环境。只有正确的安装了java运行环境&#xff0c;才能继续java的学习和实践。一、下载JDK安装1、我系统是win7 64位&#xff0c;所以我下载了jdk-8u74-windows-x64&#xff1b;下载地…

鉴权必须了解的5个知识点:cookie,session,token,jwt,单点登录

从状态说起 [HTTP 无状态] 我们知道&#xff0c;HTTP是无状态的&#xff0c;也就是说&#xff0c;HTTP请求方和响应方间无法维护状态&#xff0c;都是一次性的&#xff0c;它不知道前后的请求都发生了什么 但有的场景下&#xff0c;我们需要维护状态&#xff0c;最常见的&am…

如何实现session共享的几种解决方案?

先了解一下为什么会出现这种session共享的解决方案&#xff1f; 随着互联网公司的项目在微服务和分布式的环境下进行的搭建&#xff0c;导致一个项目可能分别部署在几个甚至很多的服务器集群下&#xff0c;此时就会出现一个问题当用户进行一个session会话的时候&#xff0c;比…

java类继承语法_java类的继承(基础)

---恢复内容开始---这篇随笔和大家讲讲java中类的继承是什么&#xff1f;希望对你们有所帮助。目录一、java继承是什么&#xff1f;二、为什么使用java继承三、java继承的实现1.1 java继承的基本语法1.2 super的用法一、Java继承是什么&#xff1f;简单来讲&#xff0c;Java中…

事务及事务隔离级别

什么是事务 事务是访问数据库的一个操作序列&#xff0c;数据库应用系统通过事务集来完成对数据库的存取。事务的正确执行使得数据库从一种状态转换为另一种状态。 事务必须服从ISO/IEC所制定的ACID原则。ACID是原子性&#xff08;atomicity&#xff09;、一致性&#xff08;…

java类加载的搜索顺序_Java类加载器加载类顺序

java ClassLoader的学习java是一门解释执行的语言&#xff0c;由开发人员编写好的java源文件先编译成字节码文件.class形式&#xff0c;然后由java虚拟机(JVM)解释执 行&#xff0c;.class字节码文件本身是平台无关的&#xff0c;但是jvm却不是&#xff0c;为了实现所谓的一次编…

HTTP 必备干货学习,一篇HTTP入门 不收藏都可惜!

文章目录&#x1f4e2;前言HTTP 必备干货学习&#xff0c;程序员不懂网络怎么行HTTP 协议五个特点&#xff1a;网络结构图解HTTP概述&#x1f3f3;️‍&#x1f308;基于 HTTP 的系统的组件客户端&#xff1a;用户代理网络服务器代理HTTP 的基本方面HTTP 很简单HTTP 是可扩展的…

在别人发来的文章上修改时,出现红色且带下划线的情况

这是因为一些比较严谨的机构将模板发过来在你修改的时候会出现特殊标记&#xff08;比如律师行业&#xff09; 这里想要直接在他的文档上进行修改&#xff0c;需要取消掉原来的修订配置 再次输入格式消失

谈Servlet与JSP

文章目录前言正文1、什么是JSP?2、什么是Servlet&#xff1f;3、JSP与Servlet的区别和联系4、扬长弊端提出MVC前言 提高Java Web 开发&#xff0c;不得不说http协议&#xff0c;接下来就说Servlet 和 Jsp 这两个java类。 正文 1、什么是JSP? JSP(Java Server Pages)是Sun…

Tomcat就是这么简单

什么是Tomcat Tomcat简单的说就是一个运行JAVA的网络服务器&#xff0c;底层是Socket的一个程序&#xff0c;它也是JSP和Serlvet的一个容器。 为什么我们需要用到Tomcat 如果你学过html&#xff0c;css&#xff0c;你会知道你写的页面只能自己访问&#xff0c;别人不能远程访…