Day23JavaSE——多线程&实现
文章目录
多线程
进程的概述和多进程的意义
线程的概述和多线程的意义
JVM运行原理以及JVM启动的线程探讨
实现多线程
线程调度
线程控制
进程的概述和多进程的意义
//我们要学习线程,先要了解进程
//线程依赖于进程存在
//进程:正在运行的应用程序(QQ,微信,IDEA)
//现在的计算机支持多进程,可以运行多个进程
//开启进程后,会执行很多任务,这个任务称之为线程
//比如QQ音乐开启后,其实就是一个进程,其中个下载,听音乐都是QQ音乐下的线程
//在一个进程内部又可以执行多个任务,而这每一个任务我们就可以看成是一个线程。是程序使用CPU的基本单位。
//所以,进程是拥有资源的基本单位, 线程是CPU调度的基本单位。
//因为CPU在某个时间点上只能做一件事情,计算机是在游戏进程和音乐进程间做着频繁切换,且切换速度很快,
//所以,我们感觉游戏和音乐在同时进行,其实并不是同时执行的。多进程的作用不是提高执行速度,而是提高CPU的使用率。
/** 多线程的意义
* 多线程的作用不是提高执行速度,而是为了提高应用程序的使用率。
* 那么怎么理解这个问题呢?
* 我们程序在运行的使用,都是在抢CPU的时间片(执行权),如果是多线程的程序,那么在抢到
* CPU的执行权的概率应该比较单线程程序抢到的概率要大.那么也就是说,CPU在多线程程序
* 中执行的时间要比单线程多,所以就提高了程序的使用率.但是即使是多线程程序,那么他们
* 中的哪个线程能抢占到CPU的资源呢,这个是不确定的,所以多线程具有随机性.*/
并行和并发的区别
/** 并行和并发
* 并发是逻辑上同时发生,指在某一个时间内同时运行多个程序。
* 指应用能够同时执行不同的任务, 例:吃饭的时候可以边吃饭边打电话, 这两件事情可以同时执行
*
* 并行是物理上同时发生,指在某一个时间点同时运行多个程序。
* 指应用能够交替执行不同的任务, 其实并发有点类似于多线程的原理,
多线程并非是如果你开两个线程同时执行多个任务。
* 执行, 就是在你几乎不可能察觉到的速度不断去切换这两个任务, 已达到"同时执行效果",
* 其实并不是的, 只是计算机的速度太快, 我们无法察觉到而已.
* 就类似于你, 吃一口饭喝一口水, 以正常速度来看, 完全能够看的出来
* 当你把这个过程以n倍速度执行时.可以想象一下.*/
Java程序运行原理和JVM的启动是多线程的吗
/** JVM的启动与运行原理
* Java程序运行原理
* Java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。
* 该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。
* 所以 main方法运行在主线程中。
* JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。*/
多线程实现
/** 多线程实现的方式
* 由于线程是依赖进程而存在的,所以我们应该先创建一个进程出来。
* 而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程。
* 但是Java是不能直接调用系统功能的,所以,我们没有办法直接实现多线程程序。
* 但是呢?Java可以去调用C/C++写好的程序来实现多线程程序。
* 由C/C++去调用系统功能创建进程,然后由Java去调用这样的东西,
* 然后提供一些类供我们使用。我们就可以实现多线程程序了。*/
//Java给我们提供好了一个类 Thread 通过这个类,进创建线程,和开启线程。
//Thread
//线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。
//我们如何使用Thread这个类来创建子线程。
方式一
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VTrTEUCs-1592322948568)(https://s1.ax1x.com/2020/06/13/tXqZ7j.png)]
//自定义线程需要继承Java提供的Thread类
public class MyThread extends Thread{
//ctrl+O c重写父类的里面的方法
//run()方法是让线程来调用的方法,不能直接调用
@Override
public void run() {
//run 方法里面写的代码就是子线程执行的耗时代码
//一些耗时且不会影响主线程下一步执行的代码就会被写入子线程
//模拟耗时操作
for (int i = 0; i < 10; i++) {
System.out.println(this.getName()+"--"+i);
}
}
}
/* 创建新执行线程有两种方法。
方法1:
1.将一个类声明为 Thread 的子类。
2.该子类应重写 Thread 类的 run 方法。
3.接下来可以分配并启动该子类的实例。*/
System.out.println("当主线程执行到这个节点处,可能我会有一个耗时的操作,比如复制文件,在这个节点处开启一个子线程来执行耗时的代码");
//开启线程 调用run()方法,其实子线程并没有开启,这种方式其实就是你创建了一个类的对象,调用了类中的一个 方法而已。
//myThread.run();
//创建一个线程
MyThread myThread = new MyThread();
MyThread myThread1 = new MyThread();
System.out.println("主线程执行了");
//正确开启一个线程的方式是调用 start();当线程开启后,由子线程去调用run()方法来执行run()方法里面的代码
//线程不要重复开启 重复开启会抛异常。IllegalThreadStateException
myThread.start();
System.out.println("主线程执行了");
System.out.println("主线程执行了");
System.out.println("主线程执行了");
System.out.println("主线程执行了");
System.out.println("主线程执行了");
myThread1.start();
System.out.println("主线程执行了");
}
}
public class Test2 {
public static void main(String[] args) {
System.out.println("===========获取当前执行线程对象===========");
//currentThread(); 获取当前执行的线程对象
Thread thread = Thread.currentThread();
System.out.println(thread);
System.out.println("=========设置当前执行线程对象名称=========");
thread.setName("主线程");
System.out.println("============获取当前执行线程名===========");
String name = thread.getName();
System.out.println(name);
//多个线程并发执行()多个抢占CPU的执行权,哪个线程抢到,在某一个时刻,就会执行哪个线程。线程的执行具有随机性。
MyThread th1 = new MyThread();
MyThread th2 = new MyThread();
MyThread th3 = new MyThread();
th1.setName("小王");
th2.setName("Tom");
th3.setName("Adair");
th1.start();
th2.start();
th3.start();
}
}
案例利用多线程复制文件
分别创建子线程复制MP3文件和MP4文件,都要继承Thread类
public class copyMp3 extends Thread {
@Override
public void run() {
FileInputStream in = null;
FileOutputStream out = null;
try {
in = new FileInputStream("C:\\Users\\adair_liu\\Desktop\\复试资料\\Jam - 七月上.mp3");
out = new FileOutputStream("多线程复制MP3.mp3");
byte[] buffer = new byte[1024 * 8];
int len = 0;
while ((len = in.read(buffer)) != -1) {
out.write(buffer);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try{
if(in!=null){
in.close();
}
if(out!=null){
out.close();
}
}catch (IOException e){
e.printStackTrace();
}
}
}
}
public class copyVideo extends Thread{
@Override
public void run() {
FileInputStream in = null;
FileOutputStream out = null;
try {
in = new FileInputStream("C:\\Users\\adair_liu\\Desktop\\复试资料\\[迅雷下载Www.99b.Cc]入殓师1280x720日语中字版.mp4");
out = new FileOutputStream("多线程复制MP4.mp4");
byte[] buffer = new byte[1024 * 20];
int len = 0;
while ((len = in.read(buffer)) != -1) {
out.write(buffer);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try{
if(in!=null){
in.close();
}
if(out!=null){
out.close();
}
}catch (IOException e){
e.printStackTrace();
}
}
}
}
主线程
public class Test1 {
public static void main(String[] args) {
System.out.println("主线程代码执行");
copyMp3 copyMp3 = new copyMp3();
copyVideo copyVideo = new copyVideo();
copyMp3.setName("复制音频");
copyVideo.setName("复制视频");
copyMp3.start();
copyVideo.start();
System.out.println("主线程下面的代码");
}
}
线程优先级问题
//自定义线程需要继承Java提供的Thread类
public class MyThread extends Thread{
//ctrl+O c重写父类的里面的方法
//run()方法是让线程来调用的方法,不能直接调用
@Override
public void run() {
//run 方法里面写的代码就是子线程执行的耗时代码
//一些耗时且不会影响主线程下一步执行的代码就会被写入子线程
//模拟耗时操作
for (int i = 0; i < 10; i++) {
System.out.println(this.getName()+"--"+i);
}
}
}
/**
* 如我们的计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,
* 线程只有得到 CPU时间片,也就是使用权,才可以执行指令。那么Java是如何对线程进行调用的呢?
* B:线程有两种调度模型:
* 分时调度模型 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
* 抢占式调度模型 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,
* 优先级高的线程获取的 CPU 时间片相对多一些。
* Java使用的是抢占式调度模型。
* C:如何设置和获取线程优先级
* public final int getPriority() //获取线程的优先级
* public final void setPriority(int newPriority)//设置线程的优先级*/
/**
* 注意事项: 有的时候我们给线程设置了指定的优先级,但是该线程并不是按照优先级高的线程执行,那是为什么呢?
* - 因为线程的优先级的大小仅仅表示这个线程被CPU执行的概率增大了.但是我们都知道多线程具有随机性,
* - 所以有的时候一两次的运行说明不了问题*/
public static void main(String[] args) {
MyThread th1 = new MyThread();
MyThread th2 = new MyThread();
MyThread th3 = new MyThread();
//设置线程的优先级 范围1--10
th1.setPriority(1);
th2.setPriority(2);
th3.setPriority(Thread.MAX_PRIORITY);
//多个线程并发执行()多个抢占CPU的执行权,哪个线程抢到,在某一个时刻,就会执行哪个线程。线程的执行具有随机性。
//获取线程的优先级
int priority1 = th1.getPriority();
int priority2 = th2.getPriority();
int priority3= th3.getPriority();
//线程默认的优先级是5
System.out.println("线程的优先级"+ priority1);
System.out.println("线程的优先级" + priority2);
System.out.println("线程的优先级" + priority3);
th1.setName("范冰冰");
th2.setName("刘亦菲");
th3.setName("张曼玉");
th1.start();
th2.start();
th3.start();
}
}
线程控制
//自定义线程需要继承Java提供的Thread类
public class MyThread extends Thread{
public MyThread(){}
public MyThread(String name){
super(name);
}
//ctrl+O c重写父类的里面的方法
//run()方法是让线程来调用的方法,不能直接调用
@Override
public void run() {
//run 方法里面写的代码就是子线程执行的耗时代码
//一些耗时且不会影响主线程下一步执行的代码就会被写入子线程
//模拟耗时操作
for (int i = 0; i < 10; i++) {
String name = Thread.currentThread().getName();
System.out.println(name+"--"+i);
}
}
}
public class MyThread1 extends Thread{
public MyThread1(){}
public MyThread1(String name){
super(name);
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
String name = Thread.currentThread().getName();
//线程礼让
Thread.yield();
System.out.println(name+"--"+i);
}
}
}
public class Test1 {
public static void main(String[] args) throws InterruptedException {
System.out.println(("=========休眠线程========="));
/**
* static void sleep(long millis)
* 使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。
* static void sleep(long millis, int nanos)
* 导致正在执行的线程以指定的毫秒数加上指定的纳秒数来暂停(临时停止执行),这取决于系统定时器和调度器的精度和准确性。*/
System.out.println("主线程开始执行了");
Thread.sleep(5000);
System.out.println("主线程下面的代码");
//线程休眠:可以让当前正在执行的线程, 睡一会。
//Thread(String name) 分配新的 Thread 对象。
//通过有参构造,可以给线程起个名字
MyThread tha = new MyThread("线程A");
MyThread thb = new MyThread("线程B");
tha.start();
thb.start();
System.out.println("=========加入线程=========");
/**A:
* 加入线程:
* public final void join ()
* 意思就是:等待该线程执行完毕了以后, 其他线程才能再次执行
* 注意事项:
* 在线程启动之后, 在调用方法
* join ()可以让多个线程并发执行,变成串行(挨个排队执行,不用抢)*/
MyThread th1 = new MyThread("小张");
MyThread th2 = new MyThread("小王");
MyThread th3 = new MyThread("小孙");
th1.start();
//在线程启动之后,再调用join()方法
th1.join();
th2.start();
th2.join();
th3.start();
th3.join();
System.out.println("=========礼让线程=========");
/**
* static void yield()
* 暂停当前正在执行的线程对象,并执行其他线程。*/
//在自定义线程中调用
Thread thLR = new MyThread1("礼让的线程");
thLR.start();
System.out.println("=========守护线程=========");
/**
* void setDaemon(boolean on)
* 将该线程标记为守护线程或用户线程。*/
Thread.currentThread().setName("刘备:主线程");
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"=="+i);
}
MyThread th4 = new MyThread();
MyThread th5 = new MyThread();
th4.setName("张飞");
th5.setName("关羽");
//设置为守护线程 当主线程死亡后,守护线程要立马死亡掉。
//注意:setDaemon(true)该方法必须在启动线程前调用。
th4.setDaemon(true);
th5.setDaemon(true);
th4.start();
th5.start();
System.out.println(Thread.currentThread().getName()+"退出了");
System.out.println("=========中断线程=========");
/**
* void interrupt()
* 中断线程。*/
MyThread th6 = new MyThread();
th6.setName("停止线程");
th6.start();
Thread.sleep(5000);
//让线程死亡
//清除线程的阻塞状态
th6.interrupt();
}
}
实现多线程方式二(解决了Java中只能单继承问题)
//创建线程的第二种方式
/*
* 1、定义一个类实现Runnable接口
* 2、重写该接口的run方法
* 3、然后就可以分配该类的实例
* 4、在创建Thread对象时作为一个参数来传递并启动*/
//这种方式扩展性强,实现一个接口,还可以再去继承其他类
//可以避免由于Java单继承带来的局限性。
/*Thread(Runnable target)
分配新的 Thread 对象。*/
//Runnable 任务 接口应该由那些打算通过某一线程执行其实例的类来实现
让线程实现一个Runnable接口
public class MyRunnable implements Runnable{
//让线程来执行的方法
@Override
public void run() {
for (int i = 0; i < 10; i++) {
Thread th = Thread.currentThread();
System.out.println(th.getName() + "===" + i);
}
}
}
public class MyRunnable2 implements Runnable{
@Override
public void run() {
System.out.println("另外一个任务");
}
}
public class Test1 {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread th1 = new Thread(myRunnable);
//另外的Runnable类就是另一个任务,不会执行
Thread th2 = new Thread(new MyRunnable2());
Thread th3 = new Thread(new MyRunnable());
Thread th4 = new Thread(myRunnable);
th1.setName("线程A");
th2.setName("线程B");
th3.setName("线程C");
th4.setName("线程D");
th1.start();
th2.start();
th3.start();
th4.start();
}
}
public class Test2 {
public static void main(String[] args) {
/**
* Thread(Runnable target, String name)
* 分配新的 Thread 对象。*/
MyRunnable myRunnable = new MyRunnable();
Thread tha = new Thread(myRunnable, "线程A");
Thread thb = new Thread(myRunnable, "线程B");
tha.start();
thb.start();
}
}
实现多线程方式3
//创建线程的第三种方式
/* C:
实现步骤
1. 创建一个类实现Callable 接口 重写接口中的call方法
2. 创建一个FutureTask类将Callable接口的子类对象作为参数传进去
3. 创建Thread类, 将FutureTask对象作为参数传进去
4. 开启线程*/
执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。
FutureTask 是 Future 接口的实现类
/** FutureTask类创建了一个任务,这个任务在执行时执行传入的Callable或Runnable
* 再将这个任务传入线程中
* FutureTask(Callable<V> callable)
* 创建一个 FutureTask ,它将在运行时执行给定的 Callable 。
* FutureTask(Runnable runnable, V result)
* 创建一个 FutureTask ,将在运行时执行给定的 Runnable ,并安排 get将在成功完成后返回给定的结果。 */
public class Test1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//我们有一种需求:当子线程执行完,我想获取子线程执行完之后的结果。
MyCallable myCallable = new MyCallable(100);
//创建一个任务
FutureTask<Integer> task = new FutureTask<>(myCallable);
Thread th = new Thread(task);
th.start();
//获取线程执行完之后返回的结果
Integer integer = task.get();
System.out.println(integer);
MyCallable myCallable1 = new MyCallable(1000);
FutureTask<Integer> task1 = new FutureTask<>(myCallable1);
Thread th2 = new Thread(task1);
th2.start();
System.out.println(task1.get());
//Runnable 任务 让线程来执行 run() 没有返回值,这个方法不能抛出异常,只能抓
//Callable 任务 让线程来执行 call() 有返回值,而且可以抛出异常。
}
}
public class MyCallable implements Callable<Integer> {
private int num;
public MyCallable(int num){this.num=num;}
//call()方法 让线程来执行的方法
@Override
public Integer call() throws Exception {
//求1-num之间的和
int sum=0;
for (int i = 0; i < num; i++) {
sum+=i;
}
return sum;
}
}
卖电影票案例
继承Thread类的方式卖电影票案例
public class CellThread extends Thread{
//设置成员变量,让三个线程共享
static int ticket = 100;
public CellThread(String name){
super(name);
}
@Override
public void run() {
while (true) {
if(ticket>=1){
System.out.println(this.getName() + "正在出售第:" + (ticket--) + "张票");
}
}
}
}
public class Test1 {
public static void main(String[] args) {
/*A:
案例演示
需求:某电影院目前正在上映贺岁大片,共有100张票,
而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
通过继承Thread类实现*/
//三个串口并发执行售票
CellThread win1 = new CellThread("窗口1");
CellThread win2 = new CellThread("窗口2");
CellThread win3 = new CellThread("窗口3");
win1.start();
win2.start();
win3.start();
}
}
实现Runnable接口的方式卖电影票
public class CellRunnable implements Runnable{
static int ticket = 100;
@Override
public void run() {
while (true) {
if(ticket>=1){
System.out.println(Thread.currentThread().getName() + "正在出售第:" + (ticket--) + "张票");
}
}
}
}
public class Test2 {
public static void main(String[] args) {
/*A:
案例演示
需求:某电影院目前正在上映贺岁大片,共有100张票,
而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
通过Runnable接口实现*/
CellRunnable cellRunnable = new CellRunnable();
Thread th1 = new Thread(cellRunnable, "窗口1");
Thread th2 = new Thread(cellRunnable, "窗口2");
Thread th3 = new Thread(cellRunnable, "窗口3");
th1.start();
th2.start();
th3.start();
}
}
## 买电影票出现了同票和负数票的原因分析
public class Test3 {
public static void main(String[] args) {
//创建了一次任务 100
CellRunnable cellRunnable = new CellRunnable();
Thread th1 = new Thread(cellRunnable, "窗口1");
Thread th2 = new Thread(cellRunnable, "窗口2");
Thread th3= new Thread(cellRunnable, "窗口3");
th1.start();
th2.start();
th3.start();
//窗口1正在出售第:-1张票
//输出了-1张票
//我们写的这段售票代码,出现了线程安全问题。多线程的环境下,在对共享数据进行操作时,有可能会出现线程安全问题。
//为什么会出现线程安全问题,以及我们如何来解决他,且听下回分解。
}
}
public class CellRunnable implements Runnable{
static int ticket = 100;
@Override
public void run() {
while (true) {
if(ticket>=1){
//模拟一下真实的售票环境,有网络延迟。
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第:" + (ticket--) + "张票");
}
}
}
}
线程安全问题
//线程安全问题,出现线程安全问题,要符合以下三个条件
//1.是否是多线程环境
//2.多条线程,有没有共享数据。
//3.有没有多条语句去操作这个共享数据。
//出现线程安全问题,怎么解决。
// synchronized (锁对象){需要同步的代码} 锁对象:是Java里面的任意一个对象,多个线程要共用一把锁对象。
//同步方法 用的锁对象是this
//静态同步方法 用的锁对象是 字节码对象。
//Lock 这个类中 提供有 lock() unlock() 进行加锁与释放锁。
//死锁现象。
生产消费者线程案例
public class Test1 {
public static void main(String[] args) {
//不同种类线程间的通信问题,或者说线程的等待唤醒机制。
//生产者线程
//消费者线程
//资源 学生对象 要让线程来共享数据
//使用同一个锁才能使线程在执行完一次逻辑前不会被打断
Student student = new Student();
SetThread th1 = new SetThread(student);
GetThread th2 = new GetThread(student);
th1.start();
th2.start();
//Object 类中的方法,让线程进行等待,以及唤醒等待到的线程
//void wait ()
//在其他线程调用此对象的 notify () 方法或 notifyAll () 方法前,导致当前线程等待。
//void wait(long timeout)
// 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。
/* void notify ()
唤醒在此对象监视器上等待的单个线程。
void notifyAll ()
唤醒在此对象监视器上等待的所有线程。*/
// wait() 方法和 sleep() 方法的区别
//共同点:都可以使线程处于阻塞状态
//sleep() 必须设置时间量
//wait() 方法可以设置时间量,也可以不设置时间量
// wait() 一旦等待,就会释放锁。
//sleep() 一旦休眠,不释放锁。
}
}
消费者线程:如果没有资源,就等着,通知生成者去生产资源
public class GetThread extends Thread{
private Student student;
public GetThread(Student student){this.student=student;}
@Override
public void run() {
while (true){
synchronized (student){
if(!student.flag){
//标志为false,等待生产资源
try{
student.wait();//等待就会释放锁,在哪里等待,被唤醒后就继续从这里执行
}catch (InterruptedException e){
e.printStackTrace();
}
}
//有资源就直接消费
System.out.println(student.name + "===" + student.age);
//消费了就没有了
//标志设为false,通知生产者线程去生产
student.flag=false;
student.notify();
}
}
}
}
public class SetThread extends Thread{
Student student;
int i=1;
public SetThread(Student student){
this.student=student;
}
@Override
public void run() {
while (true){
synchronized (student){
if(student.flag){
//有资源,生成者线程就等待
try{
//th1
student.wait();//线程一旦等待,就要立马释放锁,在哪里等待,被唤醒后,就从哪里开始执行
}catch (InterruptedException e){
e.printStackTrace();
}
}
//没有资源
if(i%2==0){
//生产资源
student.name="张三";
student.age=24;
} else {
//生产资源 th1
student.name = "李四";
student.age = 26;
}
//有了资源通知消费者线程去消费
//修改标记
student.flag=true;
student.notify();//通知唤醒之后,线程还得再次争抢时间片
}
i++;
}
}
}
public class Student {
public String name;
public int age;
//定义一个标记,表示是否有资源。false表示没有资源,true表示有资源
public boolean flag=false;
}
public class Test1 {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
while (true){
if(myRunnable.getFlag()){
System.out.println("进来执行了");
break;
}
}
//Java的内存模型:成员变量存储在主存中
//每个线程都有自己的工作内存,他们要操作主存中的数据,就需要把注册中的数据读取到自己的工作内存中进行操作,操作完之后再写回主存
}
}
class MyRunnable implements Runnable {
//volatile 不能保证原子性的操作
volatile boolean flag = false;
public boolean isFlag() {
return flag;
}
public boolean getFlag() {
return flag;
}
@Override
public void run() {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改flag的值为true
flag = true;
System.out.println("线程进来执行了" + flag);
}
}
同步代码块解决线程安全问题
同步代码块的格式
格式:
synchronized(对象){
需要同步的代码;
}
同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能
同步的好处: 同步的出现解决了多线程的安全问题。
同步的弊端: 当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
在java编程中,经常需要用到同步,而用得最多的也许是synchronized关键字了,
因为synchronized关键字涉及到锁的概念,所以先来了解一些相关的锁知识。
java的内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。
线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。
获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。
java内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,
当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,
直到线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。
java的对象锁和类锁:java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,
但是,两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,
类锁是用于类的静态方法或者一个类的class对象上的。
我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,
所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。
但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,
它只是用来帮助我们理解锁定实例方法和静态方法的区别的.
同步方法: 就是把同步关键字加到方法上
同步代码块的锁对象: 任意一个对象
同步方法的锁对象: 是this
静态同步方法的锁对象:就是当前类对应的字节码文件对象
public class CellRunnable implements Runnable {
/**
* 同步代码块的格式
* 格式:
* synchronized(对象){
* 需要同步的代码;
* }
* 同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能
*/
//这个票让三个线程共享
static int ticket = 100;
//确保这个锁对象,只有一个,多个线程公用一把锁
static Object obj = new Object();
int i = 1;
@Override
public void run() {
while (true) {
//假如就剩最后一张票 int ticket=1;
//th1、th2、th3
if (i % 2 == 0) {
synchronized (this) {
//当th1这个线程进来同步代码块后,就持有了这个锁,其他线程没有持有锁,那么就要处于等待状态,等在同步代码块的外面
if (ticket >= 1) {
//模拟一下真实的售票环境,有网络延迟
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第:" + (ticket--) + " 张票");
}
}
} else {
maiPiao();
}
i++;
//th1 执行完了,出了同步代码块,就会释放锁。释放锁了之后,多个线程再去争抢CPU的时间片
}
}
//同步方法:我们可以把一个方法用synchronized这个关键字修饰,来封装一段代码,来解决线程安全问题
//同步方法:默认用的锁对象是this
public synchronized void maiPiao() {
if (ticket >= 1) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第:" + (ticket--) + " 张票");
}
}
}
public class Test1 {
public static void main(String[] args) {
//创建了一次任务100
CellRunnable cellRunnable = new CellRunnable();
Thread th1 = new Thread(cellRunnable, "窗口1");
Thread th2 = new Thread(cellRunnable, "窗口2");
Thread th3 = new Thread(cellRunnable, "窗口3");
th1.start();
th2.start();
th3.start();
//我们写的这段售票代码,出现了线程安全问题。多线程的环境下
//在对共享数据进行操作时,有可能会出现线程安全问题。
//这个时候出现了一些不合理的数据(数据安全问题)
//1.出现0张票或负数票:原因,是由于线程的随机性所导致的。
//2.出现相同的票:原因,就是由于线程的原子性所导致的 原子性(不可分割性)
//线程对 (ticket-- 不是一个原子性操作他要对ticket这个变量要进行 读 改 写 三个操作 )
//出现线程安全的问题,得符合三个条件
//1.是不是多线程环境
//2.多个线程有没有共享数据
//3.有没有多条语句在操作同一个共享变量
//那出现了 数据安全问题,我们怎么来解决呢?
//我们把有可能出现数据安全问题的代码,使用同步代码块进行包裹
//同步代码块:
//synchronized (锁){放你认为有肯能出现问题的代码}
//锁:你可以使用Java里面的任意一个对象,来充当锁,注意,多个线程要共享一把锁,才能锁住
}
}
public class CellRunnable implements Runnable {
static int ticket = 100;
static Object obj = new Object();
int i = 1;
@Override
public void run() {
while (true) {
if (i % 2 == 0) {
synchronized (CellRunnable.class) {
if (ticket >= 1) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第:" + (ticket--) + " 张票");
}
}
} else {
maiPiao();
}
i++;
}
}
public static synchronized void maiPiao() {
if (ticket >= 1) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第:" + (ticket--) + " 张票");
}
}
}
public class Test {
public static void main(String[] args) {
CellRunnable cellRunnable = new CellRunnable();
Thread th1 = new Thread(cellRunnable, "窗口1");
Thread th2 = new Thread(cellRunnable, "窗口2");
Thread th3 = new Thread(cellRunnable, "窗口3");
th1.start();
th2.start();
th3.start();
//同步代码块:
//synchronized (锁){放你认为有肯能出现问题的代码}
//锁:你可以使用Java里面的任意一个对象,来充当锁,注意,多个线程要共享一把锁,才能锁住
}
}
同步方法锁
public class CellRunnable implements Runnable {
static int ticket = 100;
static Object obj=new Object();
@Override
public void run() {
while (true){
maiPiao();
//th1 执行完了,出了同步代码块,就会释放锁。释放锁了之后,多个线程再去争抢CPU的时间片
}
}
// 同步方法的使用的是 this 这个锁对象
public synchronized void maiPiao(){
//当th1这个线程进来同步代码块后,就持有了这个锁
//其他线程没有持有锁,那么就要处于等待状态,等在同步代码块的外面
if(ticket>=1){
//模拟一下真实的售票环境,有网络延迟。
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第:" + (ticket--) + " 张票");
}
}
}
public class Test {
public static void main(String[] args) {
//利用锁方法
CellRunnable cellRunnable = new CellRunnable();
Thread th1 = new Thread(cellRunnable, "窗口1");
Thread th2 = new Thread(cellRunnable, "窗口2");
Thread th3 = new Thread(cellRunnable, "窗口3");
th1.start();
th2.start();
th3.start();
}
}
JVM内存模型
硬要对比的话,虚拟机栈可以对应多线程的工作内存,堆可以对应多线程的主存
() {
while (true){
maiPiao();
//th1 执行完了,出了同步代码块,就会释放锁。释放锁了之后,多个线程再去争抢CPU的时间片
}
}
// 同步方法的使用的是 this 这个锁对象
public synchronized void maiPiao(){
//当th1这个线程进来同步代码块后,就持有了这个锁
//其他线程没有持有锁,那么就要处于等待状态,等在同步代码块的外面
if(ticket>=1){
//模拟一下真实的售票环境,有网络延迟。
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + “正在出售第:” + (ticket–) + " 张票");
}
}
}
```java
public class Test {
public static void main(String[] args) {
//利用锁方法
CellRunnable cellRunnable = new CellRunnable();
Thread th1 = new Thread(cellRunnable, "窗口1");
Thread th2 = new Thread(cellRunnable, "窗口2");
Thread th3 = new Thread(cellRunnable, "窗口3");
th1.start();
th2.start();
th3.start();
}
}
JVM内存模型
[外链图片转存中…(img-MPtL7fDi-1592322948571)]
硬要对比的话,虚拟机栈可以对应多线程的工作内存,堆可以对应多线程的主存