本文转载自:https://blog.csdn.net/qq_41965731/article/details/88679710#commentBox
目录
线程的核心概念:
创建线程
代理对象
lambda简化线程
线程状态
线程方法
线程终止:
线程暂停——Sleep
Yield——礼让
Join——插队
线程的状态
优先级(Priority)
守护线程(Deamon)
多线程_并发_不同步三大经典案例
线程同步
synchronized关键字
性能分析
线程协作(cooperation)
高级主题
1.任务定时调度
2.quartz的使用
3.HappenBefore(指令重排)
4.volitale
5.单例模式(DCL)
Threadlocal
可重复锁
程序、进程、线程:
在操作系统中运行的程序就是进程,一个进程可以有多个线程
线程和进程的区别:
值得注意:我们所接触的多线程是模拟出来的,真正的多线程是在多个CPU中进行,即多核,如服务器,模拟出来的多线程是在一个CPU下进行,在CPU中同一时间只能执行之中代码,因为执行的快,所以就会用同时执行的感觉
package Thread;
public class Test1 {
public static void main(String[] args) {
Test1Test test = new Test1Test();
test.start(); //不是马上执行,而是交给CPU,即我准备好了,啥时候用看你
Test1Test1 test1 = new Test1Test1(); 、//实现类对象
//实现Runable接口必须借助Thread对象, Thread称为代理对象
new Thread(test1).start();
for(int i=0;i<5;i++){
System.out.println("我在看电影");
}
}
}
/**
* 继承Thread类来实现多线程
* 1.创建 2 启动
*/
class Test1Test extends Thread{
@Override
public void run() {
for(int i =0;i<5;i++){
System.out.println("我在听歌");
}
}
}
/**
* 实现Runable接口来实现多线程
*/
class Test1Test1 implements Runnable{
@Override
public void run() {
for(int i = 0; i<5;i++){
System.out.println("我在玩游戏");
}
}
}
注:因为每次CPU处理的不用,所以每次的执行结界不一定相等
例子:用多线程下载图片
package Thread;
/**
* 用继承Thread类的开启多线程下载图片
*/
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class Download {
public static void main(String[] args) {
new DowmThread("http://spider.nosdn.127.net/fdcfb76b8e77a120810190438002a34d.jpeg","src/Thread/艾薇儿1.gif").start();
new DowmThread("http://p4.yokacdn.com/pic/people/spotlight/2013/U454P41T8D265160F430DT20131029114943_maxw808.jpg","src/Thread/艾薇儿2.gif").start();
new DowmThread("http://p0.ifengimg.com/pmop/2018/0920/0AAB0969EAEB2FF4FB2AF960F6BA82695BC0C00B_size214_w1024_h768.jpeg","src/Thread/艾薇儿3.gif").start();
}
}
class DowmThread extends Thread{
private String url;
String name;
public DowmThread(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public void run() {
Down.dowwPicture(url,name);
System.out.println(name);
}
}
class Down{
public static void dowwPicture(String url, String name){
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
}
}
}
src/Thread/艾薇儿1.gif
src/Thread/艾薇儿3.gif
src/Thread/艾薇儿2.gif
package Thread;
/**
* 利用Runnable接口实现多窗口买票
* Runnble的优点:方便共享资源
*/
public class ticket {
public static void main(String[] args) {
//一份资源
TicketTest tt = new TicketTest();
//多个代理
new Thread(tt,"码畜").start();
new Thread(tt,"码奴").start();
new Thread(tt,"码农").start();
}
}
class TicketTest implements Runnable{
int ticketcount = 99;
public void run() {
while (true){
if(ticketcount>0){
try {
Thread.sleep(100); //模拟现实网络延迟,那么结果就会出现问题
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"===>"+ticketcount--);
}
else break;
}
}
}
例如婚庆公司,虽然主体还是以结婚双方,但婚庆公司参与了协助工作,即结婚双方为真实对象,而婚庆公司为代理对象
package Thread;
/**
* 静态代理
* 1.真实对象
* 2.代理对象
* 俩个对象实现同一个接口
*/
public class StaticPorxy {
public static void main(String[] args) {
new MarryCompony(new Couple()).happyMarry();
/**
* 同实现Runnable接口的开启方法是相一致的
* new Thread(线程对象).start();
*/
}
}
interface Marry{
void happyMarry();
}
//真实对象
class Couple implements Marry{
@Override
public void happyMarry() {
System.out.println("我们结婚啦");
}
}
//代理对象
class MarryCompony implements Marry{
//真实对象
Couple couple;
public MarryCompony(Couple couple) {
this.couple = couple;
}
@Override
public void happyMarry() {
ready();
couple.happyMarry();
after();
}
private void ready(){
System.out.println("准备工作......");
}
private void after(){
System.out.println("我们收钱啦......");
}
}
准备工作......
我们结婚啦
我们收钱啦......
package Thread;
public class LambdaThreadTest {
//静态内部类
static class things implements Runnable{
@Override
public void run() {
System.out.println("我在听歌..........");
}
}
public static void main(String[] args) {
//静态内部类
new Thread(new things()).start();
//局部内部类
class things implements Runnable{
@Override
public void run() {
System.out.println("我在听歌.......");
}
}
new Thread(new things()).start();
//匿名类(需要借助接口)
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我在听歌....");
}
}).start();
//Lamdba表达式(JDK8)
new Thread(()-> {
{
System.out.println("我在听歌.");
}
}).start();
//在简化
new Thread( ()-> System.out.println("我还在听歌") ).start();
}
}
我在听歌..........
我在听歌.......
我在听歌....
我在听歌.
我还在听歌
package Thread;
/**
* 终止线程:
* 1.程序正常执行完毕
* 2.外部干涉 ==》 加入标识
*/
public class StopThread {
public static void main(String[] args) {
StopThreadTest st = new StopThreadTest("纳尔");
new Thread(st).start();
for (int i = 0; i < 50; i++) {
if (i == 49)
st.stop();
System.out.println("我是=====>提莫");
}
}
}
class StopThreadTest implements Runnable {
private String name;
//标识
private boolean flag = true;
public StopThreadTest(String name) {
this.name = name;
}
@Override
public void run() {
while (flag) {
System.out.println("我是=====>" + name);
}
}
public void stop() {
flag = false;
}
}
package Thread;
public class Yield {
public static void main(String[] args) {
new Thread(() -> {
for (int i = 0; i < 50; i++){
System.out.println("我是乔峰——"+i);
}
}).start();
for(int i = 0; i <50; i++){
if(i%10 == 0)
Thread.yield();
System.out.println("我是虚竹——"+i);
}
}
}
注:因为礼让线程是让线程从运行状态转到就绪状态,即重新继续和其他线程抢夺CPU的使用权,具体翻谁牌子还是得看CPU
join合并线程,待此线程执 行完成后,再执行其他线 程,其他线程阻塞
package Thread;
public class Jion {
public static void main(String[] args) {
System.out.println("爸爸让儿子买烟的故事...");
new Thread(new JionFather()).start();
}
}
class JionFather implements Runnable {
@Override
public void run() {
System.out.println("想抽烟,没烟了,给儿子钱,让儿子去买");
Thread thread = new Thread(new JionSon());
thread.start();
System.out.println("等待儿子买烟回来....");
try {
thread.join(); //爸爸这个线程就被阻塞了
System.out.println("烟拿回来了,抽上烟的爸爸把儿子胖揍了一顿");
} catch (InterruptedException e) {
System.out.println("儿子走丢了");
}
}
}
class JionSon implements Runnable{
@Override
public void run() {
System.out.println("儿子拿钱去买烟了");
System.out.println("然而先去了网吧");
for(int i = 0; i < 5; i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i+1+"小时过去了.....");
}
System.out.println("赶紧区买烟,回家");
}
}
爸爸让儿子买烟的故事...
想抽烟,没烟了,给儿子钱,让儿子去买
等待儿子买烟回来....
儿子拿钱去买烟了
然而先去了网吧
1小时过去了.....
2小时过去了.....
3小时过去了.....
4小时过去了.....
5小时过去了.....
赶紧区买烟,回家
烟拿回来了,抽上烟的爸爸把儿子胖揍了一顿
Object.wait
。Object.wait
没有超时Thread.join
没有超时LockSupport.park
package Thread;
public class ThreadState {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(300);
System.out.println("...........");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
System.out.println(thread.getState()); //NEW
thread.start();
System.out.println(thread.getState()); //RUNNABLE
while (!thread.getState().equals(Thread.State.TERMINATED)){
try {
Thread.sleep(200);
System.out.println(thread.getState()); //TIMED_WAITING
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(thread.getState()); //TERMINATED
}
}
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程。线程调 度器按照线程的优先级决定应调度哪个线程来执行。 线程的优先级用数字表示,范围从1到10
• Thread.MIN_PRIORITY = 1
• Thread.MAX_PRIORITY = 10
• Thread.NORM_PRIORITY = 5
使用下述方法获得或设置线程对象的优先级。
package Thread;
public class ThreadPriority {
public static void main(String[] args) {
Thread thread1 = new Thread(new ThreadPriorityTest(),"码畜");
Thread thread2 = new Thread(new ThreadPriorityTest(),"码奴");
Thread thread3 = new Thread(new ThreadPriorityTest(),"码农");
Thread thread4 = new Thread(new ThreadPriorityTest(),"IT民工");
Thread thread5 = new Thread(new ThreadPriorityTest(),"IT工程师");
Thread thread6 = new Thread(new ThreadPriorityTest(),"IT人才");
//设置优先级
thread1.setPriority(Thread.MIN_PRIORITY); //1
thread2.setPriority(Thread.MIN_PRIORITY); //1
thread3.setPriority(Thread.MIN_PRIORITY); //1
thread4.setPriority(Thread.MAX_PRIORITY); //10
thread5.setPriority(Thread.MAX_PRIORITY); //10
thread6.setPriority(Thread.MAX_PRIORITY); //10
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread5.start();
thread6.start();
}
}
class ThreadPriorityTest implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getPriority()+"======>"+Thread.currentThread().getName());
}
}
优先级的设定建议在start()调用前
注意:优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高后调 用优先级低的线程。
package Thread;
public class ThreadDeamon {
public static void main(String[] args) {
ThreadDeamonMan tdm = new ThreadDeamonMan();
Thread thread1 = new Thread(tdm);
thread1.start();
}
}
class ThreadDeamonMan implements Runnable {
private int i=0;
@Override
public void run() {
while (true){
i++;
System.out.println(i);
}
}
}
1.卖票
package Thread;
/**
*
*/
public class ticket {
public static void main(String[] args) {
//一份资源
TicketTest tt = new TicketTest();
//多个代理
new Thread(tt,"码畜").start();
new Thread(tt,"码奴").start();
new Thread(tt,"码农").start();
}
}
class TicketTest implements Runnable{
int ticketcount = 10;
boolean flag =true;
public void run() {
while (flag){
vest();
}
}
public void vest(){
if(ticketcount<=0) {
flag = false;
return;
}else {
try {
Thread.sleep(100); //模拟现实网络延迟,那么结果就会出现问题
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"===>"+ticketcount--);
}
}
}
2。取钱
package Thread;
/**
* 线程不安全:取钱
*/
public class ThreadGetMoney {
public static void main(String[] args) {
Bank bank = new Bank("喜酒钱",100);
Thread you = new Thread(new ThreadGetMoneyTest(bank,80));
Thread he = new Thread(new ThreadGetMoneyTest(bank,70));
you.start();
he.start();
}
}
class Bank{
String name;
double money;
public Bank(String name, double money) {
this.name = name;
this.money = money;
}
}
class ThreadGetMoneyTest implements Runnable{
Bank bank;
double needMoney; //要取的钱
double pocketMoney; //口袋里的钱
public ThreadGetMoneyTest(Bank bank, double needMoney) {
this.bank = bank;
this.needMoney = needMoney;
}
@Override
public void run() {
if(bank.money - needMoney < 0.0){
return;
}
try {
Thread.sleep(1000); //取钱所用的时间
bank.money -= needMoney;
pocketMoney += needMoney;
System.out.println(Thread.currentThread().getName()+"取的钱数"+needMoney);
System.out.println(Thread.currentThread().getName()+"口袋里的钱"+pocketMoney);
System.out.println("账户里的钱"+bank.money);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3.操作容器
package Thread;
/**
* 线程不安全,操作容器(会存在数组的覆盖)
*/
import java.util.ArrayList;
import java.util.List;
public class ThreadArray {
public static void main(String[] args) {
List list = new ArrayList();
for(int i = 0; i < 10000; i++){
new Thread(() -> {
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(1000);
System.out.println(list.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
并发:同一个对象被多个线程用时操作
现实生活中,我们会遇到“同一个资源,多个人都想使用”的问题。 比 如:派发礼品,多个人都想获得。天然的解决办法就是,在礼品前,大 家排队。前一人领取完后,后一人再领取。处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改 这个对象。 这时候,我们就需要用到“线程同步”。 线程同步其实就 是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕后,下一个线程再使用。
由于同一进程的多个线程共享同一块存储空间。为了保证数据在方法中被访问时的正确性,在访问 时加入锁机制(synchronized),当一个线程获得对象的排它锁,独占资源, 其他线程必须等待,使用后释放锁即可。存在以下问题:
中间有一部分内容,电脑死机了,没了,哎。先留着吧
¥¥同步方法
public synchronized void method(int args) {}
synchronized 方法控制对“成员变量|类变量”对象的访问:每个 对象对应一把锁,每个 synchronized 方法都必须获得调用该方法 的对象的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占 该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获 得该锁,重新进入可执行状态。
缺陷:若将一个大的方法声明为synchronized 将会大大影响效率。
eg.以前面三大经典中的卖票为例
package Thread;
/**
* 同步方法
*/
public class ticket {
public static void main(String[] args) {
//一份资源
TicketTest tt = new TicketTest();
//多个代理
new Thread(tt,"码畜").start();
new Thread(tt,"码奴").start();
new Thread(tt,"码农").start();
}
}
class TicketTest implements Runnable{
int ticketcount = 10;
boolean flag =true;
public void run() {
while (flag){
vest();
}
}
//同步方法
public synchronized void vest(){
if(ticketcount<=0) {
flag = false;
return;
}else {
try {
Thread.sleep(100); //模拟现实网络延迟,那么结果就会出现问题
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"===>"+ticketcount--);
}
}
}
¥¥ 同步块:
eg.以前面三大经典中的操作容器为例
package Thread;
/**
*
* 使用同步块实现线程安全
*/
import java.util.ArrayList;
import java.util.List;
public class ThreadArray {
public static void main(String[] args) {
List list = new ArrayList();
for(int i = 0; i < 10000; i++){
new Thread(() -> {
//同步块
synchronized (list) {
list.add(Thread.currentThread().getName());
}
}).start();
}
try {
Thread.sleep(1000);
System.out.println(list.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
eg.以前面三大经典中的取钱为例
package Thread;
public class ThreadGetMoney {
public static void main(String[] args) {
Bank bank = new Bank("喜酒钱",100);
Thread you = new Thread(new ThreadGetMoneyTest(bank,80));
Thread he = new Thread(new ThreadGetMoneyTest(bank,70));
you.start();
he.start();
}
}
class Bank{
String name;
double money;
public Bank(String name, double money) {
this.name = name;
this.money = money;
}
}
class ThreadGetMoneyTest implements Runnable{
Bank bank;
double needMoney; //要取的钱
double pocketMoney; //口袋里的钱
public ThreadGetMoneyTest(Bank bank, double needMoney) {
this.bank = bank;
this.needMoney = needMoney;
}
@Override
public synchronized void run() {
test();
}
//目标应该锁定bank
public void test(){
//提高性能
if(bank.money <= 0){
return;
}
synchronized (bank) {
if (bank.money - needMoney < 0.0) {
return;
}
try {
Thread.sleep(1000); //取钱所用的时间
bank.money -= needMoney;
pocketMoney += needMoney;
System.out.println(Thread.currentThread().getName() + "取的钱数" + needMoney);
System.out.println(Thread.currentThread().getName() + "口袋里的钱" + pocketMoney);
System.out.println("账户里的钱" + bank.money);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
性能分析
package Thread;
/**
*
*/
public class ticket {
public static void main(String[] args) {
//一份资源
TicketTest tt = new TicketTest();
//多个代理
new Thread(tt,"码畜").start();
new Thread(tt,"码奴").start();
new Thread(tt,"码农").start();
}
}
class TicketTest implements Runnable{
int ticketcount = 10;
boolean flag =true;
public void run() {
while (flag){
vest5();
}
}
//尽可能锁定合理的范围(不是指代码,指的是代码的完整性)
public void vest5(){
if (ticketcount <= 0) { //考虑到的是没有票的情况(没有票的情况很多)
flag = false;
return; //这被称作双重检测
}
synchronized(this) {
if (ticketcount <= 0) { //考虑最后一张票
flag = false;
return;
}
try {
Thread.sleep(100); //模拟现实网络延迟,那么结果就会出现问题
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "===>" + ticketcount--);
}
}
//线程不安全,范围太小锁不住
public void vest4(){
synchronized(this) {
if (ticketcount <= 0) {
flag = false;
return;
}
}
try {
Thread.sleep(100); //模拟现实网络延迟,那么结果就会出现问题
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "===>" + ticketcount--);
}
//线程不安全 ticketcount对象在变
//synchronized是要锁一个不变的对象
public void vest3(){
synchronized((Integer)ticketcount) {
if (ticketcount <= 0) {
flag = false;
return;
} else {
try {
Thread.sleep(100); //模拟现实网络延迟,那么结果就会出现问题
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "===>" + ticketcount--);
}
}
}
//同步块 范围太大——》性能底下
public void vest2(){
synchronized(this) {
if (ticketcount <= 0) {
flag = false;
return;
} else {
try {
Thread.sleep(100); //模拟现实网络延迟,那么结果就会出现问题
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "===>" + ticketcount--);
}
}
}
//线程安全:同步方法
public synchronized void vest1(){
if(ticketcount<=0) {
flag = false;
return;
}else {
try {
Thread.sleep(100); //模拟现实网络延迟,那么结果就会出现问题
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"===>"+ticketcount--);
}
}
}
应用场景:生产者和消费者问题
分析:这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消 费者之间相互依赖,互为条件
解决方法
package Thread.day2;
import javax.swing.plaf.metal.MetalBorders;
/**
* 协作模型:生产者和消费者——管程法
* 借助数组(管道)
*/
//模拟生产者、消费者、和包子之间的故事
public class test0 {
public static void main(String[] args) {
Buffer buffer = new Buffer();
new Thread(new Producer(buffer)).start();
new Thread(new Customer(buffer)).start();
}
}
//生产者
class Producer implements Runnable {
SteamedBun steamedBun;
Buffer buffer;
public Producer(Buffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 5 == 0) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("生产者正在生产第===" + i + "个包子");
buffer.push(new SteamedBun(i));
}
}
}
//消费者
class Customer implements Runnable {
Buffer buffer;
public Customer(Buffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("消费者买了第===" + buffer.get().getId() + "个包子");
}
}
}
//缓存区,也就是控制我生产的量
class Buffer {
int count = 0; //计数器
SteamedBun[] buffer = new SteamedBun[11];
//生产馒头
public synchronized void push(SteamedBun steamedBun) {
if (count == 10) {
try {
//容器已满,等候消费者处理
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
buffer[count++] = steamedBun;
//容器里又加入有馒头,提醒消费者前来处理
this.notifyAll();
}
//获取馒头
public synchronized SteamedBun get() {
if (count == 0) {
try {
//容器里没有馒头,只得等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//容器里的馒头没有满,唤醒生产者继续生产
this.notifyAll();
return buffer[--count];
}
}
//馒头
class SteamedBun {
private int id;
public SteamedBun(int id) {
this.id = id;
}
public int getId() {
return id;
}
public SteamedBun() {
}
}
下面这个例子不仅描述了信号灯法,而且告诉我们,搞好一件事情的重要性
package Thread.day2;
/**
* 生产者与消费者模型——信号灯法
* 设置标志位
*/
public class test {
public static void main(String[] args) {
Kungfu kungfu = new Kungfu();
new Thread(new Attack(kungfu)).start();
new Thread(new Defense(kungfu)).start();
}
}
//进攻者
class Attack implements Runnable{
Kungfu kungfu;
private String[] kungfus = {"打狗棒","葵花点穴手","降龙十八掌","黯然销魂掌","玉女心经"};
public Attack(Kungfu kungfu) {
this.kungfu = kungfu;
}
@Override
public void run() {
for(int i =0; i < 10; i++){
kungfu.attack(kungfus[(int)(Math.floor(Math.random()*kungfus.length))]);
}
}
}
//防守者
class Defense implements Runnable{
Kungfu kungfu;
public Defense(Kungfu kungfu) {
this.kungfu = kungfu;
}
@Override
public void run() {
for(int i =0; i < 10; i++){
kungfu.defense();
}
}
}
//武功
class Kungfu {
//招式
String sonicSlash;
boolean flag = true;
public synchronized void attack(String sonicSlash){
if(!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("进攻者使用出了招式===》"+sonicSlash);
//唤醒防守者防守
this.notifyAll();
flag = !flag;
}
public synchronized void defense(){
if(flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("防守者使出了龟壳神功,并大笑到,谁能伤我");
//唤醒防守者防守
this.notifyAll();
flag = !flag;
}
}
进攻者使用出了招式===》黯然销魂掌
防守者使出了龟壳神功,并大笑到,谁能伤我
进攻者使用出了招式===》葵花点穴手
防守者使出了龟壳神功,并大笑到,谁能伤我
进攻者使用出了招式===》打狗棒
防守者使出了龟壳神功,并大笑到,谁能伤我
进攻者使用出了招式===》黯然销魂掌
防守者使出了龟壳神功,并大笑到,谁能伤我
进攻者使用出了招式===》打狗棒
防守者使出了龟壳神功,并大笑到,谁能伤我
进攻者使用出了招式===》葵花点穴手
防守者使出了龟壳神功,并大笑到,谁能伤我
进攻者使用出了招式===》降龙十八掌
防守者使出了龟壳神功,并大笑到,谁能伤我
进攻者使用出了招式===》降龙十八掌
防守者使出了龟壳神功,并大笑到,谁能伤我
进攻者使用出了招式===》黯然销魂掌
防守者使出了龟壳神功,并大笑到,谁能伤我
进攻者使用出了招式===》打狗棒
防守者使出了龟壳神功,并大笑到,谁能伤我
Java提供了三种解决了线程间信息通信的问题
package Thread.day2;
import java.util.Date;
import java.util.TimerTask;
import java.util.Timer;
public class TimerTest {
public static void main(String[] args) {
Timer time = new Timer();
// time.schedule(new Work(),3000); 3秒后执行一次
// time.schedule(new Work(),2000,1000); 重复执行
time.schedule(new Work(),new Date(2050,5,20));
}
}
class Work extends TimerTask {
@Override
public void run() {
for(int i = 0; i < 3; i++){
System.out.println("我爱学习,无法自拔,玩游戏没意思");
}
System.out.println("真香");
}
}
quartz的使用
你写的代码很可能根本没按你期望的顺序执行,因为编译器和 CPU 会尝 试重排指令使得代码更快地运行。
执行代码的顺序可能与编写代码不一致,即虚拟机优化代码顺序,则为指令重排 happen-before 即:编译器或运行时环境为了优化程序性能而采取的对指令进行重新 排序执行的一种手段
数据依赖
如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个 操作之间就存在数据依赖。数据依赖分下列三种类型:
上面三种情况,只要重排序两个操作的执行顺序,程序的执行结果将会被改 变。所以,编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理 器不会改变存在数据依赖关系的两个操作的执行顺序。
package Thread.day2;
public class HappenBefore {
//变量1
private static int a = 0;
//变量2
private static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
Thread t1,t2;
for(int i = 0; i < 100; i++) {
a = 0;
flag = false;
//线程1:更改数据
t1 = new Thread(() -> {
a = 1;
flag = true;
});
//线程2:读取数据
t2 = new Thread(() -> {
if (flag) {
a *= 1;
}
//存在指令重排
if (a == 0)
System.out.println("happpen Before ===>" + a);
});
t1.start();
t2.start();
//合成线程
t1.join();
t2.join();
}
}
}
happpen Before ===>0
happpen Before ===>0
happpen Before ===>0
happpen Before ===>0
happpen Before ===>1
happpen Before ===>0
happpen Before ===>0
happpen Before ===>1
happpen Before ===>1
volatile保证线程间变量的可见性,简单地说就是当线程A对变量X进行了修改后,在线 程A后面执行的其他线程能看到变量X的变动,更详细地说是要符合以下两个规则:
各线程的工作内存间彼此独立、互不可见,在线程启动的时候,虚拟机为每个内存分配一 块工作内存,不仅包含了线程内部定义的局部变量,也包含了线程所需要使用的共享变量 (非线程内构造的对象)的副本,即为了提高执行效率。
volatile是不错的机制,但是volatile不能保证原子性。
package Thread.day3;
/**
* DCL单例模式:套路==》在多线程环境下,对外存在一个对象
* 1.构造器私有化——》避免外部new对象
* 2.提供私有的静态属性——》存储对象的地址
* 3.提供共有的静态方法——》获取地址
*/
public class Danli {
public static void main(String[] args) {
System.out.println("执行DanliTest的结果:");
new Thread(() ->{
System.out.println(DanliTest.getInstance());
}).start();
System.out.println(DanliTest.getInstance());
System.out.println("执行DanliTest2的结果:");
new Thread(() ->{
System.out.println(DanliTest2.getInstance());
}).start();
System.out.println(DanliTest2.getInstance());
}
}
class DanliTest {
//2.提供私有静态熟悉
private static volatile DanliTest dl;
//没有volatile,其他线程可能会访问一个没有初始化的对象
//1.构造器私有化
private DanliTest() {
}
public static DanliTest getInstance(){
//再次检测(双重检测)
if(null != dl) //避免不必要的同步,已经存在对象
return dl;
synchronized (DanliTest.class) {
if (null == dl)
dl = new DanliTest();
//new一个对象的步骤:1.开辟空间,2.初始化对象信息,3.返回对象的地址给引用
//new对象中初始化对象信息比较耗时,慢,可能会出现指令重排,先返回对象的地址,
//可能会出现A线程还在初始化对象信息,B线程就已经拿走了对象的引用(空的对象)
}
return dl;
}
}
class DanliTest2 {
private static volatile DanliTest2 dl;
private DanliTest2() {
}
public static DanliTest2 getInstance(){
if (null == dl)
dl = new DanliTest2();
return dl;
}
}
执行DanliTest的结果:
Thread.day3.DanliTest@4b9385
Thread.day3.DanliTest@4b9385
执行DanliTest2的结果:
Thread.day3.DanliTest2@2a0b20
Thread.day3.DanliTest2@14827d5
package Thread.day3;
/**
* ThreadLocal:每个线程自身的存储本地,局部区域
* get、set、initialValue
*/
public class Threadlocal {
private static ThreadLocal th = new ThreadLocal<>();
//更改初始化的值
/* private static ThreadLocal th = new ThreadLocal(){
@Override
protected Integer initialValue() {
return 200;
}
};*/
//java 1.8后
/*private static ThreadLocal th = ThreadLocal.withInitial(() ->{return 200;});
private static ThreadLocal th = ThreadLocal.withInitial(() -> 200);*/
public static void main(String[] args) {
//获取值
System.out.println(Thread.currentThread().getName()+"===>"+th.get());
//设置值
th.set(99);
System.out.println(Thread.currentThread().getName()+"===>"+th.get());
new Thread(new Myrun()).start();
new Thread(new Myrun()).start();
new Thread(new Myrun()).start();
}
static class Myrun implements Runnable{
@Override
public void run() {
//设置一个1-100的随机数
th.set((int)(Math.random()*100+1));
System.out.println(Thread.currentThread().getName()+"===>"+th.get());
}
}
}
main===>null
main===>99
Thread-2===>31
Thread-0===>69
Thread-1===>57
package Thread.day3;
/**
* ThreadLcoal 分析上下文
* 1.构造器:哪里调用就属于哪里
* 2.run:属于本线程自身
*/
public class Threadlocal1 {
private static ThreadLocal th = new ThreadLocal<>();
public static void main(String[] args) {
new Thread(new Myrun1()).start();
}
static class Myrun1 implements Runnable{
//属于main方法的区域
public Myrun1() {
System.out.println(Thread.currentThread().getName()+"===>"+th.get());
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"===>"+th.get());
}
}
}
main===>null
Thread-0===>null
锁作为并发共享数据保证一致性的工具,大多数内置锁都是可重入的,也就是 说,如果某个线程试图获取一个已经由它自己持有的锁时,那么这个请求会立 刻成功,并且会将这个锁的计数值加1,而当线程退出同步代码块时,计数器 将会递减,当计数值等于0时,锁释放。如果没有可重入锁的支持,在第二次 企图获得锁时将会进入死锁状态。可重入锁随处可见
eg:手动实现不可重复锁
package Thread.day3;
/**
* 不可重入锁:所不可以延续使用
*/
public class Lock1 {
public static LockTest lt = new LockTest();
public static void aa() throws InterruptedException {
lt.lock();
bb();
lt.unlock();
}
public static void bb() throws InterruptedException {
lt.lock();
System.out.println("......");
lt.unlock();
}
public static void main(String[] args) throws InterruptedException {
aa();
}
}
//锁
class LockTest {
private boolean flag = false;
//获取锁
public synchronized void lock() throws InterruptedException {
while (flag == true){
wait();
}
flag = true;
}
//释放锁
public synchronized void unlock() throws InterruptedException {
flag = false;
notify();
}
}
eg:手动实现可重复锁
package Thread.day3;
/**
* 可重入锁:所可以延续使用
*/
public class ReLock {
public static LockTest1 lt = new LockTest1();
public static void aa() throws InterruptedException {
lt.lock();
System.out.println(lt.getCount());
bb();
lt.unlock();
Thread.sleep(100);
System.out.println(lt.getCount());
}
public static void bb() throws InterruptedException {
lt.lock();
System.out.println(lt.getCount());
lt.unlock();
System.out.println(lt.getCount());
}
public static void main(String[] args) throws InterruptedException {
aa();
}
}
class LockTest1 {
private boolean flag = false;
private Thread thread = null; //当前线程
private int count = 0; //计数器
public int getCount() {
return count;
}
//获取锁
public synchronized void lock() throws InterruptedException {
Thread t = Thread.currentThread();
while (flag == true && t!=thread){
wait();
}
count++;
thread = t;
flag = true;
}
//释放锁
public synchronized void unlock() throws InterruptedException {
if(Thread.currentThread() == thread){
count--;
if(count == 0){
flag = false;
notify();
thread = null;
}
}
}
}
1
2
1
0
通过封装好的类(ReentrantLockTest)
package Thread.day3;
import java.util.concurrent.locks.ReentrantLock;
/**
* 可重入锁:所可以延续使用
*/
public class ReLock {
public static ReentrantLock lt = new ReentrantLock();
public static void aa() throws InterruptedException {
System.out.println(lt.getHoldCount());
lt.lock();
System.out.println(lt.getHoldCount());
bb();
lt.unlock();
Thread.sleep(100);
System.out.println(lt.getHoldCount());
}
public static void bb() throws InterruptedException {
lt.lock();
System.out.println(lt.getHoldCount());
lt.unlock();
System.out.println(lt.getHoldCount());
}
public static void main(String[] args) throws InterruptedException {
aa();
}
}