Java多线程学习笔记
多线程(Thread)
高可用 高性能 高并发
线程简介
- 方法间调用:普通方法调用,从哪里来到那里去,闭合的一条路径
- 多线程使用:开辟了多条路径
Process与Thread
区别 | 进程 | 线程 |
---|---|---|
根本区别 | 作为资源分配的单位 | 调度和执行的单位 |
开销 | 每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销。 | 线程可以看成是轻量级的进程,同一类线程共享代码和数据空间,每个线程有独立地运行和栈程序计数器(PC),线程切换的开销小。 |
所处环境 | 在操作系统中能同时运行多个任务(程序) | 在同一应用程序中有多个顺序流同时执行 |
分配内存 | 系统在运行的时候会为每个进程分配不同的内存区域 | 除了CPU之外,不会为线程分配内存(线程所使用的资源是它所属的进程的资源),线程组只能共享 |
包含关系 | 没有线程的进程是可以被看做单线程的,如果一个进程内拥有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的。 | 线程是进程的一部分,所以线程有的时候被称为是轻权进程或者轻量级进程。 |
注意:很多多线程是模拟出来的,真正的多线程是指由多个cpu,即多核,如服务器。如果是模拟出来的多线程,即一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。
核心概念
-
线程就是独立的执行路径
-
在程序运行时,即使没有自己创建线程,后台也会存在多个线程,如gc线程,主线程。
-
main()称之为主线程,为系统的入口点,用于执行整个程序。
-
在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为的干预的。
-
对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
-
线程会带来额外的开销,如cpu调度时间,并发控制开销
-
每个线程在自己的工作内存交互,加载和存储主内存控制不当会造成数据不一致。
线程实现
线程创建三种方式
线程的创建Thread
package com.junwei.thread;
/**
* @Auther: wangjunwei
* @Description: Thread多线程 线程的创建
* 1、创建:继承Thread 重写 Run
* 2、启动:创建子类对象 start
* @Date: Created in 10:03 2018/12/26
*/
public class StartThread extends Thread {
/**
* @Author: wangjunwei
* @Date: Created in 10:05 2018/12/26
* @Description: 重写run方法
*/
@Override
public void run() {
for (int i = 0; i < 30; i++) {
System.out.println("听歌!");
}
}
public static void main(String[] args) {
//创建子类对象
StartThread s1 = new StartThread();
//开启线程 start不保证立即运行 由cpu进行安排 run普通调用
s1.start();
for (int i = 0; i < 10; i++) {
System.out.println("coding!");
}
}
}
多线程下载图片
package com.junwei.thread;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
/**
* @Auther: wangjunwei
* @Description: 网络图片下载
* 图片下载工具类
* @Date: Created in 10:36 2018/12/26
*/
public class WebDownLoader01 {
/**
* @param url 1
* @param localPath 2
* @return : void
* @Author: wangjunwei
* @Date: Created in 10:44 2018/12/26
* @Description: 下载图片
*/
protected void downLoad(String url, String localPath) {
try {
FileUtils.copyURLToFile(new URL(url), new File(localPath));
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
package com.junwei.thread;
import lombok.*;
/**
* @Auther: wangjunwei
* @Description: 开启多线程下载图片
* @Date: Created in 10:47 2018/12/26
*/
@EqualsAndHashCode(callSuper = true)
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Data
public class TDownLoader01 extends Thread {
/**
* 图片url
*/
private String url;
/**
* 图片存贮路径及文件名
*/
private String localPath;
/**
* @return : void
* @Author: wangjunwei
* @Date: Created in 10:49 2018/12/26
* @Description: 重写run方法
*/
@Override
public void run() {
//实例化下载工具对象
WebDownLoader01 wdl = new WebDownLoader01();
wdl.downLoad(url,localPath);
System.out.println(localPath);
}
/**
* @param args 1
* @return : void
* @Author: wangjunwei
* @Date: Created in 10:52 2018/12/26
* @Description: 主函数
*/
public static void main(String[] args) {
//实例化线程对象
TDownLoader01 td1 = new TDownLoader01("url","C:\\Users\\86241\\Desktop\\IOTest(2)/1.jpg");
TDownLoader01 td2 = new TDownLoader01("url","C:\\Users\\86241\\Desktop\\IOTest(2)/2.jpg");
TDownLoader01 td3 = new TDownLoader01("url","C:\\Users\\86241\\Desktop\\IOTest(2)/3.jpg");
td1.start();
td2.start();
td3.start();
}
}
Runnable
package com.junwei.thread;
/**
* @Auther: wangjunwei
* @Description: 创建线程方式二:
* 1、创建:实现Runnable + 重写run
* 2、启动:创新实现类对象+ Threadl类start
*
* 推荐方式:
* 避免单继承的局限性,优先使用接口
* 方便资源共享
* @Date: Created in 12:22 2018/12/26
*/
public class StartRun implements Runnable {
/**
* @Author: wangjunwei
* @Date: Created in 12:24 2018/12/26
* @Description: 重写run方法
*/
public void run() {
System.out.println("听歌!");
}
public static void main(String[] args) {
/*//创建实现类对象
StartRun st = new StartRun();
//创建代理类对象
Thread t = new Thread(st);
//启动
t.start();//开启线程*/
new Thread(new StartRun()).start();
System.out.println("coding!");
}
}
- 实现接口Runnable具有多线程能力
- 启动线程:传入目标对象+Thread对象.start()
- 推荐使用:oop多实现,灵活方便,同一份对象的代理
package com.junwei.thread;
/**
* @Auther: wangjunwei
* @Description: 模拟龟兔赛跑
* @Date: Created in 12:47 2018/12/26
*/
public class Racer implements Runnable {
/**胜利者*/
private String winner;
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->" + i++);
boolean flag = gameOver(i);
if (flag) {
break;
}
}
}
private boolean gameOver(int steps) {
if (winner != null) {
return true;
} else {
if (steps == 100) {
winner = Thread.currentThread().getName();
System.out.println("winner==>" + winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
Racer racer = new Racer();
new Thread(racer, "乌龟").start();
new Thread(racer, "兔子").start();
}
}
实现Callable
class CDownLoader implements Callable{
private String url;
private String fname;
public CDownLoader(String url, String fname){
this.url=url;
this.fname=fname;
}
public Object call()throws Exception{
WebDownLoader downLoader = new WebDownLoader();
downLoader.downLoad(url, fname);
return true;
}
}
- 创建目标对象:
- 创建执行服务:
- 提交执行:
- 获取结果:
- 关闭服务:
package com.junwei.thread;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.concurrent.*;
/**
* @Auther: wangjunwei
* @Description:
* @Date: Created in 14:01 2018/12/26
*/
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class CallAbleTest implements Callable<Boolean> {
private String url;
private String name;
@Override
public Boolean call() throws Exception {
WebDownLoader01 wd = new WebDownLoader01();
wd.downLoad(url,name);
System.out.println(name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
CallAbleTest cd1 = new CallAbleTest();
CallAbleTest cd2 = new CallAbleTest();
CallAbleTest cd3 = new CallAbleTest();
//创建执行服务 线程池
ExecutorService ser = Executors.newFixedThreadPool(3);
//提交执行结果
Future<Boolean> submit1 = ser.submit(cd1);
Future<Boolean> submit2 = ser.submit(cd2);
Future<Boolean> submit3 = ser.submit(cd3);
//获取结果
Boolean r1 = submit1.get();
Boolean r2 = submit2.get();
Boolean r3 = submit3.get();
//关闭服务
ser.shutdownNow();
}
}
静态代理
package com.junwei.thread;
import lombok.AllArgsConstructor;
/**
* @Auther: wangjunwei
* @Description: 静态代理
* 接口:
* 1、真实角色
* 2、代理角色
* @Date: Created in 14:36 2018/12/26
*/
public class StaticProxy {
public static void main(String[] args) {
new WeddingCompany(new You()).happyMarry();
/*new Thread(线程对象).start()*/
}
}
interface Marry{
void happyMarry();
}
/**
* @Author: wangjunwei
* @Date: Created in 14:38 2018/12/26
* @Description: 真实角色
*/
class You implements Marry{
@Override
public void happyMarry() {
System.out.println("新婚快乐!");
}
}
/**
* @Author: wangjunwei
* @Date: Created in 14:39 2018/12/26
* @Description: 代理角色
*/
@AllArgsConstructor
class WeddingCompany implements Marry{
//真实角色
private Marry target;
@Override
public void happyMarry() {
ready();
this.target.happyMarry();
after();
}
private void after() {
System.out.println("婚礼完成!");
}
private void ready() {
System.out.println("布置婚礼!");
}
}
线程状态
线程方法
- sleep()
- 使线程停止运行一段时间,将处于阻塞状态
- 如果调用了sleep方法之后,没有其他等待执行的线程,这个时候当前线程不会马上恢复执行!
- join()
- 阻塞指定线程等到另一个线程完成以后再继续执行
- yield()
- 让当前正在执行线程暂停,不是阻塞线程,而是将线程转入就绪状态
- 调用了yield方法之后,如果没有其他等待执行的线程,此时当前线程就会马上恢复执行!
- setDaemon()
- 可以将指定的线程设置成后台线程,守护线程
- 创建用户线程的线程结束时,后台线程也随之消亡
- 只能在线程启动之前把它设为后台线程
- setPriority(int newPriority) getPriority()
- 线程的优先级代表的是概率
- 范围从1到10,默认为5
- stop()停止线程
- 不推荐使用
多线程的终止
- 不使用JDK提供的stop()/destrory()方法(它们本身也被JDK废弃了)
- 提供一个boolean类型的终止变量,当这个变量置为false,则终止线程的运行
线程的暂停
- sleep(时间)制定当前线程阻塞的毫秒数
- sleep存在异常InterruptedException
- sleep时间达到后线程进入就绪状态
- sleep可以模拟网络延时、倒计时等
- 每一个对象都有一个所,sleep不会释放锁
线程的礼让
- 礼让线程,让当前正在执行线程暂停
- 不是阻塞线程,而是将线程从运行状态转入就绪状态
- 让cpu调度器重新调度
join插队线程
- join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
多线程优先级
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程。线程调度器按照线程的优先级决定应调度哪个线程来执行。
线程的优先级用数字表示,从范围1到10
- Thread.MIN_PRIOPITY = 1
- Thread.MAX_PRIOPITY = 10
- Thread.NORM_PRIORITY = 5
使用下述方法获得或设置线程对象的优先级。
- int getPriority();
- void setPriority(int newPriority);
优先级的设定建议在start()调用前
注意:优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高后调用优先级低的线程。
守护线程
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 如后台记录操作日志、监控内存使用等。
new Thread(god).setDaemon(true);//将用户线程调整为守护线程
常用其他方法
方法 | 功能 |
---|---|
isAlive() | 判断线程是否还活着,即线程是否还未终止 |
setName() | 给线程起一个名字 |
getName() | 获取线程的名字 |
currentThread() | 取得当前正在运行的线程对象,也就是获取自己本身 |
线程同步(synchronized)并发控制
并发:
同一个对象多个线程同时操作
处理多线程问题时,多个线程访问同一对象,并且某些线程还想修改这个对象。这时候,我们就需要用到“线程同步”。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕后,下一个线程再使用。
线程同步
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突的问题。为了保证数据在方法中被访问时的正确性,在访问时加入 锁机制(synchronized),当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。存在以下问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起
- 在多线程竞争下,枷锁、释放锁会导致比较多的上下文切换和调度延时,因其性能问题。
- 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题。
由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法:synchronized方法和synchronized块。
-
同步方法
public synchronized void method(int args){}
synchronized方法控制对“成员变量|类变量”对象的访问:每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。
缺陷:若将一个大的方法声明为synchronized将会大大影响效率。
- 同步块:synchronized(obj){},obj称之为同步监视器
- obj可以是任何对象,但是推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器是this即该对象本身,或class即类的模子
package com.junwei.syn;
/**
* @Auther: wangjunwei
* @Description: 线程安全:在并发时保证数据的正确性,效率尽可能高
* synchronized
* 1、同步方法
* 2、同步块
* @Date: Created in 19:02 2018/12/26
*/
public class SynTest01 {
public static void main(String[] args) {
//一份资源
SafeWeb12306 web = new SafeWeb12306();
//多个代理
new Thread(web, "黄牛1").start();
new Thread(web, "黄牛2").start();
new Thread(web, "黄牛3").start();
}
}
class SafeWeb12306 implements Runnable {
/*总票数*/
private int ticketNums = 99;
private boolean flag = true;
/**
* @Author: wangjunwei
* @Date: Created in 12:42 2018/12/26
* @Description: 重写run方法
*/
@Override
public void run() {
while (flag) {
test();
}
}
/**
* @Author: wangjunwei
* @Date: Created in 19:12 2018/12/26
* @Description: 线程安全
*/
public synchronized void test() {
if (ticketNums <= 0) {
flag = false;
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
/*获取当前线程名称 并打印剩余票数*/
System.out.println(Thread.currentThread().getName() + "-->" + ticketNums--);
}
}
- 同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其中代码
- 第二个线程访问,发现同步监视器被锁定,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器未锁,锁定并访问
package com.junwei.syn;
import java.util.ArrayList;
import java.util.List;
/**
* @Auther: wangjunwei
* @Description: 操作容器
* @Date: Created in 19:36 2018/12/26
*/
public class SynBlockTest {
public static void main(String[] args) throws InterruptedException {
final List<String> list = new ArrayList<String>();
for (int i=0;i<10000;i++){
new Thread(()->{
synchronized (list) {
list.add(Thread.currentThread().getName());
}}).start();
}
Thread.sleep(5000);
System.out.println(list.size());
}
}
性能分析(多用同步块)
package com.junwei.syn;
/**
* @Auther: wangjunwei
* @Description:
* @Date: Created in 20:09 2018/12/26
*/
public class SynBlockTest03 {
public static void main(String[] args) {
//一份资源
SynSafeWeb12306 web = new SynSafeWeb12306();
//多个代理
new Thread(web, "黄牛1").start();
new Thread(web, "黄牛2").start();
new Thread(web, "黄牛3").start();
}
}
class SynSafeWeb12306 implements Runnable {
/*总票数*/
private int ticketNums = 99;
private boolean flag = true;
/**
* @Author: wangjunwei
* @Date: Created in 12:42 2018/12/26
* @Description: 重写run方法
*/
@Override
public void run() {
while (flag) {
test();
}
}
/**
* @Author: wangjunwei
* @Date: Created in 19:12 2018/12/26
* @Description: 线程安全
* 双重检测 double checking
*/
public void test() {
//考虑没票的情况
if (ticketNums <= 0) {
flag = false;
return;
}
//考虑只有一张票的情况
synchronized (this) {
if (ticketNums <= 0) {
flag = false;
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
/*获取当前线程名称 并打印剩余票数*/
System.out.println(Thread.currentThread().getName() + "-->" + ticketNums--);
}
}
}
影院购票案例
package com.junwei.syn;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @Auther: wangjunwei
* @Description: 快乐影院
* @Date: Created in 20:25 2018/12/26
*/
public class HappyCinema {
public static void main(String[] args) throws InterruptedException {
Cinema cinema = new Cinema(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), "《喜洋洋与灰太狼》");
new Thread(new Customer(cinema, Arrays.asList(1, 3, 7, 9)), "顾客1").start();
new Thread(new Customer(cinema, Arrays.asList(2, 4, 6, 8, 10)), "顾客2").start();
new Thread(new Customer(cinema, Arrays.asList(5, 8)), "顾客3").start();
}
}
/**
* 顾客
*/
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
class Customer implements Runnable {
private Cinema cinema;
private List<Integer> seats;
@Override
public void run() {
synchronized (cinema) {
//调用影院方法 查看票是否够
boolean flag = cinema.bookTickets(seats);
//如果票够 提示购票成功
if (flag) {
System.out.println("出票成功!" + Thread.currentThread().getName() + "->位置为:" + seats);
} else {
System.out.println("出票失败!" + Thread.currentThread().getName() + "->位置不够!");
}
}
}
}
/**
* 影院
*/
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
class Cinema {
/**
* 可用的位置
*/
private List<Integer> available;
/**
* 名称
*/
private String name;
/**
* @param seats 1 要选几个位子
* @return : boolean
* @Author: wangjunwei
* @Date: Created in 20:29 2018/12/26
* @Description: 判断是否购票成功
*/
public boolean bookTickets(List<Integer> seats) {
//输出剩余位置
System.out.println("可用位置为:" + available);
//临时存放集合
List<Integer> copy = new ArrayList<>();
copy.addAll(available);
//相减
copy.removeAll(seats);
//判断大小
if (available.size() - copy.size() == seats.size()) {
//如果成功 将票的库存重新赋值
available = copy;
return true;
}
return false;
}
}
/**运行结果*/
可用位置为:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
出票成功!顾客1->位置为:[1, 3, 7, 9]
可用位置为:[2, 4, 5, 6, 8, 10]
出票成功!顾客2->位置为:[2, 4, 6, 8, 10]
可用位置为:[5]
出票失败!顾客3->位置不够!
并发容器
**java.util.concurrent.CopyOnWriteArrayList **
CopyOnWriteArrayList内部已经实现同步,则不需要在方法里进行同步
死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,而导致两个或多个线程都在等待对方释放资源,都停止执行的情景。某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。
避免:不要在同一个代码块中同时持有多个锁
package com.junwei.syn;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* @Auther: wangjunwei
* @Description: 死锁
* 过多的同步可能造成互相不释放资源
* 从而互相等待,一般发生于同步中持有多个对象的锁
* @Date: Created in 21:52 2018/12/26
*/
public class DeadLock {
public static void main(String[] args) {
Markup m1 = new Markup(1,"丫头");
Markup m2 = new Markup(2,"公主");
m1.start();
m2.start();
}
}
/**
* @Author: wangjunwei
* @Date: Created in 21:55 2018/12/26
* @Description: 口红
*/
class Liostick {
}
/**
* @Author: wangjunwei
* @Date: Created in 21:55 2018/12/26
* @Description: 镜子
*/
class Mirror {
}
/**
* @Author: wangjunwei
* @Date: Created in 21:54 2018/12/26
* @Description: 化妆
*/
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
class Markup extends Thread {
static Liostick li = new Liostick();
static Mirror mi = new Mirror();
/**
* 选择
*/
private int choic;
/**
* 名字
*/
private String girl;
@Override
public void run() {
//化妆
makeUp();
}
/**
* @Author: wangjunwei
* @Date: Created in 21:58 2018/12/26
* @Description: 相互持有对方的对象锁
* 可能造成死锁
*/
private void makeUp(){
if (choic==0){
synchronized (li){
//获得口红的锁
System.out.println(this.getGirl()+"获得口红");
//1秒后想拥有镜子
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (mi){
//获得镜子
System.out.println(this.getGirl()+"获得镜子");
}
}else {
synchronized (mi){
//获得口红的锁
System.out.println(this.getGirl()+"获得镜子");
//2秒后想拥有镜子
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (li){
//获得镜子
System.out.println(this.getGirl()+"获得口红");
}
}
}
}
生产者消费者
线程协作(线程通信)
分析:这是一个线程同步问题,生产者和消费者共享一个资源,并且生产者和消费者之间相互依赖,互为条件
- 对于生产者,没有生产产品之前,要通知消费者等待。而生产了产品之后,又需要马上通知消费者消费。
- 对于消费者,在消费之后,要通知生产者已经消费结束,需要继续生产新的产品以供消费
- 在生产者消费者问题中,仅有synchronized是不够的
- synchronized可阻止并发更新同一个共享资源,实现了同步
- synchronized不能用来实现不同线程之间的消息传递(通信 )
解决方式1:
并发协作模型“生产者/消费者模式”–>管程法
- 生产者:负责生产数据的模块(这里模块可能是:方法、对象、线程、进程)
- 消费者: 负责处理数据的模块(这里模块可能是:方法、对象、线程、进程)
- 缓冲区:消费者不能直接使用生产者的数据,它们之间有个“缓冲区”
生产者将生产好的数据放入“缓冲区”,消费者从“缓冲区”拿要处理的数据
package com.junwei.cooperation;
import lombok.AllArgsConstructor;
/**
* @Auther: wangjunwei
* @Description: 协作模型
* 生产者消费者实现方式一:管程法
* @Date: Created in 09:06 2018/12/27
*/
public class CoTest01 {
public static void main(String[] args) {
//缓冲区
SynContainer sc = new SynContainer();
new Productor(sc).start();
new Consumer(sc).start();
}
}
/**
* @Author: wangjunwei
* @Date: Created in 9:07 2018/12/27
* @Description: 生产者
*/
@AllArgsConstructor
class Productor extends Thread {
/**
* 缓冲区
*/
SynContainer container;
@Override
public void run() {
//生产
for (int i = 0; i < 100; i++) {
System.out.println("生产-->" + i + "个产品");
//调用方法生产
container.push(new Steamedbun(i));
}
}
}
/**
* @Author: wangjunwei
* @Date: Created in 9:07 2018/12/27
* @Description: 消费者
*/
@AllArgsConstructor
class Consumer extends Thread {
/**
* 缓冲区
*/
SynContainer container;
@Override
public void run() {
//消费
for (int i = 0; i < 100; i++) {
//调用方法消费
System.out.println("消费-->" + container.pop().id + "个产品");
}
}
}
/**
* @Author: wangjunwei
* @Date: Created in 9:07 2018/12/27
* @Description: 缓冲区
*/
class SynContainer {
/**
* 产品存储容器
*/
Steamedbun[] buns = new Steamedbun[10];
/**
* 计数器
*/
int count = 0;
/**
* @param bun 1
* @return : void
* @Author: wangjunwei
* @Date: Created in 9:12 2018/12/27
* @Description: 存储 生产
*/
public synchronized void push(Steamedbun bun) {
//何时能生产 容器存在空间
//不能生产 只有等待
if (count==buns.length){
try {
//线程阻塞 消费者通知 生产解除
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
buns[count] = bun;
count++;
//存在数据了 可以通知消费了
this.notifyAll();
}
/**
* @return : com.junwei.cooperation.Steamedbun
* @Author: wangjunwei
* @Date: Created in 9:14 2018/12/27
* @Description: 获取 消费
*/
public synchronized Steamedbun pop() {
//何时消费 容器中是否存在数据
//没有数据 只有等待
if (count==0){
try {
//此时线程阻塞 生产者通知消费 阻塞解除
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
Steamedbun bun = buns[count];
//存在空间了 可以唤醒生产了
this.notifyAll();
return bun;
}
}
/**
* @Author: wangjunwei
* @Date: Created in 9:07 2018/12/27
* @Description: 数据
*/
@AllArgsConstructor
class Steamedbun {
int id;
}
解决方式2:
并发协作模型“生产者/消费者模式”–>信号灯法
Java提供了3个方法解决线程之间的通信问题
package com.junwei.cooperation;
import lombok.AllArgsConstructor;
/**
* @Auther: wangjunwei
* @Description: 协作模型:
* 生产者消费者实现方式二:信号灯法
* 借助标志位
* @Date: Created in 09:43 2018/12/27
*/
public class CoTest02 {
public static void main(String[] args) {
Tv tv = new Tv();
new Actor(tv).start();
new Dudience(tv).start();
}
}
/**
* @Author: wangjunwei
* @Date: Created in 9:45 2018/12/27
* Description: 演员 生产者 继承线程类
*/
@AllArgsConstructor
class Actor extends Thread {
Tv tv;
/**
* @Author: wangjunwei
* @Date: Created in 9:46 2018/12/27
* @Description: 重写run方法
*/
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i % 2 == 0) {
this.tv.play("动物世界");
} else {
this.tv.play("广告时间");
}
}
}
}
/**
* @Author: wangjunwei
* @Date: Created in 9:47 2018/12/27
* @Description: 观众 消费者 继承线程类
*/
@AllArgsConstructor
class Dudience extends Thread {
Tv tv;
/**
* @Author: wangjunwei
* @Date: Created in 9:47 2018/12/27
* @Description: 重写 run方法
*/
@Override
public void run() {
for (int i = 0; i < 20; i++) {
this.tv.watch();
}
}
}
/**
* @Author: wangjunwei
* @Date: Created in 9:48 2018/12/27
* @Description: 同一个资源 电视
*/
class Tv {
/**
* 声音 资源
*/
String voice;
/**
* 信号灯
* T 表示演员表演 观众等待
* F 表示观众观看 演员等待
*/
boolean flag = true;
/**
* @param voice 1
* @Author: wangjunwei
* @Date: Created in 9:51 2018/12/27
* @Description: 表演
*/
public synchronized void play(String voice) {
//演员等待
if (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("表演了: " + voice);
this.voice = voice;
this.notifyAll();
//切换标志
this.flag = !this.flag;
}
/**
* @Author: wangjunwei
* @Date: Created in 9:53 2018/12/27
* @Description: 观众观看
*/
public synchronized void watch() {
if (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//观众等待
System.out.println("听到了:" + voice);
this.notifyAll();
//切换标志
this.flag = !this.flag;
}
}
方法名 | 作用 |
---|---|
final void wait() | 表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁 |
final void wait(long timeout) | 指定等待的毫秒数 |
final void notifiy() | 唤醒一个处于等待状态的线程 |
final void notifyAll() | 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度 |
均是java.lang.Object类的方法都只能在同步方法或者同步代码块中使用,否则会抛出异常
高级主题
任务定时调度
通过Timer和Timerask,我们可以实现定时启动某个线程
- java.util.Timer:类似闹钟的功能,本身实现的就是一个线程
- java.util.TimerTask:一个抽象类,该类实现了Runnable接口,所以该类具备多线程的能力。
package com.junwei.others;
import org.junit.Test;
import java.text.SimpleDateFormat;
import java.util.Timer;
import java.util.TimerTask;
/**
* @Auther: wangjunwei
* @Description: 任务调度
* Timer 和 TimerTask
* @Date: Created in 10:17 2018/12/27
*/
public class TimerTest01 {
public static void main(String[] args) {
//执行安排
Timer timer = new Timer();
//执行任务 参数 第一次执行时间
timer.schedule(new MyTask(), 1000, 1000);
}
}
class MyTask extends TimerTask {
@Override
public void run() {
//定时任务
System.out.println(new SimpleDateFormat("HH:mm:ss").format(System.currentTimeMillis()));
System.out.println("-----------------");
}
}
Quartz(任务调度框架)
- Scheduler:调度器,控制所有的调度
- Trigger:触发条件,采用DSL模式
- JobDetail:需要处理的JOB
- Job:执行逻辑
DSL:Domain-specific language领域特定语言,针对一个特定的领域,具有受限表达性的一种计算机程序语言,即领域专用语言,声明式编程:
- Method Chaining 方法链 Fluent Style流畅风格,builder模式构建器
- Nested Functions 嵌套函数
- Lambda Expressions/Closures
- Functional Sequence
HappenBefore
- 你写的代码很可能根本没按照你期望的顺序执行,因为编译器和cpu会尝试重排指令使得代码更快运行
执行代码的顺序可能与编写代码不一致,即虚拟机优化代码顺序,则为指令重排
happen-before即:编译器或运行时环境为了优化程序性能而采取的对指令进行重新排序执行的一种手段。
Volatile(不常见)
volatile保证线程间变量的可见性,简单地说就是当线程A对变量X进行了修改后,在线程A后面执行的其他线程能看到变量X的变动,更详细地说是要符合以下两个规则:
- 线程对变量进行修改之后,要立刻回写到主存
- 线程对变量读取的时候,要从主存中读。而不是缓存
各线程的工作内存间彼此独立、互不可见,在线程启动的时候,虚拟机为每个内存分配一块工作内存,不仅包括了线程内部定义的局部变量,也包含了线程所需要使用的共享变量(非线程内构造的对象)的副本,即为了提高执行效率。
volatile是不错的机制,但是volatile不能保证原子性。
CAS
锁分为两类:
- 悲观锁:synchronized是独占锁即悲观锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。
- 乐观锁:每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。
Compare and Swap 比较并交换:
- 乐观锁的实现:
- 有三个值:一个当前内存值V、旧的预期值A、将更新的值B。现获取到内存当中当前的内存值V,再将内存值V和原值A作比较,要是相等就修改为要修改的值B并返回true。否则什么都不做,并返回false
- CAS是一组原子操作,不会被外部打断
- 属于硬件级别的操作(利用cpu的CAS指令,同时借助JNI来完成的非阻塞算法),效率比枷锁操作高
- ABA问题:如果变量V初次读取的时候是A,并且在准备赋值的时候检查到它仍然是A,那能说明它的值没有被其他线程修改过了吗?如果在这段时间曾经被改成B,然后又改回A,那CAS操作就会无人为它从来没有被修改过。