Java多线程
进程
程序是静止的,运行中的程序就是进程
进程的三个特征:
1:动态性:进程是运行中的程序,要动态的占用内存,CPU和网络等资源
2:独立性:进程与进程之间是相互独立的,彼此有自己的独立内存区域
3:并发性:假如CPU是单核,同时刻其实内存只有一个进程在执行,CPU会分时轮询切换以此为每个进程服务
线程
一个进程可以有多个线程,线程是进程中的一个独立执行单元,创建开销相对进程较小,同时支持并发
线程的作用
1:可以提高程序的效率,线程也支持并发,可以有更多机会得到CPU
2:多线程可以解决很多业务模型
3:大型高并发技术的核心技术
4:涉及到多线程的开发可能都比较难理解
Thread类的API:
1:public void setName(String name):给当前线程取名字
2:public void getName():获取当前线程名字
-- 线程存在默认名称,子线程的默认名称是:Thread-索引
-- 主线程的默认名称是:main
3:public static Thread currentThread()
-- 获取当前线程对象,这个代码在哪个线程中,就得到哪个线程对象
4:public static void sleep(long time):让当前线程休眠多少毫秒再继续执行
线程的创建方式
1:直接定义一个类继承线程类Thread,重写run()方法,创建线程对象;调用线程对象的start()方法启动线程
2:定义线程任务类实现Runnable接口,重写run()方法,创建线程任务对象,把线程任务对象包装成线程对象,调用start()方法启动线程
3:实现Callable接口
直接定义一个类继承线程类Thread
/* 优点:编码简单 缺点:线程类已经继承了Thread类无法继承其它类了,功能不能通过继承拓展(单继承) */ public class ThreadDemo { // 启动后的ThreadDemo当成一个进程 // main方法是由主线程执行的,理解成main方法是一个主线程 public static void main(String[] args) { // 3.创建一个线程对象 Thread t = new MyThread(); // 4.调用线程对象的start()方法启动线程 t.start(); for (int i = 0; i < 100; i++) { System.out.println("main线程输出:" + i); } } } // 1.定义一个线程类继承Thread类 class MyThread extends Thread { // 2.重写run方法 @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("子线程输出:" + i); } } }
定义线程任务类实现Runnable接口
/*
缺点:代码复杂一点;不能直接得到线程执行的结果
优点:线程任务类只是实现了Runnable接口,可以继承其它类,而且可以继续实现其它接口(避免了单继承)
同一个线程任务对象可以被包装成多个线程对象
适合多个线程去共享同一个资源
实现解耦操作,代码可以被多个线程共享,线程任务代码和线程独立
线程池可以放入实现Runable或Callable线程任务对象
*/
public class ThreadDemo { public static void main(String[] args) { // 3.创建一个线程任务对象(注意:线程任务对象不是线程对象,只是执行线程任务的) Runnable target = new MyRunnable(); // 4.把线程任务对象包装成线程对象,且可以指定线程名称 Thread t = new Thread(target, "1号线程"); // 5.调用线程对象的start()方法启动线程 t.start(); for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+"==>" + i); } } } // 1.创建一个线程任务类实现Runnable接口 class MyRunnable implements Runnable { // 2.重写run()方法 @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+"==>" + i); } } }
实现Callable接口
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask;
/*
缺点:编码复杂
优点:线程任务类只是实现了Callable接口,可以继承其它类,而且可以继续实现其它接口(避免了单继承)
同一个线程任务对象可以被包装成多个线程对象
适合多个线程去共享同一个资源
实现解耦操作,代码可以被多个线程共享,线程任务代码和线程独立
线程池可以放入实现Runable或Callable线程任务对象
能直接得到线程执行的结果
*/
public class ThreadDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { // 3.创建一个Callable的线程任务对象 Callable call = new MyCallable(); // 4.把Callable任务对象包装成一个未来任务对象 FutureTask<String> task = new FutureTask<>(call); // 5.把未来任务对象包装成线程对象 Thread t = new Thread(task); // 6.启动线程对象 t.start(); for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+"=>" + i); } // 最后获取线程执行的结果,如果线程没有结果,让出CPU等待线程执行完再来取结果 String rs = task.get(); // 获取call方法返回的结果 System.out.println(rs); } } // 1.创建一个线程任务类实现Callable接口,申明线程返回的结果类型 class MyCallable implements Callable<String> { // 2.重写线程线程任务类的call方法 @Override public String call() throws Exception { int sum = 0; for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+"=>" + i); sum += i; } return Thread.currentThread().getName()+"执行的结果是:" + sum; } }
线程同步的方式
1:同步代码块
2:同步方法
3:lock显示锁
同步代码块
格式:synchronized(锁对象) {
// 访问共享资源的核心代码
}
锁对象理论上可以是任意的“唯一”对象;原则上建议使用共享资源,实例方法中建议用this,静态方法中建议用类名.class字节码作锁对象import lombok.Data;
@Data public class Account { private String cardId; private double money; public Account(String s, int i) { this.cardId = s; this.money = i; } // 同步代码块
// 同步方法——等价于同步代码快——public synchronized void drawMoney(double money){} public void drawMoney(double money) { // 1.先判断是谁来取钱 String name = Thread.currentThread().getName(); // 2.判断余额是否足够 synchronized (this) { if (this.money >= money) { System.out.println(name + "来取钱,余额足够,吐出" + money); this.money -= money; System.out.println("还有多少钱:" + this.money); } else { System.out.println("没钱"); } } }
} public class DrawThread extends Thread{ // 定义一个成员变量接收账户对象 private Account acc; public DrawThread(Account acc, String name) { super(name); this.acc = acc; } @Override public void run() { acc.drawMoney(100000); } } public class ThreadSafe { public static void main(String[] args) { // 1.创建一个共享账户资源 Account acc = new Account("ICBC-110", 100000); // 2.创建2个线程对象去账户中取钱 Thread littleMing = new DrawThread(acc, "小明"); littleMing.start(); Thread littleRed = new DrawThread(acc, "小红"); littleRed.start(); } }
Lock显示锁
Lock锁也称同步锁,比上述同步方法功能更强大,它将加锁与释放锁方法化,如下:
- public void lock():加同步锁
- public void unlock():释放同步锁
lock.lock(); try { if (this.money >= money) { System.out.println(name + "来取钱,余额足够,吐出" + money); this.money -= money; System.out.println("还有多少钱:" + this.money); } else { System.out.println("没钱"); } }catch (Exception e) { e.printStackTrace(); }finally { lock.unlock(); }
线程同步总结
线程安全,性能差
线程不安全,性能好,假如开发中不存在多线程安全问题,建议使用线程不安全的设计类
线程通信
线程通信一定是多个线程在操作同一个资源才需要进行通信;线程通信必须保证安全
线程通信的Object的核心方法
public void wait():让当前线程进入到等待状态,此方法必须对象调用
public void notify():唤醒当前锁对象等待状态的某个线程,此方法必须锁对象调用
public void notifyAll():唤醒当前锁对象等待状态的全部线程,此方法必须锁对象调用
线程状态
线程池
线程池在Java中的代表类:ExecutorService(接口)
线程池的方法
public static ExecutorService newFixedThreadPool(int nThreads):创建一个线程池返回
Future<?> submit(Runnable task):提交一个Runnable的任务对象给线程池执行
Future<?> submit(Callable task):提交一个Callable的任务对象给线程池执行
ExecutorService.shutdown():等待任务执行完毕以后才会关闭线程池
ExecutorService.shutdownNow():立即关闭线程池的代码,无论任务是否执行完毕
public class ThreadPoolsDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { // 1.创建一个线程池,指定线程池数量为3 ExecutorService pools = Executors.newFixedThreadPool(3); // 2. 添加线程任务让线程池处理 Runnable target = new MyRunnablel(); pools.submit(target);// 第一次提交,创建新线程,自动触发执行 pools.submit(target);// 第二次提交,创建新线程,自动触发执行 pools.submit(target);// 第三次提交,创建新线程,自动触发执行 pools.submit(target);// 第四次提交,复用之前线程 pools.shutdown(); ExecutorService pools2 = Executors.newFixedThreadPool(3); // 提交callable的任务对象后返回一个未来任务对象 Future<String> t1 = pools2.submit(new MyCallable(100)); Future<String> t2 = pools2.submit(new MyCallable(200)); Future<String> t3 = pools2.submit(new MyCallable(300)); Future<String> t4 = pools2.submit(new MyCallable(400)); // 获取线程池执行的r任务的结果 String rs1 = t1.get(); String rs2 = t2.get(); String rs3 = t3.get(); String rs4 = t4.get(); System.out.println("rs1:" + rs1); System.out.println("rs2:" + rs2); System.out.println("rs3:" + rs3); System.out.println("rs4:" + rs4); } } class MyRunnablel implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); } } } class MyCallable implements Callable<String> { // 需求,使用线程池,计算出1-100,1-200,1-300的和并返回 private int n; MyCallable(int n) { this.n = n; } @Override public String call() { int sum = 0; for (int i = 0; i <= n; i++) { sum+=i; } return Thread.currentThread().getName() + ":" + sum; } }
死锁
多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放,即造成死锁
变量不可见性内存定义
Java内存模型(JMM)描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这样的底层细节
JMM有以下规定
1. 所有的共享变量都存储于主内存。这里所说的变量指的是实例变量和类变量,不包含局部变量,因为局部变量是线程私有的,因此不存在竞争关系
2. 每一个线程还存在自己的工作内存,线程的工作内存,保留了被线程使用的变量的工作副本
3. 线程对变量的所有的操作(读,取)都必须在工作内存中完成,而不能直接读写主内存中的变量
4. 不同线程之间也不能直接访问对方工作内存中的变量,线程间变量的值的传递需要通过主内存中转来完成
并发编程下变量不可见性解决方案
1. 加锁
2.对共享的变量进行volatile关键字修饰
1.加锁——线程进入synchronized代码块前后,执行过程如下
1. 线程获得锁
2. 清空工作内容
3.从主内存拷贝共享变量最新的值到工作内存成为副本
4.执行代码
5.将修改后的副本的值刷新回主内存中
6.线程释放锁
2.volatile关键词修饰
代码如下:
/** * 目标:并发编程下,多线程访问变量的不可见性问题 */ public class volatileDemo extends Thread{ private volatile boolean flag = false; @Override public void run() { try { Thread.sleep(1); } catch (Exception e) { e.printStackTrace(); } // 线程中修改变量 flag = true; } public boolean isFlag() { return flag; } public void setFlag(boolean flag) { this.flag = flag; } } class VisibilityDemo{ public static void main(String[] args) { // 1.启动子线程,修改flag的变量为true volatileDemo t1 = new volatileDemo(); t1.start(); // // 2.主线程 // while (true) { // synchronized (VisibilityDemo.class) { // if (t1.isFlag()) { // System.out.println("主线程进入执行~~~~~~"); // } // } // } // 2.主线程 while (true) { if (t1.isFlag()) { System.out.println("主线程进入执行~~~~~~"); } } } }
原子性
原子性是指在一次或多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行
volatile只能保证线程间变量的可见性,不能保证变量操作的原子性
保证原子性操作方案
1. 加锁——悲观锁
2. 使用原子类——AtomicInteger——乐观锁
原子类的方法
public AtomicInteger():初始化一个默认值为0的原子型Integer
public AtomicInteger(int initialValue):初始化一个指定值的原子型Integer
int get():获取值
int getAndIncrement():以原子方式将当前值加1,这里返回的是自增前的值
int incrementAndGet():以原子方式将当前值加1,这里返回的是自增后的值
int addAndGet(int data):以原子方式将输入的值与实例或在哪个的值(AtomicInteger里的value)相加,并返回结果
int getAndSet(int value):以原子方式设置为newValue的值,并返回旧值
public class volatileAtomicDemo { public static void main(String[] args) { Runnable target = new MyRunnable(); for(int i = 0; i < 100; i++) { new Thread(target).start(); } } } class MyRunnable implements Runnable { private AtomicInteger atomicInteger = new AtomicInteger(); @Override public void run() { synchronized (this) { for (int i = 0; i < 100; i++) { System.out.println("count==========>>>" + atomicInteger.incrementAndGet()); } } } }
原子类实现CAS机制实现线程安全
CAS全称:Compare And Swap是现代CPU广泛支持的一种对内存中的共享数据进行操作的一种特殊指令。CAS可以将read-modify-check-write转换为原子操作,这个原子操作直接由处理器保证。
乐观锁与悲观锁区别
并发包
对比HashMap、HashTable、ConcurrentHashMap
public class ConcurrentHashMapDemo { // hashMap在高并发下不安全 // public static Map<String, String> maps = new HashMap<>(); // 高并发下Hashtable可替代HashMap,但效率低,每步操作采用悲观锁 // public static Map<String, String> maps = new Hashtable<>(); // 采用高并发包ConcurrentHashMap,只采用局部锁,只对当前元素上锁,相对Hashtable效率更高, public static Map<String, String> maps = new ConcurrentHashMap<>(); public static void main(String[] arts) { Runnable target = new MyRunnable(); Thread t1 = new Thread(target, "线程1"); Thread t2 = new Thread(target, "线程2"); t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (Exception e) { e.printStackTrace(); } System.out.println("元素个数:" + maps.size()); } } class MyRunnable implements Runnable { @Override public void run() { long start = System.currentTimeMillis(); for (int i = 1; i <= 500000; i++) { ConcurrentHashMapDemo.maps.put(Thread.currentThread().getName() + i, Thread.currentThread().getName() + i); } long end = System.currentTimeMillis(); System.out.println(Thread.currentThread().getName() + "消耗时间:" + (end - start)/1000.0 + "s"); } }
CountDownLatch——与Thread.join作用相似
CyclicBarrier
作用:某个线程必须等待其他线程执行完毕以后才能最终触发自己执行
Semaphore
作用:主要是控制线程的并发数量
Exchanger
作用:用于进行线程间的数据交换