java线程和进程
进程
- 概念:是正在运行的程序
- 是系统进行资源分配和调用的独立单位
- 每一个进程都有自己的内存空间和系统资源
线程
- 概念:是进程中单个顺序控制流是一条执行路径
- 单线程:一个进程只有一个执行路径(按照顺序)
- 多线程:即多条执行路径
- 多线程例子
- 扫雷:一个控制时间一个控制玩家自己的顺序 卖票存取钱等。
多线程的实现方式
- 一:继承Thread类(后面有通过接口实现)
- 定义一个类MyThread继承Thread类
在MyThread类中重写run方法
(区分能被线程执行的代码)- 创建MyThread类的对象
- 启动线程
- 注意点
- 重写run方法,因为该方法是封装线程执行代码的,
直接调用它的话会被当成普通方法调用
- 需要调用
start方法
启动线程,然后由JVM调用该线程的run方法 - 获取线程名称时如果要在main中获取则用
Thread.currentThread().getName()
,获取名称有三种方式1、get set方法(getName直接用相当于this.getName()),2、带参构造,3、静态方法currentThread()返回对当前执行线程的引用
- 重写run方法,因为该方法是封装线程执行代码的,
package Thread;
public class ThreadName extends Thread{
ThreadName(String name){
super(name);//超类有这个带参构造则一定要传super的参数且为第一句
}
//@Override
public void run() {
for(int i=0;i<50;i++){
System.out.println(getName()+":"+i);
}
}
}
package Thread;
public class ThreadNameDemo{
public static void main(String[] args) {
ThreadName tn1=new ThreadName("线程1");//tn1.setName("线程1")
ThreadName tn2=new ThreadName("线程2");
tn1.start();
tn2.start();
//也可以在main函数中用getName方法,但是因为测试类没有继承Thread类,所以不可以直接用getName,需要用到Thread.currentThread().getName()
}
}
-
二、线程调度
- java采用抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个
但是优先级高并不一定就是一直先执行,只是该线程抢占到的CPU时间片的几率更大
,但是要在次数比较多或者运行多次的时候才可以看到想要的效果- 线程默认值是5,优先级范围是1-10
public class ThreadPriorityDemo {
public static void main(String[] args) {
ThreadPriority tp1=new ThreadPriority();
ThreadPriority tp2=new ThreadPriority();
tp1.setName("线程1");
tp2.setName("线程2");
System.out.println(tp1.getPriority());
System.out.println(tp2.getPriority());
tp1.setPriority(1);
tp2.setPriority(10);
tp1.start();
tp2.start();
}
}
public final int getPriority():返回线程优先级
public final void setPriority():更改此线程的优先级
- 三、线程控制
-
-
join方法可以让该线程执行完成后才开始其他线程
-
抛InterruptedException(说明该方法是可能会花一点时间,但是是可以取消的方法)的代表方法有:
-
- java.lang.Object 类的
wait 方法
- java.lang.Object 类的
-
- java.lang.Thread 类的
sleep 方法
- java.lang.Thread 类的
-
- java.lang.Thread 类的
join 方法
- java.lang.Thread 类的
-
-
执行wait方法的线程,会进入等待区等待被notify/notify All。在等待期间,线程不会活动。
执行sleep方法的线程,会
暂停执行参数内所设置的时间。
执行join方法的线程,会等待到指定的线程结束为止。
public class ThreadSleep extends Thread{
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println(getName()+":"+i);
try {
Thread.sleep(1000);//让当前正在执行的线程休眠1s(暂停执行),每次执行三者各执行一次,因为ts1抢到后执行run()休眠1次,ts2抢到后也休眠1次同时抢抢到后执行调用run才开始休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadJoinDemo {
public static void main(String[] args) {
ThreadJoin tj1=new ThreadJoin();
ThreadJoin tj2=new ThreadJoin();
ThreadJoin tj3=new ThreadJoin();
tj1.setName("开花");
tj2.setName("结果1");
tj3.setName("结果2");
tj1.start();
try {
tj1.join();//等待tj1执行完
} catch (InterruptedException e) {
e.printStackTrace();
}
tj2.start();
tj3.start();
}
}
public class ThreadDaemondemo {
public static void main(String[] args) {
ThreadDaemon td1=new ThreadDaemon();
ThreadDaemon td2=new ThreadDaemon();
td2.setName("士兵1");
td1.setName("士兵2");
//设置主线程为将军
Thread.currentThread().setName("将军");
//设置守护线程
td1.setDaemon(true);
td2.setDaemon(true);
td1.start();
td2.start();//守护线程在主线程结束后很快执行完毕但是不是立即结束
for(int i=0;i<40;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
尽管主线程在程序启动时自动创建,但它可以由一个Thread对象控制。为此,你必须调用方法
currentThread()获得它的一个引用
,currentThread()是Thread类的公有的静态成员。
- 三、线程的生命周期
-
所以之前的sleep方法是它运行一次之后阻塞了,再回去抢执行权三个线程每个都是运行一次再回去抢,所以是三个为一轮
- 多线程的其他实现方式:(创建新执行线程有两种方法)
- 一、将类
声明为 Thread 的子类
。该子类应重写 Thread 类的 run 方法,接下来可以分配并启动该子类的实例 - 二、声明
实现 Runnable 接口的类
。该类实现 run 方法。然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动
- 之前的例子都是采用第一种方式实现,接下来采用第二种
- 第二种方式避免了java单继承的局限性
- 定义一个类Myrunnable类实现Runnable接口
- 在MyRunnable类中重写run方法
- 创建Myrunnable类的对象
- 创建Thread对象将上面的对象作为参数传递给Thread对象
- 启动线程
- Thread构造方法
- 一、将类
public class MyRunnable implements Runnable{
//Runnable接口和Thread类没有关系不能调用Thread中的getName方法
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public class MyRunnableDemo {
public static void main(String[] args) {
MyRunnable mr=new MyRunnable();
Thread t1=new Thread(mr);//Thread t1=new Thread(mr,"线程1")
Thread t2=new Thread(mr);//Thread t2=new Thread(mr,"线程2")起名
t1.start();
t2.start();
}
}
综合案例(互斥机制锁机制)
- 售票
//第一次:出现两种问题负数票和一票多用,问题原因是线程执行的随机性
public void run() {
while(true){
if(ticket>0){
try {
Thread.sleep(100);//通过sleep模拟出票时间
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在售出第"+ticket+"张票");
//可能在ticket--之前t2或者t3抢到了CPU的执行权:这就是看到同一张票出现多次的原因
//可能在t1将ticket--变成0时,t1不能再继续售票,但t2已经进来循环了,所以还可以--:这就是出现负数票的原因
ticket--;
}
}
}
- 因为随机性出现的问题基础:多线程环境并且有共享数据资源且有多条语句操作共享数据
- 解决问题:将多条语句操作共享数据的代码锁起来,让任意时刻只有一个线程
- 同步代码块:
synchronized(任意对象){多条语句操作共享数据的代码块}
(任意对象可看成是一把锁)
- 同步代码块:
public class SellTicket implements Runnable{
private int ticket=100;
private Object obj=new Object();//共用一把锁
@Override
public void run() {
while(true){
synchronized (obj){
//如果是t1先抢到执行权则t1执行到这里会把这段代码锁起来,t2、t3只能等即时t1休眠期直到t1出来代码块
if(ticket>0){
try {
Thread.sleep(100);//通过sleep模拟出票时间
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在售出第"+ticket+"张票");
ticket--;
}
}
}
}
}
- 同步好处和弊端:解决了多线程的数据安全问题但是当线程很多时,每个线程都会去判断同步上的锁,这样耗费资源降低运行效率
- 同步方法:
修饰符 sychronized 返回值类型 方法名(方法参数){}
- 同步静态方法:
修饰符 static sychronized 返回值类型 方法名(方法参数){}
- 同步方法中锁的对象是this(如果是静态方法需要将this换成类名.class)