Java从入门到精通十二(java线程)
计算机操作系统的有关线程和进程的浅显说明
按照操作系统的理解,进程是操作系统分配资源的基本单位。
线程是调度资源的基本单位。
有很多形象的比喻,其实还是抽象化了。抽象化隐藏细节,实现似乎具体的可观。这样就是帮助理解。是在软件层次上的理解。
把一个进程比作一个车间,然后车间的工人就是就相当于线程,然后其实多线程的化,你就向大的级别进行,多线程就是相当于一个大厂,然后大厂里的多个车间就是进程。进程是车间,车间的资源提供给线程进行共享。然后执行每个零碎的任务。
一种非常标准的说法就是,进程就是程序的一次执行。打开一个程序,然后一个或者多个进程就开始了。然后这个程序具体功能的实现还是需要线程去具体实现的。
尽管这样说明,但是其实相信好多人还是和我有好多的疑问。因为根本的底层我们是不太理解的,或者是不理解。我的模电数电,组成原理没学好。汇编,操作系统的系统支撑了我的一些二对于底层的模糊认识。
在电脑上,我们在任务管理器可以直接查看进程。
所以说这些执行程序都可以认为是进程,基本的执行进程下面都有相应的线程。
还可以设置优先级
还可以设置哪些处理器运行指定的进程。
我的cpu不是八核的,这里是虚拟出来的,实际上只有四个内核。因为是intel,采用了超线程技术。
有的进程执行的时候只含有一个线程。举个例子。栗子很多,那么你到底想吃哪个?
开启一个终端,就是开启这样一个cmd.exe。cmd.exe是比较简单的程序,所以只需要这样一个线程。这也说明了它的功能就是比较单一的。等待用户输入命令,然后执行。
但是并不是一直一个,我在观察的时候会发现有时候会出现四个线程。我观察到,过一会儿会有规律的变成一个线程,即使我在终端执行命令,有时候它还是一个线程。
上面我们说了,线程就像车间忙碌的工人,大一点的程序一定会有多条线程的。
这样告诉我线程数就完了吗?我还想比较直观的看到线程的参数,以及优先级,我还想看到它的状态。
系统是一定会让你在终端查看进程的。我要先看进程。为什么我要先看进程,因为我可以通过进程的标识,查看它下面对应的进程。
提供一些参数
简单了解一下就可以,来举这个例子就是为了直观看到这些线程。感受一下。操纵系统中常说的阻塞或者排队状态,上面也给出了标识。
页面错误在组成原理提到过
我清楚的记得组成原理有提到过。果然听听还是有很大用的。
java执行方面的进程和线程的体现
我们会想到java基本的运行机制
java源代码首先需要通过java编译器编译为字节码文件(.class文件),字节码文件是一种二进制的文件,里面的数据紧密相连。文件的内容比较紧凑。字节码文件时通过javac .exe生成,然后再java.exe的作用下进行启动虚拟机(jvm),执行字节码。虚拟机会将编译好的字节码文件加载到内存(也就是类加载。然后虚拟机会对这个类加载的文件进行解释执行。)
直接由虚拟机进行解释执行,而并非操作系统。这样特点也决定了它的跨平台(java程序通过jvm实现)。
jvm也是一个软件层次的程序,在功能的实现上,具有自己完整的堆栈存储区,数据存储区这些,计数器等等。因此有时候也可以被认为是一个小型的计算机。但是这些都是虚拟出来的,实在软件的层次上虚拟出来的功能架构。
jvm是用来具体执行java程序的。jvm执行的时候本身也是一个进程的的进程。并且它是一个多线程对我进程。因为jvm需要做一些事情来支持java的运行机制。
所以jvm本身就是一个多线程的应用。从我们执行程序的main方法入口开始。jvm程序需要执行的时候,操作系统将jvm从磁盘存储器将其调入到内存中,然后创建一个jvm的进程。jvm启动主线程,主线程调用类的main方法,所以主线程也就是从main方法这里开始执行了。既然是一个多线程的应用,那么除了主线程以外还有其它的线程。
比如比较经典的垃圾回收机制的GC thread,此类线程主要进行对JVM里面的垃圾进行收集。再比如字节码编译的线程Compiler threads,再比如给jvm收集信号的线程Singal dispatcher thread。
好奇线程是如何调用的,实现的底层是什么?这就给自己又添麻烦了。
自己去查看start()这个启动的源码
这个函数的里面调用的另一个函数start0才应该是真正启动线程的。底层还是c++写的。native修饰说明是调用了原生系统函数。我去看了看,觉得还是挺复杂的。用notepad打开比较快一点
一个jvm.cpp就将近四千行代码,所以我大致看了几个函数就走了。再见!
参考了网上的一些说明,大致都是在说明方法调用逻辑。
线程启动过程。这是在c++源码上来说的。
说明:该图来自
小傅哥
CodeGuide | 程序员编码指南 Go! - 沉淀、分享、成长,让自己和他人都能有所收获!
此文有对源码,以及具体执行过程的一些探索。
java自己携带了一个可视化的工具,可以动态的观察一下cpu占用,堆,线程的情况。当然这些不是目前主要去看的,主要用在后面的性能调优上面。
VisualVM,java自带的一个监控工具
主要可以分析数据,跟踪内存泄漏,监控垃圾回收,执行内存等等的操作。
直接在控制台运行jvisualvm,大体就是这个界面。当然可以去进行远程连接,查看资源情况等等。
当你在运行一个程序的时候,比较大的一个程序,可以去看看jvm的堆占用情况。
java线程
Thread类信息摘要
一:嵌套类说明
该类的枚举常量说明了线程的状态
另外一个嵌套类
这是一个接口
二:主要的一些字段
优先级可以自己进行设定,有一个范围,也可以去获取运行线程的优先级。
三:构造方法
四:方法
以上引用摘自javaapi
具体的使用说明的话,将说明总结一些比较常用的方法进行举例代码。
创建线程
基于Thread类创建继承类
再次回到Thread类
这个类实现的是Runnable接口
我们进行创建线程的时候,第一种方式就是基于Thread进行一个继承。
使用该方法需要继承Thread类,然后进行重写run方法(多数情况下需要重写run方法。)
package process;
public class ThreadPratice01 extends Thread {
@Override
public void run() {
// super.run();
for(int i = 0;i<100;i++)
{
System.out.println(Thread.currentThread().getName()+":"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String args[])
{
ThreadPratice01 th1= new ThreadPratice01();
ThreadPratice01 th2 = new ThreadPratice01();
th1.start();
th2.start();
}
}
线程会进行竞争占用分配资源。在一定程度上取决于cpu时间片分配。在没有人为进行设置优先级的情况下,基本对资源的占用几率是一样的,即使设置了较高的优先级,也只能说明有更大的几率抢占到资源。
有一个问题,为什么要去重写run()方法?
为什么要重写run()方法?
首先来看看这个run()方法究竟做了什么事情?(没有重写前)
首先,这是Thread的对接口类Runnable的抽象run()方法的一个实现。
可以了解下target是什么?
可以看到target其实是一个Runnable类型的对象。我们一般在自己创建线程类的时候就会使用到Thread或者Tunnable类型的对象进行传入,然后实现对方法的一个调用。
但是其实你发现这个Thread实现的的run()方法其实也没有做什么事情。
当然如果你不重写run()方法的话并不是不可以,至少在语法上这是没有问题的,只是如果这样做的话,毫无意义。
如下这样启动线程的话是没有任何输出内容的。但是你要知道这里是默认调用了没有进行重写的run()方法的。
package process;
public class ThreadPratice01 extends Thread {
// super.run();
public static void main(String args[])
{
ThreadPratice01 th1= new ThreadPratice01();
ThreadPratice01 th2 = new ThreadPratice01();
th1.start();
th2.start();
}
}
所以我们一般是需要对run()方法进行重写的,这样自己定义一个启动执行的多线程程序。
start()方法如何调用run()方法?
找来找去,也查看了一些说明,原来还是涉及到c++
首先还是和前面的start0()这个方法有关系。
native在这里进行修饰说明了这里调用了一个非java语言实现的接口。java需要提供一个java和本地的c语言代码进行相互操作的接口,一般简称为JNI(java Native Interface),这样调用底层的c++的接口,这里面的一些加载的过程全部交给jvm进行操作。
registerNatives()是一个注册的方法,位于static修饰的静态代码块中。这样的作用就是在Thread类被jvm直接运行。在类初始化的时候会执行一次,并且执行的优先级高于非静态的代码块。也就是说会在类加载的时候直接执行。然后就执行相应的功能,就是实现一个注册。
具体的话可以在openjdk进行查看源代码。(代码非常多)
但是可以大致得出就是start通过jvm来实现对run()方法的调用。当然这似乎就是废话。主要是在jvm的一些底层实现上。
然后我去进行查找。
抱歉,前面似乎说的有一些问题。一部分Thread是c,一部分是c++。
这个在Thread.c文件中。我开始还去Thread.cpp去找。结果在c++代码中没有找到。
这个Thread.c的代码只有七十多行代码,其中有一些还是注释。
/*
* Copyright (c) 1994, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*-
* Stuff for dealing with threads.
* originally in threadruntime.c, Sun Sep 22 12:09:39 1991
*/
#include "jni.h"
#include "jvm.h"
#include "java_lang_Thread.h"
#define THD "Ljava/lang/Thread;"
#define OBJ "Ljava/lang/Object;"
#define STE "Ljava/lang/StackTraceElement;"
#define STR "Ljava/lang/String;"
#define ARRAY_LENGTH(a) (sizeof(a)/sizeof(a[0]))
static JNINativeMethod methods[] = {
{"start0", "()V", (void *)&JVM_StartThread},
{"stop0", "(" OBJ ")V", (void *)&JVM_StopThread},
{"isAlive", "()Z", (void *)&JVM_IsThreadAlive},
{"suspend0", "()V", (void *)&JVM_SuspendThread},
{"resume0", "()V", (void *)&JVM_ResumeThread},
{"setPriority0", "(I)V", (void *)&JVM_SetThreadPriority},
{"yield", "()V", (void *)&JVM_Yield},
{"sleep", "(J)V", (void *)&JVM_Sleep},
{"currentThread", "()" THD, (void *)&JVM_CurrentThread},
{"countStackFrames", "()I", (void *)&JVM_CountStackFrames},
{"interrupt0", "()V", (void *)&JVM_Interrupt},
{"isInterrupted", "(Z)Z", (void *)&JVM_IsInterrupted},
{"holdsLock", "(" OBJ ")Z", (void *)&JVM_HoldsLock},
{"getThreads", "()[" THD, (void *)&JVM_GetAllThreads},
{"dumpThreads", "([" THD ")[[" STE, (void *)&JVM_DumpThreads},
{"setNativeName", "(" STR ")V", (void *)&JVM_SetNativeThreadName},
};
#undef THD
#undef OBJ
#undef STE
#undef STR
JNIEXPORT void JNICALL
Java_java_lang_Thread_registerNatives(JNIEnv *env, jclass cls)
{
(*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
}
从上面代码可以了解到调用的start方法,会对应调用JVM_StartThread,我们还可以找到对应熟悉的方法,相必stop0也是同样的道理。再具体的话,真的就不想再去看了。说了这么多,只是为了说明这个调用的方式。和底层的实现有关,还有一个非java的接口。提供对应的注册调用。
在此基础上我们可以尝试一些线程相关的方法
package process;
//实现多线程
public class MyThread extends Thread {
//也可以自己设置无参和带参构造方法
// 这样在进行创建对象的时候可以直接进行传值
public MyThread() {
}
public MyThread(String name) {
super(name);//调用父类的这个构造方法,这样就可以在new实例化的时候传入字符串。
}
@Override
//run方法封装线程执行的代码
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
try {
Thread.sleep(1000);//线程休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
MyThread my1 = new MyThread();//这样就可以选择时候在创建对象的时候是否进行传入值
MyThread my2 = new MyThread();
//获取线程的优先级
System.out.println(my1.getPriority());//都是5
System.out.println(my2.getPriority());
my1.setName("高铁");//给对应的线程设置名字
my2.setName("飞机");
//设置一个主线程
currentThread().setName("刘备");
System.out.println(Thread.MAX_PRIORITY);//线程最大优先级
System.out.println(Thread.MIN_PRIORITY);//线程最小优先级
System.out.println(Thread.NORM_PRIORITY);//线程默认优先级
//设置优先级
my1.setPriority(6);
my2.setPriority(8);
my1.join();//让调用他的线程先执行完,其它线程再执行。
//线程优先级高只是代表获取cpu时间片的几率高
//设置优先级
my1.setDaemon(true);//设置守护线程
//设置为守护线程,则会在主线程执行完后,守护线程也会马上执行完毕
my1.start();
//自己设置线程名称
my2.start();
System.out.println("获取main方法正在执行的线程名称:" + Thread.currentThread().getName());
for (int i =0;i<10;i++)
{
System.out.println(Thread.currentThread().getName()+","+i);
}
}
}
但是在启动线程方面,海域一个疑惑,就是我们可以也可以进行调用run()方法,直接调用,不通过start(),这样在语法上是没有错误的,但是好奇这样做的话,会有什么样的区别。
直接调用run()启动和通过start()启动线程的区别
package process;
public class ThreadPratice01 extends Thread {
@Override
public void run() {
for(int i =0;i<10;i++)
{
System.out.println(Thread.currentThread().getName()+":"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String args[])
{
ThreadPratice01 th1= new ThreadPratice01();
ThreadPratice01 th2 = new ThreadPratice01();
//th1.start();不可以被重复调用哦
//th1.start();
th2.start();
th1.run();
}
}
这样执行后,其实你会发现程序th1.run()之后,可以发现程序的线程名是main,或者你单独运行它一下,这样的启动线程仍然是在main进行。所以根本就没有创建新的线程。
但是通过start调用的话,你会发现会得到一个新的创建的线程。
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
关键要素还是start0()这里调用的非java实现的方法,前面对这个start0()有一些说明,就不再赘述。
通过实现Runnable接口实现创建线程
这样去创建线程的话,很明显的特点就是在实现Runnable接口的时候还可以去基础其它的类,显得更加灵活,更加符合java语言多态的特点。
package process;
public class MyThread01 implements Runnable {
//通过实现Runnable接口来实现多线程
public static void main(String[] args) {
MyThread01 my = new MyThread01();
// Thread t1 = new Thread(my);
// Thread t2 = new Thread(my);
//给线程起名字
Thread t1 = new Thread(my, "高铁");
Thread t2 = new Thread(my, "飞机");
t1.start();
t2.start();
}
@Override
public void run() {
for(int i =0;i<100;i++)
{
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
使用到Thread类的构造方法
通过 实现Callable接口创建线程
Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable 不会返回结果,并且无法抛出经过检查的异常。
那就简单实现一下
这样实现的话还需要去靠一些接口类去实现。
package process;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class MyThread02 implements Callable {
public static void main(String[] args) {
MyThread02 mt = new MyThread02();
FutureTask ft = new FutureTask<>(mt);
FutureTask ft1 = new FutureTask<>(mt);
new Thread(ft).start();
new Thread(ft1).start();
}
@Override
public Object call() throws Exception {
for(int i =1000;i>0;i--)
{
System.out.println(Thread.currentThread().getName()+";"+i);
}
return null;
}
}
说明
多线程卖票出现的问题
当然这是一个很经典并且常用的例子了
package process;
public class SellTicketDemo implements Runnable {
private int tickets = 100;
public static void main(String[] args) {
//创建对象
SellTicketDemo st = new SellTicketDemo();
Thread t1 = new Thread(st, "窗口一");
Thread t2 = new Thread(st, "窗口二");
Thread t3 = new Thread(st, "窗口三");
//t1.setPriority(10);
//启动线程
t1.start();
t2.start();
t3.start();
}
//出现的问题:
// 相同的票出现了多次
@Override
public void run() {
while (true) {
if (tickets > 0) {
//通过sleep()方法来模拟出票时间
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
}
}
这个休眠时间可以设置不一样的时间,会出现不同的结果。(比较明显)
在我自己的电脑上最终尝试结果是会有卖出0张票,和一张票,在有的操作系统上,还会出现卖出-1张票。
这些不同的结果都与多线程抢占资源有关。
在看黑马教程的时候,老师给出了非常详细的说明
1:为什么会卖出重复票?
线程在执行代码语句后,都有可能被其它的线程抢占到资源。
2:为什么可能会出现负数或者0张票?
执行权的抢占决定了运行的结果,但是基本没有见过-2张票的情况。主要需要知道的是线程执行具有随机性。
3:卖票案例数据安全问题的尝试解决
同步代码块
数据安全问题主要是说明了对数据的处理不符合现实的预期情况。
多线程在没有进行自己处理的情况下,对数据的共享会出现问题,单线程是不会造成这种问题的。
解决办法就是让任一时刻只有一个线程执行。
同步代码块
按照这样的解决思路,我们可以这样去做
package process;
public class SellTicketDemo implements Runnable {
private int tickets = 100;
private Object obj = new Object();
public static void main(String[] args) {
//创建对象
SellTicketDemo st = new SellTicketDemo();
Thread t1 = new Thread(st, "窗口一");
Thread t2 = new Thread(st, "窗口二");
Thread t3 = new Thread(st, "窗口三");
//t1.setPriority(10);
//启动线程
t1.start();
t2.start();
t3.start();
}
@Override
public void run() {
while (true) {
synchronized (obj){
if (tickets > 0) {
//通过sleep()方法来模拟出票时间
{
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
if(tickets==0)
{
System.out.println("票已经卖完!");
System.exit(0);
}
}
}
}
}
}
注意new一个锁对象的时候,不要进行重复new,不然就是重复加锁,这样做还是徒劳的。
采用同步的方式加锁,解决了多线程的数据安全的问题,但是在线程很多的时候,每个线程都会去判断同步的锁,这样会降低程序运行的效率。
当然你可以将代码块方法封装到一个定义地方法中,比如下面这样,但是这样不叫同步方法。
package process;
public class SellTicketDemo implements Runnable {
private int tickets = 100;
private Object obj = new Object();
public static void main(String[] args) {
//创建对象
SellTicketDemo st = new SellTicketDemo();
Thread t1 = new Thread(st, "窗口一");
Thread t2 = new Thread(st, "窗口二");
Thread t3 = new Thread(st, "窗口三");
//t1.setPriority(10);
//启动线程
t1.start();
t2.start();
t3.start();
}
@Override
public void run() {
sellTicket();
}
private void sellTicket() {
while (true) {
synchronized (obj){
if (tickets > 0) {
//通过sleep()方法来模拟出票时间
{
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
if(tickets==0)
{
System.out.println("票已经卖完!");
System.exit(0);
}
}
}
}
}
}
也可以使用this关键字
package process;
public class SellTicketDemo implements Runnable {
private int tickets = 1000;
private Object obj = new Object();
public static void main(String[] args) {
//创建对象
SellTicketDemo st = new SellTicketDemo();
Thread t1 = new Thread(st, "窗口一");
Thread t2 = new Thread(st, "窗口二");
Thread t3 = new Thread(st, "窗口三");
//t1.setPriority(10);
//启动线程
t1.start();
t2.start();
t3.start();
}
@Override
public void run() {
sellTicket();
}
private void sellTicket() {
while (true) {
synchronized (this){
if (tickets > 0) {
//通过sleep()方法来模拟出票时间
{
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
if(tickets==0)
{
System.out.println("票已经卖完!");
System.exit(0);
}
}
}
}
}
}
this指代了当前对象,也就是该类的实例。
这样是可以的,但是该段代码需要注意的是,如果while循环也位于锁内,其实效果还是相当于将整个方法锁定。结果与此不同。
同步方法
想办法把锁加到方法上
package process;
public class SellTicketDemo implements Runnable {
private int tickets = 100;
private Object obj = new Object();
public static void main(String[] args) {
//创建对象
SellTicketDemo st = new SellTicketDemo();
Thread t1 = new Thread(st, "窗口一");
Thread t2 = new Thread(st, "窗口二");
Thread t3 = new Thread(st, "窗口三");
//t1.setPriority(10);
//启动线程
t1.start();
t2.start();
t3.start();
}
@Override
public void run() {
sellTicket();
}
private synchronized void sellTicket() {
while (true) {
if (tickets > 0) {
//通过sleep()方法来模拟出票时间
{
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
if(tickets==0)
{
System.out.println("票已经卖完!");
System.exit(0);
}
}
}
}
}
但是这样其实并没有达到想要的效果,只有一个线程可以执行完,一直到程序结束。这样还是将整个方法锁定了,所以线程不能实现交替执行。最终只有一个线程执行完后退出。
静态同步方法锁(类锁的一种形式)
主要体现在synchronized对静态方法地修饰上面。然后他锁定的话主要是对类进行锁定。
package process;
public class SellTicketDemo implements Runnable {
static int tickets = 1000;
private Object obj = new Object();
public static void main(String[] args) {
//创建对象
SellTicketDemo st = new SellTicketDemo();
Thread t1 = new Thread(st, "窗口一");
Thread t2 = new Thread(st, "窗口二");
Thread t3 = new Thread(st, "窗口三");
//t1.setPriority(10);
//启动线程
t1.start();
t2.start();
t3.start();
}
@Override
public void run() {
sellTicket();
}
private static synchronized void sellTicket() {
while (true) {
if (tickets > 0) {
//通过sleep()方法来模拟出票时间
{
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
if(tickets==0)
{
System.out.println("票已经卖完!");
System.exit(0);
}
}
}
}
}
但是这样的话,是锁住了整个类,类中的资源需要等到开始抢占到资源的线程释放掉类锁后才允许执行。其它的线程也位于这个类中,当然是无法执行的。
修饰类得到的类锁
package process;
public class SellTicketDemo implements Runnable {
private int tickets = 1000;
private Object obj = new Object();
public static void main(String[] args) {
//创建对象
SellTicketDemo st = new SellTicketDemo();
Thread t1 = new Thread(st, "窗口一");
Thread t2 = new Thread(st, "窗口二");
Thread t3 = new Thread(st, "窗口三");
//t1.setPriority(10);
//启动线程
t1.start();
t2.start();
t3.start();
}
@Override
public void run() {
sellTicket();
}
private void sellTicket() {
while (true) {
synchronized (SellTicketDemo.class)
{
if (tickets > 0) {
//通过sleep()方法来模拟出票时间
{
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
if(tickets==0)
{
System.out.println("票已经卖完!");
System.exit(0);
}
}
}
}
}
}
这样启动的话,事实证明这样的类锁也可以解决数据安全问题。(卖票案例)
这些都是锁的使用的一些很浅显的例子。
lock加锁
Lock接口的说明,相比较synchronized有什么特点呢?
使用格式
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
提供了一些方法
应该是java5之后推出的lock锁。
不过在本例中,我们只需要用到两个方法
针对上面的问题。再次尝试一种锁,lock锁。看看怎么用?
package process;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SellTicketDemo implements Runnable {
private int tickets = 100;
private Lock lock = new ReentrantLock();
private Object obj = new Object();
public static void main(String[] args) {
//创建对象
SellTicketDemo st = new SellTicketDemo();
Thread t1 = new Thread(st, "窗口一");
Thread t2 = new Thread(st, "窗口二");
Thread t3 = new Thread(st, "窗口三");
//t1.setPriority(10);
//启动线程
t1.start();
t2.start();
t3.start();
}
@Override
public void run() {
sellTicket();
}
private void sellTicket() {
while (true) {
try {
lock.lock();
if (tickets > 0) {
//通过sleep()方法来模拟出票时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
if (tickets == 0) {
System.out.println("票已经卖完!");
System.exit(0);
}
}
} finally {
lock.unlock();
}
}
}
}
生产者消费者问题
一部分的说明
先来说说生产者消费者问题(计算机层次上,非生物科学)
以上摘自百度百科
这当然说的还是比较专业的,但是其实简单说就是生产者生产出资源,然后消费者使用。我把生产的资源放入存储柜,然后消费者去取。存在的问题可能就是供不应求,比如生产的不够消费者用,或者还有就是堵塞,我生产的过多,存储柜都快放不下了,消费者难以及时消费掉。
比较和谐的做法就是,如果我生产在存储柜里的资源足够的话,就让消费者去消费,如果资源不够的话,就让消费者等待,等我生产出再去取。
简单的说,就是这些,当然还有信号,对于死锁问题的说明等等,就不详细说明了。
生产牛奶和消费牛奶
还是在b看看视频学到的,然后自己浅显的加了点东西。
作为笔记,非常喜欢这个案例,虽然简单,但是这个例子和操作系统的pv等的操作很相似,可以说基本一样。对于自己对系统资源的这个管理认识也有一定的帮助,记录下来,以后可以自己再看看。
编写一个模拟程序
一个生产者类,一个消费者类,一个箱子类,一个测试类。
package process;
import java.util.Random;
public class Producer implements Runnable{
private Box b;
public Producer(Box b) {
this.b = b;
}
@Override
public void run() {
while(true)
{
Random random = new Random();
int i = random.nextInt(10)+1;
try {
b.put(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// for(int i= 1;i<=5;i++)
// {
// try {
// b.put(i);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
}
}
package process;
public class Customer implements Runnable {
private Box b;
public Customer(Box b) {
this.b = b;
}
@Override
public void run() {
while(true)
{
try {
b.get();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package process;
import java.util.Random;
public class Box {
private int milk;
//定义一个成员变量,表示奶箱的状态
private boolean state = false;
//提供存储牛奶和获取牛奶的操作
public synchronized void put(int milk) throws InterruptedException {
//如果有,等待消费
//如果没有就生产
if(state)
{
wait();
}
this.milk = this.milk+milk;
System.out.println("送奶工将" + milk + "瓶奶放入奶箱");
//生产完毕之后,改变奶箱状态
state = true;
//唤醒其它等待线程
notifyAll();
}
public synchronized void get() throws InterruptedException {
Random random = new Random();
int i = random.nextInt(5)+1;
System.out.println("用户要取"+i+"瓶奶");
//如果没有牛奶,等待生产
if(!state)
{
wait();
}
//如果有,就消费
if(i<=this.milk)
{
System.out.println("用户拿了" + i + "瓶奶");
this.milk = this.milk-i;
System.out.println("奶箱里还剩"+this.milk+"瓶牛奶");
}
else {
System.out.println("牛奶不足");
state = false;
}
notifyAll();
}
}
package process;
public class BoxDemo {
public static void main(String[] args) {
Box b = new Box();
Producer p = new Producer(b);
Customer c = new Customer(b);
Thread t1 = new Thread(p);
Thread t2 = new Thread(c);
t2.start();
t1.start();
}
}
程序做的不是很完善,但是基本模拟了这种情况。送入的奶是随机生成的,每次剩下的奶会与再次送入的奶相加。用户来取的话,如果奶不足,这个程序就设定不会提供了。线程调用加奶,随机一位用户来去,如果奶够的话就取走。如果你觉得逻辑不太好的,可以自己进行修改。
自己可以加入休眠,以及设定线程循环结束条件,或者修改主要逻辑。
就介绍这么多吧!之后有错误或补充的话,再改。
欢迎访问主页