一.idea
1.Module
- 分为project和module:比如在project-Senior里创建module-section;
-
大型项目的分布式部署
分布式部署.png
二.多线程
1.概念:程序、进程、线程
程序program:为了完成特定任务、用某种语言编写的一组指令的几何。即指一段静态的代码,静态对象。
进程process:程序的一次执行过程,或是正在运行的一个程序,是动态的,是资源分配的单位(内存)。生命周期:产生、存在、消亡。
-
线程thread:进程的进一步细化,程序内部的一条执行路径。
1.若一个进程同时并行执行多个线程,就是支持多线程的
2.线程作为调度和执行的单位,每个线程拥有独立的运行和程序计数器
每个线程各自有一套vmStack和程序计数器,而方法区和堆是属于进程,而线程共享他们.png3.一个进程的多个线程共享相同内存-》从同一堆中分配对象,可以访问相同的变量和对象,使得多个线程间通信更简便高效,但有安全隐患
单核和多核CPU的理解:
单核CPU是一种假的多线程,一个时间单元只能执行一个线程。并行与并发:
并行:多个cpu同时执行多个任务
并发:一个cpu同时执行多个任务。(其实是快速切换任务,多个线程作同一个事),秒杀。多线程的优点:
1.提高应用程序的响应
2.提高计算机系统CPU利用率
3.改善程序结构。何时需要多线程
1.程序需要同时执行两个或多个任务
2.程序需要实现一些需要等待的任务
3.需要一些后台运行程序时候
2.线程的创建和使用
2.1线程的创建方式一
- JVM允许程序运行多个线程,通过java.land.Thred类来实现。
- Tread类特性:每个线程都是通过某个特定的Tread
- 方式一:
1.创建一个基础于Thread类的子类
2.重写Thred类的run方法-》将此线程执行的操作声明在run中
3.创建Thread类的子类对象
4.通过此对象调用start方法
//1
class MyThread extends Thread{
//2
@Override
public void run() {
//线程要作的事:
for (int i=0;i<100;i++)
{
if(i%2==0)
System.out.println(i);
}
}
}
public class CreationI {
public static void main(String[] args) {
//3
MyThread t1 = new MyThread();
//4
t1.start();
//main线程
for (int i=0;i<100;i++)
{
if(i%2!=0)
System.out.println(Thread.currentThread().getName() + ":"+"***"+i);
}
}
}
//此时main的主线程和t1同时执行
- Thread类中的void start()用来1.让线程开始执行,2.JVM调用当前线程的run方法
- 问题一:如果不用start而直接调用run方法呢?
这样的话就不是多线程,就还在主线程上调用了重写的run方法。
System.out.println(Thread.currentThread().getName() + ":"+ i);
//会发现全是main线程
//main:0
//main:2
//。。。
如果改回start方法
//main:***1
//main:***3
//Thread-0:0
//Thread-0:2
- 问题二:在启动一个线程,遍历100内偶数,也就是在调用一次t1.start():java.lang.IllegalThreadStateException,由下面的源码看来threadStatus已经不是0了。需要新建一个对象才可以
//source code
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
MyThread t1 = new MyThread();
t1.start();
MyThread t2 = new MyThread();
t2.start();
//出现thread 1,thread 2,main三个线程
- 继承方式的练习:
/**
* @author : liulinzhi
* @date: 2020/06/29/20:49
* @description:
* 创建两个分线程,一个遍历100内偶数,另一个编译100以内奇数
*
*/
class MyThread0 extends Thread{
//2
@Override
public void run() {
//线程要作的事:
for (int i=0;i<100;i++)
{
if(i%2==0)
System.out.println(Thread.currentThread().getName() + ":"+ i);
}
}
}
class MyThread1 extends Thread{
//2
@Override
public void run() {
//线程要作的事:
for (int i=0;i<100;i++)
{
if(i%2!=0)
System.out.println(Thread.currentThread().getName() + ":"+ i);
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
MyThread0 t1 = new MyThread0();
//4
t1.start();
MyThread1 t2 = new MyThread1();
t2.start();
}
}
- 方法二:匿名类:(new 父类(){子类内容})
public static void main(String[] args) {
new Thread(){
@Override
public void run(){
for (int i=0;i<100;i++)
{
if(i%2!=0)
System.out.println(Thread.currentThread().getName() + ":"+ i);
}
}
}.start();
new Thread(){
@Override
public void run(){
for (int i=0;i<100;i++)
{
if(i%2==0)
System.out.println(Thread.currentThread().getName() + ":"+ i);
}
}
}.start();
在多态中,子类对象向上提升为父类类型后,对于数据域和静态的,编译和运行都是父类的,但是方法却是编译看父类有没有,没有就不通过编译,运行则是执行子类的方法
2.2Thread有关方法
package ThreadAPI;
/**
* @author : liulinzhi
* @date: 2020/06/30/8:58
* @description:THread相关方法
* 1.start():启动当前线程;调用当前线程的run方法
* 2.run():通常需要重写Thread类中的方法,将创建的线程
* 要执行的操作声明在该方法中
* 3.currentThread:静态方法,返回当前代码执行的线程
* 4.getName:获取当前线程的名字
* 5.setName:设置线程名字
* 6.yield():释放当前CPU的执行权
* 7.join:在线程A中调用B的join方法,此时线程A进入
* 阻塞状态,直到B执行完毕
* 8.sleep(long milltime):当前线程睡眠指定毫秒
* 9.isAlive():判断当前线程是否存货
*/
class MyThread extends Thread{
@Override
public void run(){
for (int i=0;i<100;i++)
{
if(i%2==0){
try {
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":"+ i);
}
// if(i%20==0)
// this.yield();
}
}
public MyThread(String name){
super(name);
}
}
public class ThreadAPI {
public static void main(String[] args) {
MyThread t1 = new MyThread("线程:一");
//构造方法改线程名
// t1.setName("线程一");
t1.start();
Thread.currentThread().setName("主线程");
for (int i=0;i<100;i++)
{
if(i%2==0)
System.out.println(Thread.currentThread().getName() + ":"+ i);
if(i==20) {
try {
t1.join();//先执行t1线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println(t1.isAlive());
}
}
- 线程优先级的设置
线程调度.png
线程优先级.png
并不是优先级高的一定在低的之前,只是概率高 - 例题:多窗口卖票
package exer;
/**
* @author : liulinzhi
* @date: 2020/06/30/9:45
* @description:创建三个窗口卖票,总票数100张
*
*/
class Window extends Thread{
private static int ticket = 100;
//公用的
@Override
public void run(){
while(true)
{
if(ticket>0)
{
System.out.println(Thread.currentThread().getName()
+ ":买票,票号尾:" + ticket);
ticket--;
}else
break;
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window t1 = new Window();
Window t2 = new Window();
Window t3 = new Window();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
虽然static公用了票,但此时还是线程不安全的,涉及到同步问题。
2.3第二种创建多线程的方式:实现Runnable接口
- 实现Runnable接口的run方法
/**
* @author : liulinzhi
* @date: 2020/06/30/10:37
* @description:第二种多线程创建方式
* 1.创建实现了Runnable接口的类
* 2.实现类实现Runnable的run方法
* 3.创建实现类的对象
* 4.将此对象作为参数传到Thread类的构造器中,创建
* Thread类对象,
* 5.通过它调用start方法
*/
class MThread implements Runnable{
@Override
public void run(){
for (int i=0;i<100;i++)
{
if(i%2==0){
System.out.println(Thread.currentThread().getName() + ":" +Thread.currentThread().getPriority() +"+:"+ i);
}
}
}}
public class CreationII {
public static void main(String[] args) {
MThread t = new MThread();
Thread t1 = new Thread(t);
t1.start();
}
}
因为Thread(Runnable target)传入实现了Runnable的类对象,造成Thread里的run方法变成了该类对象的run方法
- Runnable接口实现多窗口卖票
package exer;
/**
* @author : liulinzhi
* @date: 2020/06/30/10:57
* @description:
*/
class WindowII implements Runnable{
private int ticket = 100;
//公用的
@Override
public void run(){
while(true)
{
if(ticket>0)
{
System.out.println(Thread.currentThread().getName()
+ ":买票,票号尾:" + ticket);
ticket--;
}else
break;
}
}
}
public class WindowTestII {
public static void main(String[] args) {
WindowII t = new WindowII();
//同一个t,所谓三个线程公用ticket
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
-
两种方式的对比:
一般用Runnable方式1. 因为不能多继承有局限,所以实现接口更好;2.线程共享同一个对象的内容,比如上面的t,而继承的方式资源不共享,需要static
联系:Thread类也实现了Runnable接口,接口实现类覆盖了Runnable的run方法。
3.线程的生命周期
-
Thread类下的State类定义了线程的状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED;
线程的状态.png
状态转换.png
4.线程的同步
4.1线程安全问题
- 多个线程执行的不确定性引起执行结果的不稳定;
-
如果多个线程共享数据,会造成不安全;例如买票的例子,出现重票、错票
多窗口买票.png - 原因:当某个线程操作车票过程中,尚未完成时,其他线程参与进来,也操作车票
- 如何解决:当一个线程在操作共享数据(ticket)其他线程不能参与,直到该线程操作完毕该共享数据,采允许其他线程操作。(即使该线程过程中出现阻塞)
4.2 同步机制解决安全问题
- java最初提供了两种方法:
1.同步代码块:可以解决线程同步问题,操作同步代码时只能有一个线程参与,其他线程等待,相当于一个单线程的过程,效率低。
注意不能包含多了(可能出错,比如这里把while放进,就一直是同一个窗口了),也不能少了
示意图.png
针对实现Runnable接口的类,定义Object对象作为类的属性,
充当同步监视器(锁):
synchronized(同步监视器){
//需要被同步的代码
}
说明:操作共享数据的代码,就是需要被同步的代码。
共享数据:多个线程共同操作的数据;
同步监视器:俗称:锁;任何一个类的对象都可以充当锁。
要求:多个线程必须共用同一把锁
解决了不安全问题!
Object o = new Object();
//公用的
@Override
public void run(){
while(true)
{
synchronized(o) {
if(ticket>0)
{
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println(Thread.currentThread().getName()
+ ":买票,票号:" + ticket);
ticket--;
}else
break;
}
}}
针对继承实现Thread的创建方法:
这是因为创建的是三个类对象,上面的
Obj对象各自不同,所以需要加static共享
才能作为唯一锁
private static Object o = new Object();
//公用的
@Override
public void run(){
while(true)
{
synchronized (o){
if(ticket>0)
{
System.out.println(Thread.currentThread().getName()
+ ":买票,票号尾:" + ticket);
ticket--;
}else
break;
}}
}
这样每次创建一个锁对象,太烦。Runnable接口方式可以直接用this作为锁,继承Thread类方式可以用当前类充当(类名.class)作为锁。
2.同步方法解决安全问题:如果操作共享数据的代码完整的声明在一个方法中,我们不妨将这个方法声明同步的。
1.对Runnable接口:
private synchronized void show() {
//同步监视器就是this
if (ticket > 0) {
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println(Thread.currentThread().getName()
+ ":买票,票号:" + ticket);
ticket--;
}
}
//公用的
@Override
public void run() {
while (true) {
// synchronized(o) {
show();
if(ticket==0)
break;
}
}
}
2.继承创建线程类型:
必须加static变成类相关,否则就是this作为锁。明显三个对象不同。
private static synchronized void show() {
//不安全,锁的问题,this不同。必须static
if (ticket > 0) {
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println(Thread.currentThread().getName()
+ ":买票,票号:" + ticket);
ticket--;
}
}
//公用的
@Override
public void run() {
while (true) {
show();
if (ticket == 0)
break;
}
}
}
- 关于同步方法的总结:同步方法仍然设计到同步监视器,只是不需要显式声明。非静态的同步监视器是this,静态的同步方法,同步监视器是当前类本身。
4.3 单例模式
复习:
单例设计模式:对某个类之只能存在一个对象实例,
且该类只提供一个取得其对象实例的方法。构造函数设置为private,
此时就只能在类内部定义对象。
由类内部的静态方法来返回该对象(静态方法只能访问类中静态成员变量,
所以变量也要定义为静态的)
* 2.饿汉模式
*public class DesignMode {
* public static void main(String[] args) {
* // Bank b = new Bank();私有后不能构建
* Bank bank1 = Bank.getInstance();
* }
* }
*
* class Bank{
* private Bank(){}
* private static Bank instance = new Bank();
* public static Bank getInstance()
* {
* return instance;
* }
*
* }
*
* 3.懒汉模式
*public class DesignMode {
* public static void main(String[] args) {
* // Bank b = new Bank();私有后不能构建
* Bank bank1 = Bank.getInstance();
* }
* }
*
* class Bank{
* private Bank(){}
* //先声明类对象实例,不初始化
* //static变量随类加载,被所有对象共享
* private static Bank instance = null;
* public static Bank getInstance()
* {
* if(instance == null)
* instance = new Bank();//防止多次开堆空间
* return instance;
* }
*
* }
*
* 4.懒汉饿汉的区别:对比
* 饿汉:对象生命周期过长,线程安全
* 懒汉:延迟对象创建,目前写法线程不安全(
* 如两个线程都同时调用getInstance())——》多线程
改造饿汉模式
class Bank{
private Bank(){}
private static Bank instance = null;
public static synchronized Bank getInstance() {//Bank.class作为锁
// synchronized (Bank.class) {
if (instance == null) {
instance = new Bank();
}
return instance;
}
// }
}
效率高的方式二:
if(instance ==null)
{
synchronized (Bank.class){
if (instance == null) {
instance = new Bank();
}
}}
return instance;
}
5.线程的通信
5.1死锁问题:
- 概念:不同的线程分别占用对方的资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了死锁。出现死锁后,不会出现异常和提示,只是所有线程处于阻塞状态,无法继续。
package Lock;
/**
* @author : liulinzhi
* @date: 2020/07/01/9:28
* @description:死锁
*/
public class Lock {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
//
new Thread(){
@Override
public void run()
{
synchronized (s1)
{
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2)
{
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
//匿名对象(实现Runnable接口的匿名类+new)
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2)
{
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1)
{
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
5.2 JDK5提供Lock
- 提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁用Lock对象充当;
- java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应该先获得Lock对象。
- ReenTrantLock类实现了Lock,可以显式加锁、释放锁。
3.解决同步问题方式三:
TeentrantLock
package Lock.JDK5Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author : liulinzhi
* @date: 2020/07/01/9:53
* @description:解决线程安全问题方式三:
*1.实例化ReentrantLock对象;
* 2.调用锁 lock
* 3.解锁 unlock
*
* 对比:
* synchronized和lock异动:
* 同:二者都可以解决线程安全问题
* 不同:前者自动释放锁(执行完同步代码块),后者手动释放
* 建议优先用Lock->同步代码块(已经进入方法体,分配了资源)
* ->同步方法(在方法体外)
*
* 如何解决线程安全问题?有几种?
*/
class Window implements Runnable {
private int ticket = 100;
// turn:FIFO
private ReentrantLock lock = new ReentrantLock(true);
@Override
public void run() {
while (true) {
try{
//2.调用lock
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ ":买票,票号:" + ticket);
ticket--;
}else
break;
}finally{
//3解锁
lock.unlock();
}
}
}
}
public class JDK5Lock {
public static void main(String[] args) {
Window w = new Window();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("线程1");
t2.setName("线程2");
t3.setName("线程3");
t1.start();
t2.start();
t3.start();
}
}
5.3 练习
练习一:
package exer;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author : liulinzhi
* @date: 2020/07/01/10:33
* @description:
*/
class BankDemo implements Runnable
{
private int store = 3000;
private ReentrantLock lock = new ReentrantLock(true);
@Override
public void run() {
while(true)
{ try{
lock.lock();
if (store != 0)
System.out.println(Thread.currentThread().getName()
+ "now bank have "+ store +" store 1000;" );
else
break;
store -= 1000;
}finally{
lock.unlock();
}
}
}
}
public class BankTestI {
public static void main(String[] args) {
BankDemo b = new BankDemo();
Thread t1 = new Thread(b);
Thread t2 = new Thread(b);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
-
练习2:线程的通信
image.png
package exer;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author : liulinzhi
* @date: 2020/07/01/10:44
* @description:两个线程交替打印
* wait():当前线程进入阻塞,释放同步监视器
* notify():一旦执行该方法,唤醒被wait的一个线程,如果
* 多个线程被wait,唤醒优先级最高的
* notifyAll():唤醒所有wait线程
*
* 注意:线程同行:
* 1.只能用在同步代码块或同步方法用。
* 2.这三个方法的调用者必须是同步监视器(锁),否则报错
* 3.三个方法定义在java.lang.Object类,
* 因为任何一个类对象都可以做锁,故定义在Object类
* 才能任意对象调用这三个方法
*
*/
class Number implements Runnable
{
private int number = 1;
@Override
public void run() {
while(true)
{
synchronized (this) {
notify();//唤醒一个线程,看优先级
if (number <=100) {
System.out.println(Thread.currentThread().getName()
+ " number: " + number);
number++;
try {
wait();//调用该方法的线程阻塞,释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
} else
break;
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number b = new Number();
Thread t1 = new Thread(b);
Thread t2 = new Thread(b);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
6.sleep()和wait()异同
- 相同:一旦执行,就阻塞当前进程
-
不同:
1)两个方法声明位置不同,Tread类声明sleep,而Object类声明wait
2)调用要求不同:sleep可以任意场景调用;而wait必须在同步代码块中调用
3)wait阻塞的同时释放了同步监视器(锁)
7.生产者和消费者
-
以前都是对同一个类的共享数据用同一个同步监视器,这里对生产者消费者两个类同步也要用同一个同步监视器clerk对象;
经典问题.png
package exer;
/**
* @author : liulinzhi
* @date: 2020/07/01/11:13
* @description:生产者消费者问题 1.是否是多线程:是,生产者线程,消费者线程
* 2.线程安全问题:因为有共享数据,所以有
* 3.如何解决线程安全问题:同步机制,三种方法
* 4.线程间通信,wait notify
*/
class Clerk {
private int productCount = 0;
public synchronized void produceProduct() {
if (productCount < 20) {
System.out.println("生产者" + Thread.currentThread().getName()
+ "开始生产第" + productCount +"个产品");
productCount++;
notify();
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void consumeProduct() {
if (productCount > 0) {
System.out.println("消费者" + Thread.currentThread().getName()
+ "开始消费第" + productCount +"个产品");
productCount--;
notify();
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread {
private Clerk clerk;
@Override
public void run() {
System.out.println("生产者" + Thread.currentThread().getName() + "开始生产产品");
while (true) {
clerk.produceProduct();
}
}
public Producer(Clerk clerk) {
this.clerk = clerk;
}
}
class Concumer extends Thread {
private Clerk clerk;
@Override
public void run() {
System.out.println("消费者" + Thread.currentThread().getName() + "开始生产产品");
while (true) {
clerk.consumeProduct();
}
}
public Concumer(Clerk clerk) {
this.clerk = clerk;
}
}
public class ProductTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();//锁唯一
Producer p1 = new Producer(clerk);
p1.setName("生产者");
Concumer c1 = new Concumer(clerk);
c1.setName("消费者");
p1.start();
c1.start();
}
}
8.创建多线程方式三:Callable接口(JDK5)
- 比起Runnable接口更强大:
1)相比run方法,可以有返回值;
2)方法可以抛出异常;
3)支持泛型的返回值;
4)需要借助Future Task类,比如获取返回结果,该类实现了Runnable和Callable接口,也是Future的唯一实现类
package CreationI;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @author : liulinzhi
* @date: 2020/07/01/12:06
* @description:Callable接口创建线程
* 1.创建Callable接口实现类
* 2.实现call方法,线程要做的操作,可以有返回值
* 3.创建Callable接口实现类的对象
* 4.将该对象作为参数传入FutureTask构造器创建对象
* 5.Thread构造器传入FT对象,调用start方法
* 6.可以使用FT对象.get得到call()返回值
*
*
* 理解Callable接口方式创建多线程比Runnable接口创建更强大?
* 1.前者可以有返回值
* 2.前者可以抛出异常,可以catch
* 3.前者支持泛型
* 4.
*/
class NumThread implements Callable {
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
sum += i;
}
}
return sum;
}
}
public class CreationIII {
public static void main(String[] args) {
NumThread n = new NumThread();
FutureTask f = new FutureTask(n);//又封装一层
new Thread(f).start();
//因为FurueTask实现了Runnable接口
try {
Object sum = f.get();//返回值就是FutureTask
//构造器参数Callable实现类重写的call方法
System.out.println(sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
9.创建多线程方式四:线程池(JDK5)
- 提前创建好多个线程,放入线程池,使用时直接获取,使用完放回池中。可以频繁避免创建销毁、实现重复利用。
- 好处:提高响应速度(减少了创建新线程的时间)、降低资源消耗(重复利用线程池的线程)、便于线程管理
-
corePoolSize:核心池的大小;maximumPoolSize:最大线程数;keepAliveTime:线程没任务时最多保持多长时间后会终止。
image.png
package CreationI;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
/**
* @author : liulinzhi
* @date: 2020/07/01/14:25
* @description:线程池创建线程
*
*/
class Number implements Runnable{
@Override
public void run(){
int sum = 0;
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
sum += i;
}
}
}
}
public class CreationIV {
public static void main(String[] args) {
//1.提供指定线程池数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//设置线程池的属性,在实现类ThreadPoolExtcutor里
ThreadPoolExecutor service1 = (ThreadPoolExecutor)service;
// service1.setCorePoolSize();
// service1.setKeepAliveTime();
// service1.setMaximumPoolSize();
//2.执行指定线程的操作,需要提供实现Runnbable或Callable
//接口的实现类对象
service.execute(new Number());//适合Runnable
// service.submit();//适合Callable
//3.关闭线程池
service.shutdown();
}
}
三.多线程高级juc
1.volatile关键字和内存可见性
- 提出问题:flag不同步。JVM对两个线程创建两个独立的缓存,线程1写回主存后,main线程还是false,没有同主存中读过来——内存可见性问题:多个线程操作共享数据时,彼此不可见。
image.png
package JUC;
/**
* @author : liulinzhi
* @date: 2020/07/01/14:54
* @description:volatile关键字
*/
class Number 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(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println("flag=" + isFlag());
}
}
public class VolatileDemo {
public static void main(String[] args) {
Number n = new Number();
new Thread(n).start();
//这里卡在flag=true,为什么分线程flag是
//true而主线程一直循环?
/*
Jvm对每个线程都分配一个独立的缓存用于提高效率;
*/
while (true) {
if (n.isFlag()) {
System.out.println("---------");
break;
}
}
}
}
- volatile关键字修饰共享数据:该关键字保证多个线程操作该共享数据时候,内存中数据是可见的,时刻刷新缓存数据到主存,可以近似看成直接对主存进行操作,比锁效率高
- 相较于synchronized是一种较为轻量级的同步策略;
- 注意:
1)volatile不具备互斥性
2)不能保证变量的”原子性“
2.变量的原子性和CAS算法
-
下面图中线程一的V=A,故把B更新主存,num=1,此时如果线程2的V读的早于这一更新操作,那么V=0,而这时更新后A=1,不一致,就不做任何操作
image.png
image.png
package JUC;
import java.util.TreeMap;
/**
* @author : liulinzhi
* @date: 2020/07/01/15:18
* @description:原子性问题
* 1.i++的原子性问题:读-改-写三部操作
* j = i++本质上在底层 int temp = i; i = i+1; j =temp;
*
* 解决方式:原子变量:java.util.concurrent.atomic包
* 提供了大量常用的原子变量:
* 1.有volatile特性,里面都用这个修饰,保证的内存可见性
* 2.CAS(compare and swap)算法保证数据的原子性:
* 他是硬件对并发操作共享数据的支持,底层的支持:
* 包含了三个操作数:
* 内存值V
* 预估值A
* 更新值B
* 当前仅当V==A时,V=B,否则不做任何操作
*/
class Atomic implements Runnable{
private int num = 0;
public int getNum(){
return num++;
}
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ " :" + getNum());
}
}
public class AtomicDemo {
public static void main(String[] args) {
Atomic a1 = new Atomic();
for (int i = 10; i>0; i--)
new Thread(a1).start();
}
}
- CAS不成功的时候,线程不会放弃更新权,会再次进行CAS,比同步锁效率高。
private AtomicInteger num = new AtomicInteger(0);
public int getNum(){
return num.getAndIncrement();
}
- volatile和synchronized:
1、Volatile轻量级的,只能修饰变量。
synchronize重量级的,还可以修饰方法
2、Volatile只保证数据的可见性,不能用来同步,
因为多线程访问Volatile变量不会阻塞
3、synchronize不仅保证可见性,而且保证原子性,因为自由获得了锁的线程才能到达临界区,
从而保证了临界区中的所有语句被执行,多个线程抢夺synchronize锁的时候,会出现阻塞。