多线程(java.thread)
1.线程简介
多线程指多条路径,每个线程都有自己的工作内存,负责和主内存进行交互
方法间调用:普通方法调用,从哪里来到哪里去,闭合的一条路径,程序执行时必须等方法调用完才能往下执行
多线程使用:开辟了多条路径,不需要等到方法执行完就可以往下执行
2.线程实现
实现线程的三种方式:
- 继承Thread类(实现run()方法,调用时执行Thread类的start()方法)
- 实现Runnable接口(实现run()方法,调用时通过Thread对象调用start()方法)
- 实现Callable接口(实现call()方法)
2.1继承Thread类
• 执行线程必须调用start(),加入到调度器中
• 不一定立即执行,系统安排调度分配执行
• 直接调用run()不是开启多线程,是普通方法调用
start()方法开启一个线程,不保证立刻执行,交给cpu安排时间片进行调度,不用等方法执行完就可以执行下面的代码,但还是调用的run()方法
run()方法就是普通的方法调用,必须等到方法执行完才能往下执行
public class Listaa {
public static void main (String[] args)throws IOException {
// 仅仅创建了线程对象
Say say = new Say();
say.start();// 启动线程,不保证立刻执行,方法执行时,调用run()方法被执行
// say.run();// 普通方法调用
for(int i=0;i<10;i++){
System.out.println("游戏");
}
}
}
class Say extends Thread{
// 线程体的入口点
@Override
public void run() {
// 线程执行的代码
for(int i=0;i<10;i++){
System.out.println("听歌");
}
}
}
public class WebDownloader {
public void download(String url,String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (MalformedURLException e) {
e.printStackTrace();
System.out.println("不合法的url");
} catch (IOException e) {
e.printStackTrace();
System.out.println("下载失败");
}
}
}
public class TDownloader extends Thread {
private String url; //远程路径
private String name; //存储名字
public TDownloader(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public void run() {
WebDownloader wd =new WebDownloader();
wd.download(url, name);
System.out.println(name);
}
public static void main(String[] args) {
TDownloader td1 =new TDownloader("http://upload.news.cecb2b.com/2014/0511/1399775432250.jpg","phone.jpg");
TDownloader td2 =new TDownloader("http://p1.pstatp.com/large/403c00037462ae2eee13","spl.jpg");
TDownloader td3 =new TDownloader("http://5b0988e595225.cdn.sohucs.com/images/20170830/d8b57e0dce0d4fa29bd5ef014be663d5.jpeg","success.jpg");
//启动三个线程
td1.start();
td2.start();
td3.start();
}
}
2.2实现Runnable接口
通过Thread类对象调用start(),本质是一种静态代理模式(也是装饰器模式)
public class StartRun implements Runnable{
/**
* 线程入口点
*/
@Override
public void run() {
for(int i=0;i<20;i++) {
System.out.println("一边听歌");
}
}
public static void main(String[] args) {
//创建实现类对象
new Thread(new StartRun()).start();//本质是一种静态代理模式
for(int i=0;i<20;i++) {
System.out.println("一边coding");
}
}
}
public class WebDownloader {
public void download(String url,String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (MalformedURLException e) {
e.printStackTrace();
System.out.println("不合法的url");
} catch (IOException e) {
e.printStackTrace();
System.out.println("下载失败");
}
}
}
public class TDownloader implements Runnable {
private String url; //远程路径
private String name; //存储名字
public TDownloader(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public void run() {
WebDownloader wd =new WebDownloader();
wd.download(url, name);
System.out.println(name);
}
public static void main(String[] args) {
TDownloader td1 =new TDownloader("http://upload.news.cecb2b.com/2014/0511/1399775432250.jpg","phone.jpg");
TDownloader td2 =new TDownloader("http://p1.pstatp.com/large/403c00037462ae2eee13","spl.jpg");
TDownloader td3 =new TDownloader("http://5b0988e595225.cdn.sohucs.com/images/20170830/d8b57e0dce0d4fa29bd5ef014be663d5.jpeg","success.jpg");
//启动三个线程
new Thread(td1).start();
new Thread(td2).start();
new Thread(td3).start();
}
}
/**
* 共享资源,并发(线程安全)
*/
public class Web12306 implements Runnable{
//票数
private int ticketNums = 99;
@Override
public void run() {
while(true) {
if(ticketNums<0) {
break;
}
try {
Thread.sleep(200);// 线程睡眠0.2秒,这样线程不安全
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
}
public static void main(String[] args) {
//一份资源
Web12306 web =new Web12306();
System.out.println(Thread.currentThread().getName());
//多个代理
new Thread(web,"码畜").start();
new Thread(web,"码农").start();
new Thread(web,"码蟥").start();;
}
}
/**
* 静态代理
* 公共接口:
* 1、真实角色
* 2、代理角色
*/
public class StaticProxy {
public static void main(String[] args) {
new WeddingCompany(new You()).happyMarry();
//new Thread(线程对象).start();
}
}
interface Marry{
void happyMarry();
}
//真实角色
class You implements Marry{
@Override
public void happyMarry() {
System.out.println("you and 嫦娥终于奔月了....");
}
}
//代理角色
class WeddingCompany implements Marry{
//真实角色
private Marry target;
public WeddingCompany(Marry target) {
this.target = target;
}
@Override
public void happyMarry() {
ready();
this.target.happyMarry();
after();
}
private void ready() {
System.out.println("布置猪窝。。。。");
}
private void after() {
System.out.println("闹玉兔。。。。");
}
}
/**
* Lambda表达式 简化线程(用一次)的使用,使用Lambda表达式接口必须只有一个方法
*/
public class LambdaThread {
//静态内部类
static class Test implements Runnable{
public void run() {
for(int i=0;i<20;i++) {
System.out.println("一边听歌");
}
}
}
public static void main(String[] args) {
//new Thread(new Test()).start();
//局部内部类
class Test2 implements Runnable{
public void run() {
for(int i=0;i<20;i++) {
System.out.println("一边听歌");
}
}
}
new Thread(new Test2()).start();
//匿名内部类 必须借助接口或者父类
new Thread(new Runnable() {
public void run() {
for(int i=0;i<20;i++) {
System.out.println("一边听歌");
}
}
}).start();
//jdk8 简化 lambda表达式
new Thread(()-> {
for(int i=0;i<20;i++) {
System.out.println("一边听歌");
}
}
).start();
}
}
/**
* lambda推导 +参数+返回值
*/
public class LambdaTest03 {
public static void main(String[] args) {
IInterest interest = (int a,int c)-> {
System.out.println("i like lambda -->"+(a+c));
return a+c;
};
interest.lambda(100,200);
interest = (a,c)-> {
System.out.println("i like lambda -->"+(a+c));
return a+c;
};
interest.lambda(200,200);
interest = (a,c)-> {
return a+c;
};
interest = (a,c)-> a+c;
interest = (a,c)-> 100;
System.out.println(interest.lambda(10, 20));
}
}
interface IInterest{
int lambda(int a,int b);
}
//外部类
class Interest implements IInterest{
@Override
public int lambda(int a,int c) {
System.out.println("i like lambda -->"+(a+c));
return a+c;
}
}
2.3实现Callable接口
-
创建目标对象: CDownloader cd =new CDownloader(“图片地址”,“baidu.png”);
-
创建执行服务: ExecutorService ser=Executors.newFixedThreadPool(1);
-
提交执行: Future result =ser.submit(cd) ;
-
获取结果: boolean r =result.get();
-
关闭服务: ser.shutdownNow();
public class CDownloader implements Callable<Boolean>{
private String url; //远程路径
private String name; //存储名字
public CDownloader(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public Boolean call() throws Exception {
WebDownloader wd =new WebDownloader();
wd.download(url, name);
System.out.println(name);
return true;
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
CDownloader cd1 =new CDownloader("http://upload.news.cecb2b.com/2014/0511/1399775432250.jpg","phone.jpg");
CDownloader cd2 =new CDownloader("http://p1.pstatp.com/large/403c00037462ae2eee13","spl.jpg");
CDownloader cd3 =new CDownloader("http://5b0988e595225.cdn.sohucs.com/images/20170830/d8b57e0dce0d4fa29bd5ef014be663d5.jpeg","success.jpg");
//创建执行服务:
ExecutorService ser=Executors.newFixedThreadPool(3);
//提交执行:
Future<Boolean> result1 =ser.submit(cd1) ;
Future<Boolean> result2 =ser.submit(cd2) ;
Future<Boolean> result3 =ser.submit(cd3) ;
//获取结果:
boolean r1 =result1.get();
boolean r2 =result1.get();
boolean r3 =result1.get();
System.out.println(r3);
//关闭服务:
ser.shutdownNow();
}
}
3.线程状态
线程不能重新开启
线程阻塞进入就绪状态,不是运行状态
1.新生状态
2.就绪状态(1.start方法,2.阻塞解除,3.yield方法,线程自己中断自己,4.JVM从本地线程切换到其他线程)
3.运行状态
4.阻塞状态(1.sleep方法,抱着资源睡觉,2.wait方法,不占用资源,3.join方法,插队,4.IO流的read,writer,需要调用操作系统)(sleep称为wait状态,join称为timewait状态,wait和io流称为Blocked)
5.死亡状态(1.stop方法,2.destroy方法。不推荐,也都被废弃了)一般是等代码执行完或者想办法让代码执行完
1.线程状态 Thread.state
NEW // 新生状态
尚未启动的线程处于此状态。
RUNNABLE // 就绪状态和运行状态,统一称之为RUNNABLE
在Java虚拟机中执行的线程处于此状态。
BLOCKED // 阻塞状态 wait方法及IO流的read和writer方法
被阻塞等待监视器锁定的线程处于此状态。
WAITING // 阻塞状态 sleep方法
正在等待另一个线程执行特定动作的线程处于此状态。
TIMED_WAITING // 阻塞状态 join方法
正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
TERMINATED // 死亡状态
已退出的线程处于此状态。
2.活动线程数 Thread.activeCount();
3.1线程终止
// 不使用JDK提供的stop()/destroy()方法(它们本身也被JDK废弃了)。
// 提供一个boolean型的终止变量,当这个变量置为false,则终止线程的运行。
class Study implements Runnable{
//1)、线程类中 定义 线程体使用的标识
private boolean flag =true;
@Override
public void run() {
//2)、线程体使用该标识
while(flag){
System.out.println("study thread....");
}
}
//3)、对外提供方法改变标识
public void stop(){
this.flag =false;
}
}
3.2线程暂停
// 静态方法 Thread.sleep();
// sleep(时间)指定当前线程阻塞的毫秒数;
// sleep存在异常InterruptedException;
// sleep时间达到后线程进入就绪状态;
// sleep可以模拟网络延时、倒计时等。
// 每一个对象都有一个锁,sleep不会释放锁;会占用线程资源,和wait进行对比
// 容易有并发异常
/**
* 共享资源,并发(线程安全)
*/
public class Web12306 implements Runnable{
//票数
private int ticketNums = 99;
@Override
public void run() {
while(true) {
if(ticketNums<0) {
break;
}
try {
Thread.sleep(200);// 线程睡眠0.2秒,这样线程不安全
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
}
public static void main(String[] args) {
//一份资源
Web12306 web =new Web12306();
System.out.println(Thread.currentThread().getName());
//多个代理
new Thread(web,"码畜").start();
new Thread(web,"码农").start();
new Thread(web,"码蟥").start();;
}
}
/**
* sleep模拟倒计时
*/
public class BlockedSleep03 {
public static void main(String[] args) throws InterruptedException {
//倒计时
Date endTime=new Date(System.currentTimeMillis()+1000*10);
long end = endTime.getTime();
while(true) {
System.out.println(new SimpleDateFormat("mm:ss").format(endTime));
Thread.sleep(1000);
endTime=new Date(endTime.getTime()-1000);
if(end-10000 >endTime.getTime() ) {
break;
}
}
}
public static void test() throws InterruptedException {
//倒数10个数,1秒一个
int num = 10;
while(true) {
Thread.sleep(1000);
System.out.println(num--);
}
}
}
3.3线程礼让
// 静态方法 Thread.yield();
// 礼让线程,让当前正在执行线程暂停
// 不是阻塞线程,而是将线程从**运行状态**转入**就绪状态**
// 让cpu调度器重新调度
/**
* yield 礼让线程,暂停线程 直接进入就绪状态不是阻塞状态
*/
public class YieldDemo01 {
public static void main(String[] args) {
MyYield my =new MyYield();
new Thread(my,"a").start();
new Thread(my,"b").start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->start");
Thread.yield(); //礼让
System.out.println(Thread.currentThread().getName()+"-->end");
}
}
3.4线程插队
// join合并线程,待此线程执行完成后,再执行其他线程,**其他线程阻塞**
// join是成员方法,必须通过Thread对象进行调用,new Thread(对象).join();
/**
* join:合并线程,插队线程
*/
public class BlockedJoin02 {
public static void main(String[] args) throws InterruptedException {
System.out.println("爸爸和儿子买烟的故事");
new Thread(new Father()).start();
}
}
class Father extends Thread{
public void run() {
System.out.println("想抽烟,发现没了");
System.out.println("让儿子去买中华");
Thread t =new Thread(new Son());
t.start();
try {
t.join(); //father被阻塞
System.out.println("老爸接过烟,把零钱给了儿子");
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("孩子走丢了,老爸出去找孩子了。。。");
}
}
}
class Son extends Thread{
public void run() {
System.out.println("接过老爸的钱出去了。。。");
System.out.println("路边有个游戏厅,玩了10秒");
for(int i=0;i<10;i++) {
System.out.println(i+"秒过去了...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("赶紧买烟去。。。。");
System.out.println("手拿一包中华回家了。。。。");
}
}
3.5线程优先级
注意:优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高后调用优先级低的线程。
-
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程。线程调度器按照线程的优先级决定应调度哪个线程来执行。
-
线程的优先级用数字表示,范围从1到10
2.1 Thread.MIN_PRIORITY = 1
2.2 Thread.MAX_PRIORITY = 10
2.3 Thread.NORM_PRIORITY = 5 // 所有线程创建后默认优先级是5
-
优先级的设定建议在start()调用前
-
使用下述方法获得或设置线程对象的优先级。
4.1 int getPriority();
4.2 void setPriority(int newPriority);
/**
* 线程的优先级 1-10
* 1、NORM_PRIORITY 5 默认
* 2、MIN_PRIORITY 1
* 2、MAX_PRIORITY 10
* 概率 ,不代表绝对的先后顺序
*/
public class PriorityTest {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getPriority());
MyPriority mp = new MyPriority();
Thread t1 = new Thread(mp,"adidas");
Thread t2 = new Thread(mp,"NIKE");
Thread t3 = new Thread(mp,"回力");
Thread t4 = new Thread(mp,"李宁");
Thread t5 = new Thread(mp,"双星");
Thread t6 = new Thread(mp,"puma");
//设置优先级在启动前
t1.setPriority(10);
t2.setPriority(Thread.MAX_PRIORITY);
t3.setPriority(Thread.MAX_PRIORITY);
t4.setPriority(Thread.MIN_PRIORITY);
t5.setPriority(Thread.MIN_PRIORITY);
t6.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
Thread.yield();
}
}
3.6守护线程
Thread t = new Thread();
t.setDaemon(true);// 将用户线程调整为守护线程
3.7线程方法
方法 | 功能 |
---|---|
isAlive() | 判断线程是否还活着,即线程是否终止 |
setName() | 给线程起一个名字 |
getName() | 获取线程的名字 |
currentThread() | 取得当前正在运行的线程对象 |
4.线程同步(synchronized)
并发:同一个对象多个线程同时操作
目标:即保证安全又提高性能
4.1线程同步的条件:
1.形成队列,同一个时间点只允许一个线程操作
2.锁机制,当一个线程获取对象( 资源)的排他锁,独占资源,其他线程必须等待,使用后释放锁
4.2锁机制的问题:
1.一个线程持有锁会导致其他所有需要此锁的线程挂起
2.频繁的加锁,释放锁会引起性能问题
3.优先级高的线程等待优先级低的线程释放锁会引起优先级倒置,性能问题
4.3 synchronized方法和synchronized块
范围太大,效率低下
范围太小,线程不安全
提高效率:先判断资源是否存在,再访问资源,我们锁的是访问资源这一块,好的方法是在锁外判断下资源是否存在,在锁里再判断下资源是否存在,再访问资源。
4.3.1 synchronized方法
锁的是对象的资源,不是方法,下面锁的是this,也就是SafeWeb12306这个对象,要保证方法内的资源都是属于这个对象的。
/**
* 线程安全: 在并发时保证数据的正确性、效率尽可能高
* synchronized
* 1、同步方法
*/
public class SynTest01 {
public static void main(String[] args) {
//一份资源
SafeWeb12306 web =new SafeWeb12306();
//多个代理
new Thread(web,"码畜").start();
new Thread(web,"码农").start();
new Thread(web,"码蟥").start();;
}
}
class SafeWeb12306 implements Runnable{
//票数
private int ticketNums =10;
private boolean flag = true;
@Override
public void run() {
while(flag) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
test();
}
}
//线程安全 同步
public synchronized void test() {
if(ticketNums<=0) {
flag = false;
return ;
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
// 或者
public synchronized void test1() {
synchronized(this){
if(ticketNums<=0) {
flag = false;
return ;
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
}
}
4.3.2 synchronized块
我们锁的是账户,不是取款机
提高效率:先判断资源是否存在,再访问资源,我们锁的是访问资源这一块
public class SynBlockTest01 {
public static void main(String[] args) {
//账户
Account account =new Account(1000,"结婚礼金");
SynDrawing you = new SynDrawing(account,80,"可悲的你");
SynDrawing wife = new SynDrawing(account,90,"happy的她");
you.start();
wife.start();
}
}
//模拟取款 线程安全
class SynDrawing extends Thread{
Account account ; //取钱的账户
int drawingMoney ;//取的钱数
int packetTotal ; //口袋的总数
public SynDrawing(Account account, int drawingMoney,String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
@Override
public void run() {
test() ;
}
//目标锁定account
public void test() {
//提高性能
if(account.money<=0) {
return ;
}
//同步块
synchronized(account) {
if(account.money -drawingMoney<0) {
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money -=drawingMoney;
packetTotal +=drawingMoney;
System.out.println(this.getName()+"-->账户余额为:"+account.money);
System.out.println(this.getName()+"-->口袋的钱为:"+packetTotal);
}
}
}
public class SynBlockTest02 {
public static void main(String[] args) throws InterruptedException {
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(10000);
System.out.println(list.size());
}
}
4.4 性能提升
public class SynBlockTest03 {
public static void main(String[] args) {
//一份资源
SynWeb12306 web =new SynWeb12306();
//多个代理
new Thread(web,"码畜").start();
new Thread(web,"码农").start();
new Thread(web,"码蟥").start();;
}
}
class SynWeb12306 implements Runnable{
//票数
private int ticketNums =10;
private boolean flag = true;
@Override
public void run() {
while(flag) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
test5();
}
}
//线程安全:尽可能锁定合理的范围(不是指代码 指数据的完整性)
//double checking
public void test5() {
if(ticketNums<=0) {//考虑的是没有票的情况
flag = false;
return ;
}
synchronized(this) {
if(ticketNums<=0) {//考虑最后的1张票
flag = false;
return ;
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
}
//线程不安全 范围太小锁不住
public void test4() {
synchronized(this) {
if(ticketNums<=0) {
flag = false;
return ;
}
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
//线程不安全 ticketNums对象在变
public void test3() {
synchronized((Integer)ticketNums) {
if(ticketNums<=0) {
flag = false;
return ;
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
}
//线程安全 范围太大 -->效率低下
public void test2() {
synchronized(this) {
if(ticketNums<=0) {
flag = false;
return ;
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
}
//线程安全 同步
public synchronized void test1() {
if(ticketNums<=0) {
flag = false;
return ;
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
}
4.4 并发容器
public class SynContainer {
public static void main(String[] args) throws InterruptedException {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
for(int i=0;i<10000;i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}) .start();
}
Thread.sleep(10000);
System.out.println(list.size());
}
}
4.5 死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。
避免:不要在同一个代码块中,持有多个对象的锁
5.生产消费者(线程协作或线程通信)
生产者和消费者不能直接交流
生产者---------仓库---------消费者
分析:这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消 费者之间相互依赖,互为条件
1.对于生产者,没有生产产品之前,要通知消费者等待。而生产了产品之后, 又需要马上通知消费者消费
2.对于消费者,在消费之后,要通知生产者已经消费结束,需要继续生产新 产品以供消费
3.在生产者消费者问题中,仅有synchronized是不够的
3.1 synchronized可阻止并发更新同一个共享资源,实现了同步
3.2 synchronized不能用来实现不同线程之间的消息传递(通信)
方法 | 作用 |
---|---|
final void wait() | 表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁 |
final void wait(long timeout) | 指定等待的毫秒数 |
final void notifiy() | 唤醒一个处于等待状态的线程 |
final void notifyAll() | 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度 |
5.1 管程法
下面的this指的是缓冲区的对象
/**
* 协作模型:生产者消费者实现方式一:管程法
* 借助缓冲区
*/
public class CoTest01 {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Productor(container).start();
new Consumer(container).start();
}
}
//生产者
class Productor extends Thread{
SynContainer container ;
public Productor(SynContainer container) {
this.container = container;
}
public void run() {
//生产
for(int i=0;i<100;i++) {
System.out.println("生产-->"+i+"个馒头");
container.push(new Steamedbun(i) );
}
}
}
//消费者
class Consumer extends Thread{
SynContainer container ;
public Consumer(SynContainer container) {
this.container = container;
}
public void run() {
//消费
for(int i=0;i<100;i++) {
System.out.println("消费-->"+container.pop().id+"个馒头");
}
}
}
//缓冲区
class SynContainer{
Steamedbun[] buns = new Steamedbun[10]; //存储容器
int count = 0; //计数器
//存储 生产
public synchronized void push(Steamedbun bun) {
//何时能生产 容器存在空间
//不能生产 只有等待
if(count == buns.length) {
try {
this.wait(); //线程阻塞 消费者通知生产解除
} catch (InterruptedException e) {
}
}
//存在空间 可以生产
buns[count] = bun;
count++;
//存在数据了,可以通知消费了
this.notifyAll();
}
//获取 消费
public synchronized Steamedbun pop() {
//何时消费 容器中是否存在数据
//没有数据 只有等待
if(count == 0) {
try {
this.wait(); //线程阻塞 生产者通知消费解除
} catch (InterruptedException e) {
}
}
//存在数据可以消费
count --;
Steamedbun bun = buns[count] ;
this.notifyAll(); //存在空间了,可以唤醒对方生产了
return bun;
}
}
//馒头
class Steamedbun{
int id;
public Steamedbun(int id) {
this.id = id;
}
}
5.2 信号灯法
/**
* 协作模型:生产者消费者实现方式二:信号灯法
* 借助标志位
*/
public class CoTest02 {
public static void main(String[] args) {
Tv tv =new Tv();
new Player(tv).start();
new Watcher(tv).start();
}
}
//生产者 演员
class Player extends Thread{
Tv tv;
public Player(Tv tv) {
this.tv = tv;
}
public void run() {
for(int i=0;i<20;i++) {
if(i%2==0) {
this.tv.play("奇葩说");
}else {
this.tv.play("太污了,喝瓶立白洗洗嘴");
}
}
}
}
//消费者 观众
class Watcher extends Thread{
Tv tv;
public Watcher(Tv tv) {
this.tv = tv;
}
public void run() {
for(int i=0;i<20;i++) {
tv.watch();
}
}
}
//同一个资源 电视
class Tv{
String voice;
//信号灯
//T 表示演员表演 观众等待
//F 表示观众观看 演员等待
boolean flag = true;
//表演
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;
}
//观看
public synchronized void watch() {
//观众等待
if(flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//观看
System.out.println("听到了:"+voice);
//唤醒
this.notifyAll();
//切换标志
this.flag =!this.flag;
}
}
6.高级主题
6.1 任务定时调度
public class TimerTest01 {
public static void main(String[] args) {
Timer timer = new Timer();
//执行安排
//timer.schedule(new MyTask(), 1000); //执行任务一次
//timer.schedule(new MyTask(), 1000,200); //执行多次
Calendar cal = new GregorianCalendar(2099999,12,31,21,53,54);
timer.schedule(new MyTask(), cal.getTime(),200); //指定时间
}
}
//任务类
class MyTask extends TimerTask{
@Override
public void run() {
for(int i=0;i<10;i++) {
System.out.println("放空大脑休息一会");
}
System.out.println("------end-------");
}
}
6.2 quartz(任务调度框架)
1.Scheduler(调度器,控制所有的调度)
2.Trigger(触发器,采用DSL模式)
3.JobDetail(需要处理的Job)
4.Job(执行逻辑)
public class HelloJob implements Job {
public HelloJob() {
}
public void execute(JobExecutionContext context)
throws JobExecutionException {
System.out.println("-------start---------");
System.out.println("Hello World! - " + new Date());
System.out.println("-------end---------");
}
}
public class QuartzTest {
public void run() throws Exception {
// 1、创建 Scheduler的工厂
SchedulerFactory sf = new StdSchedulerFactory();
//2、从工厂中获取调度器
Scheduler sched = sf.getScheduler();
// 3、创建JobDetail
JobDetail job = newJob(HelloJob.class).withIdentity("job1", "group1").build();
// 时间
Date runTime = evenSecondDateAfterNow();
// 4、触发条件,可以写什么时间执行
//Trigger trigger = newTrigger().withIdentity("trigger1", "group1").startAt(runTime).build();
Trigger trigger = newTrigger().withIdentity("trigger1", "group1").startAt(runTime)
.withSchedule(simpleSchedule().withIntervalInSeconds(5).withRepeatCount(3)).build();
// 5、注册任务和触发条件
sched.scheduleJob(job, trigger);
// 6、启动
sched.start();
try {
// 100秒后停止
Thread.sleep(100L * 1000L);
} catch (Exception e) {
}
sched.shutdown(true);
}
public static void main(String[] args) throws Exception {
QuartzTest example = new QuartzTest();
example.run();
}
6.3 指令重排
内存执行顺序:
1.获取指令
2.从寄存器拿取值,copy到工作内存中‘
3.操作
4.写回寄存器
如果不存在数据依赖,后面的代码可能会先执行
写后读 a = 1;b = a; 写一个变量之后,再读这个位置。
写后写 a = 1;a = 2; 写一个变量之后,再写这个变量。
读后写 a = b;b = 1; 读一个变量之后,再写这个变量。
下面代码可能3可能会先执行
1.subTotal = price + fee;
2.total += subtotal;
3.flag = true;
6.3 DCL单例模式
/**
* DCL单例模式: 懒汉式套路基础上加入并发控制,保证在多线程环境下,对外存在一个对象
* 1、构造器私有化 -->避免外部new构造器
* 2、提供私有的静态属性 -->存储对象的地址
* 3、提供公共的静态方法 --> 获取属性
*/
public class DoubleCheckedLocking {
//2、提供私有的静态属性
//没有volatile其他线程可能访问一个没有初始化的对象,避免指令重排
private static volatile DoubleCheckedLocking instance;
//1、构造器私有化
private DoubleCheckedLocking() {
}
//3、提供公共的静态方法 --> 获取属性
public static DoubleCheckedLocking getInstance() {
//再次检测
if(null!=instance) { //避免不必要的同步 ,已经存在对象
return instance;
}
synchronized(DoubleCheckedLocking.class) {
if(null == instance) {
instance = new DoubleCheckedLocking();
//new 一个对象发生的事情 1、开辟空间 //2、初始化对象信息 //3、返回对象的地址给引用
}
}
return instance;
}
public static DoubleCheckedLocking getInstance1(long time) {
if(null == instance) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new DoubleCheckedLocking();
//1、开辟空间 //2、初始化对象信息 //3、返回对象的地址给引用
}
return instance;
}
public static void main(String[] args) {
Thread t = new Thread(()->{
System.out.println(DoubleCheckedLocking.getInstance());
}) ;
t.start();
System.out.println(DoubleCheckedLocking.getInstance());
}
}
6.4 threadLocal
就是保存了多个线程的各自的成员变量
ThreadLocal最常用的地方就是为每个线程绑定一个数据库连接,HTTP
请求,用户身份信息等,这样一个线程的所有调用到的方法都可以非常
方便地访问这些资源。
• Hibernate的Session 工具类HibernateUtil
• 通过不同的线程对象设置Bean属性,保证各个线程Bean对象的独立
性
/**
* ThreadLocal:每个线程自身的存储本地、局部区域
* get/set/initialValue
* @author 裴新 QQ:3401997271
*
*/
public class ThreadLocalTest01 {
//private static ThreadLocal<Integer> threadLocal = new ThreadLocal<> ();//默认null
//更改初始化值方法1
/*private static ThreadLocal<Integer> threadLocal = new ThreadLocal<> () {
protected Integer initialValue() {
return 200;
};
};*/
//更改初始化值方法2
private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(()-> 200);
public static void main(String[] args) {
//获取值
System.out.println(Thread.currentThread().getName()+"-->"+threadLocal.get());// 200
//设置值
threadLocal.set(99);
System.out.println(Thread.currentThread().getName()+"-->"+threadLocal.get());// 99
new Thread(new MyRun()).start();//5
new Thread(new MyRun()).start();//5
}
public static class MyRun implements Runnable{
public void run() {
threadLocal.set(5);
System.out.println(Thread.currentThread().getName()+"-->"+threadLocal.get());
}
}
}
/**
* ThreadLocal:分析上下文 环境 起点
* 1、构造器: 哪里调用 就属于哪里 找线程体
* 2、run方法:本线程自身的
*/
public class ThreadLocalTest03 {
private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(()-> 1);
public static void main(String[] args) {
new Thread(new MyRun()).start();
new Thread(new MyRun()).start();
}
public static class MyRun implements Runnable{
public MyRun() {
threadLocal.set(-100);// 此线程是属于main方法的线程
System.out.println(Thread.currentThread().getName()+"-->"+threadLocal.get());
}
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+threadLocal.get());
//new Thread(new MyRunxxx()).start();
}
}
}
子类
/**
* InheritableThreadLocal:继承上下文 环境的数据 ,拷贝一份给子线程
*/
public class ThreadLocalTest04 {
private static ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) {
threadLocal.set(2);
System.out.println(Thread.currentThread().getName()+"-->"+threadLocal.get()); //2
//线程由main线程开辟
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"-->"+threadLocal.get()); //2
threadLocal.set(200);
System.out.println(Thread.currentThread().getName()+"-->"+threadLocal.get()); //200
}) .start();
}
}
6.5 可重入锁
如果某个线程试图获取一个已经由它自己持有的锁时,那么这个请求会立刻成功,并且会将这个锁的计数值加1,而当线程退出同步代码块时,计数器将会递减,当计数值等于0时,锁释放。
// 第一次获得锁
synchronized(this) {
while(true) {
// 第二次获得同样的锁
synchronized(this) {
System.out.println("ReentrantLock!");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
class ReentrantLockTest {
public synchronized void a() {
}
public synchronized void b() {
}
/**
* 很明显的可重入锁用法
*/
public synchronized void all() {
this.a(); //此时对象的锁计数值已经达到2了
this.b(); }
}
6.6 CAS(比较并实现)
乐观锁的实现
锁分为两类:
• 悲观锁:synchronized是独占锁即悲观锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。
• 乐观锁:每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。
1.有三个值:一个当前内存值V、旧的预期值A、将更新的值B。先获取到内存
当中当前的内存值V,再将内存值V和原值A作比较,要是相等就修改为要修
改的值B并返回true,否则什么都不做,并返回false;
2.CAS是一组原子操作,不会被外部打断;
3.属于硬件级别的操作(利用CPU的CAS指令,同时借助JNI来完成的非阻塞算 法),效率比加锁操作高。
4.ABA问题:如果变量V初次读取的时候是A,并且在准备赋值的时候检查到它
仍然是A,那能说明它的值没有被其他线程修改过了吗?如果在这段期间曾
经被改成B,然后又改回A,那CAS操作就会误认为它从来没有被修改过。