本文共 17831 字,大约阅读时间需要 59 分钟。
进程就是正在运行的程序,是系统进行资源分配和调用的独立单位。
每一个进程都有它自己的内存空间和系统资源。单进程计算机只能做一件事情。而我们现在的计算机都可以一边玩游戏(游戏进程),一边听音乐(音乐进程),
所以我们常见的操作系统都是多进程操作系统。比如:Windows,Mac和Linux等,能在同一个时间段内执行多个任务。对于单核计算机来讲,游戏进程和音乐进程是同时运行的吗?不是。因为CPU在某个时间点上只能做一件事情,计算机是在游戏进程和音乐进程间做着频繁切换,且切换速度很快,所以,我们感觉游戏和音乐在同时进行,其实并不是同时执行的。多进程的作用不是提高执行速度,而是提高CPU的使用率。在一个进程内部又可以执行多个任务,而这每一个任务我们就可以看成是一个线程。是程序使用CPU的基本单位。
多线程的作用不是提高执行速度,而是为了提高应用程序的使用率。
怎么理解?我们程序在运行的使用,都是在抢CPU的时间片(执行权),如果是多线程的程序,那么在抢到CPU的执行权的概率应该比较单线程程序抢到的概率要大.那么也就是说,CPU在多线程程序中执行的时间要比单线程多,所以就提高了程序的使用率.但是即使是多线程程序,那么他们中的哪个线程能抢占到CPU的资源呢,这个是不确定的,所以多线程具有随机性.并发是逻辑上同时发生,指在某一个时间内同时运行多个程序。
并行是物理上同时发生,指在某一个时间点同时运行多个程序。指应用能够交替执行不同的任务, 其实并发有点类似于多线程的原理, 多线程并非是如果你开两个线程同时执行多个任务, 执行, 就是在你几乎不可能察觉到的速度不断去切换这两个任务, 已达到"同时执行效果", 其实并不是的, 只是计算机的速度太快, 我们无法察觉到而已. 就类似于你, 吃一口饭喝一口水, 以正常速度来看, 完全能够看的出来, 当你把这个过程以n倍速度执行时..可以想象一下
指应用能够同时执行不同的任务, 例:吃饭的时候可以边吃饭边打电话, 这两件事情可以同时执行
ava命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。
所以 main方法运行在主线程中。JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。
由于线程是依赖进程而存在的,所以我们应该先创建一个进程出来。
而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程。但是Java是不能直接调用系统功能的,所以,我们没有办法直接实现多线程程序。但是呢?Java可以去调用C/C++写好的程序来实现多线程程序。由C/C++去调用系统功能创建进程,然后由Java去调用这样的东西,然后提供一些类供我们使用。我们就可以实现多线程程序了。参考Thread类我们启动线程使用不是run方法,而应该是start方法.使该线程开始执行;
Java 虚拟机调用该线程的 run 方法。为什么要重写run方法?
这个类是一个线程类,那么在这个类中我们可不可以写一些其他的方法呢?
我们可以在写其他的方法,那么其他方法中封装的代码都是需要被我们线程执行的吗? 不一定那么也就是run方法中封装应该是必须被线程执行的代码.run方法中的代码的书写原则: 一般是比较耗时的代码public static void main(String[] args) {
//在Java中如何开启一个线程,利用 Thread 这个线程类//线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。MyThread th1 = new MyThread();th1.setName("刘亦菲");th1.setPriority(1);th1.start();//开启线程的方法MyThread th2 = new MyThread();th2.setPriority(Thread.MAX_PRIORITY);th2.setName("林青霞");th2.start();//Java中多个线程,执行是随机性的,因为我Java采用的线程调度模型是抢占式调度,线程优先级高的优先使用CPU的执行权//优先级一样,就是随机抢占}
public class MyThread extends Thread {
System.out.println(Thread.currentThread().getName()+"===" + i); }}
}
假如我们的计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,
线程只有得到 CPU时间片,也就是使用权,才可以执行指令。所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。 Java使用的是抢占式调度模型。
public final int getPriority() //获取线程的优先级public final void setPriority(int newPriority)//设置线程的优先级
线程休眠: public static void sleep(long millis) 线程休眠
public static void main(String[] args) throws InterruptedException {
System.out.println("这是一段广告"); Thread.sleep(5000); System.out.println("bbbbb"); //Thread(String name) 分配新的 Thread 对象。 MyThread th1 = new MyThread("林青霞"); th1.start();}
public class MyThread extends Thread {
public MyThread(String name) { super(name);}@Overridepublic void run() { //需要线程来执行的方法 //一般run方法里面写的就是耗时操作 for (int i = 0; i < 100; i++) { try { Thread.sleep(2000);//让线程休眠 } catch (InterruptedException e) { e.printStackTrace(); } //System.out.println(this.getName()+"=="+i); System.out.println(Thread.currentThread().getName() + "===" + i); }}
}
加入线程: public final void join()
意思就是: 等待该线程执行完毕了以后,其他线程才能再次执行注意事项: 在线程启动之后,在调用方法public class MyTest {
public static void main(String[] args) throws InterruptedException { MyThread th1 = new MyThread("刘备");MyThread th2 = new MyThread("关羽");MyThread th3 = new MyThread("张飞");//join()方法可以将多个线程并发的执行,转换成串行 //A: //加入线程: //public final void join () //意思就是: //等待该线程执行完毕了以后, 其他线程才能再次执行 //注意事项: //在线程启动之后, 在调用方法 th1.start(); th1.join(); th2.start(); th2.join(); th3.start();}
}
public class MyThread extends Thread { public MyThread(String name) { super(name);}@Overridepublic void run() { //需要线程来执行的方法 //一般run方法里面写的就是耗时操作 for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + "===" + i); }}
}
礼让线程: public static void yield(): 暂停当前正在执行的线程对象,并执行其他线程。
public class MyTest {
public static void main(String[] args) { MyThread th1 = new MyThread("刘备");MyThread th2 = new MyThread("关羽");th1.start();th2.start();}
}
public class MyThread extends Thread { public MyThread(String name) { super(name);}@Overridepublic void run() { //需要线程来执行的方法 //一般run方法里面写的就是耗时操作 for (int i = 0; i < 100; i++) { Thread.yield();//线程礼让 System.out.println(Thread.currentThread().getName() + "===" + i); }}
}
守护线程: public final void setDaemon(boolean on):
将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。public class MyTest {
public static void main(String[] args) { Thread.currentThread().setName("刘备");System.out.println("主线程开始了");System.out.println("主线程开始了");System.out.println("主线程开始了");System.out.println("主线程开始了");//A: //守护线程: //public final void setDaemon ( boolean on): //将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。 //该方法必须在启动线程前调用。 //当用户线程,执行完之后,那么守护线程,必须里面死亡 MyThread th1 = new MyThread("张飞"); MyThread th2 = new MyThread("关羽"); //设置为守护线程 该方法必须在启动线程前调用。 th1.setDaemon(true); th2.setDaemon(true); th1.start(); th2.start(); System.out.println("主线程结束了"); System.out.println("主线程结束了"); System.out.println("主线程结束了"); System.out.println("主线程结束了"); System.out.println("主线程结束了");}
}
public class MyThread extends Thread { public MyThread(String name) { super(name);}@Overridepublic void run() { //需要线程来执行的方法 //一般run方法里面写的就是耗时操作 for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + "===" + i); }}
}
2.用户线程和守护线程的适用场景
由两者的区别及dead时间点可知,守护线程不适合用于输入输出或计算等操作,因为用户线程执行完毕,程序就dead了,适用于辅助用户线程的场景,如JVM的垃圾回收,内存管理都是守护线程,还有就是在做数据库应用的时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监听连接个数、超时时间、状态等。3.创建守护线程
调用线程对象的方法setDaemon(true),设置线程为守护线程。1)thread.setDaemon(true)必须在thread.start()之前设置。2)在Daemon线程中产生的新线程也是Daemon的。3)不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。因为Daemon Thread还没来得及进行操作,虚拟机可能已经退出了。4.Java守护线程和Linux守护进程
两者不是一个概念。Linux守护进程是后台服务进程,没有控制台。在Windows中,你可以运行javaw来达到释放控制台的目的,在Unix下你加&在命令的最后就行了。所以守护进程并非一定需要的。中断线程
public final void stop(): 停止线程的运行public void interrupt(): 中断线程(这个翻译不太好),查看API可得当线程调用wait(),sleep(long time)方法的时候处于阻塞状态,可以通过这个方法清除阻塞public class MyTest {
public static void main(String[] args) throws InterruptedException {MyThread th1 = new MyThread("张飞");th1.start();Thread.sleep(20);th1.stop();//强制停掉子线程
}
}public class MyThread extends Thread { public MyThread(String name) { super(name);}@Override
public void run() { //需要线程来执行的方法//一般run方法里面写的就是耗时操作for (int i = 0; i < 100000; i++) {System.out.println(Thread.currentThread().getName() + "===" + i);}
}
}public class MyTest {
public static void main(String[] args) throws InterruptedException { MyThread th1 = new MyThread("张飞");th1.start();Thread.sleep(1000);th1.interrupt();//打断线程的阻塞状态,让线程继续运行}}public class MyThread extends Thread { public MyThread(String name) { super(name);}@Override
public void run() { //需要线程来执行的方法//一般run方法里面写的就是耗时操作try { Thread.sleep(5000); //让线程休眠,其实是让线程处于了一种阻塞状态} catch (InterruptedException e) { e.printStackTrace();}for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + "===" + i);}
}
}实现Runnable接口 这种方式扩展性强 实现一个接口 还可以再去继承其他类
优点:可以避免由于Java单继承带来的局限性。public class MyTest {
public static void main(String[] args) { //1. 创建线程的另一种方法是声明实现 Runnable 接口的类。//2. 该类然后实现 run 方法。然后可以分配该类的实例,// 3. 在创建 Thread 时作为一个参数来传递并启动MyRunable myRunable = new MyRunable();//Thread(Runnable target)//分配新的 Thread 对象。//Thread(Runnable target, String name)//分配新的 Thread 对象。Thread th = new Thread(myRunable,"王菲");th.start();Thread th2 = new Thread(myRunable,"谢霆锋");th2.start();
}
}public class MyRunable implements Runnable { //Runable 任务实现 Callable 接口。 相较于实现 Runnable 接口的方式,方法可以有返回值,并且可以抛出异常。
执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。 FutureTask 是 Future 接口的实现类public class MyTest {
public static void main(String[] args) throws ExecutionException, InterruptedException { //创建一个线程的方式3//A://实现 Callable 接口。相较于实现 Runnable 接口的方式,方法可以有返回值,并且可以抛出异常。////B://执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。FutureTask 是 Future 接口的实现类////C://实现步骤//1. 创建一个类实现Callable 接口//2. 创建一个FutureTask类将Callable接口的子类对象作为参数传进去//3. 创建Thread类, 将FutureTask对象作为参数传进去//4. 开启线程//Callable 返回结果并且可能抛出异常的任务。实现者定义了一个不带任何参数的叫做 call 的方法。////Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。// 但是 Runnable 不会返回结果,并且无法抛出经过检查的异常。MyCallable myCallable = new MyCallable();FutureTaskintegerFutureTask = new FutureTask<>(myCallable);Thread th = new Thread(integerFutureTask);th.start();//获取线程执行完之后,返回的结果Integer integer = integerFutureTask.get();System.out.println(integer);
}
}public class MyCallable implements Callable<Integer> {//需要线程来执行的方法
return 100;
}
}同步的弊端: 当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
在java编程中,经常需要用到同步,而用得最多的也许是synchronized关键字了,因为synchronized关键字涉及到锁的概念,所以先来了解一些相关的锁知识。java的内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。java内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,直到线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。java的对象锁和类锁:java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,但是,两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的.public class CellRunable implements Runnable {
static int piao = 100;static Object obj = new Object();int i = 0;// th2
while (true) { if (i % 2 == 0) { synchronized (CellRunable.class) { //obj 就相当于一把锁 哪个线程一进入同步代码块,就会持有锁,那么这个把所不释放,其他线程就阻塞状态 if (piao > 0) { //模拟延迟 try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } //i-- i++ 先使用 后运算 //th3 //th2 100 System.out.println(Thread.currentThread().getName() + "正在售卖 " + (piao--) + " 张票"); } } //哪个线程出了同步代码块就会释放锁 } else { maiPiao(); } i++;}
}
//将 synchronized 加到方法上,我们叫做同步方法
//同步方法用的是this 这个多对象//静态同步方法使用的是当前类的字节码文件对象public static synchronized void maiPiao() { //System.out.println(this);// 相当于一把锁 哪个线程一进入同步代码块,就会持有锁,那么这个把所不释放,其他线程就阻塞状态if (piao > 0) { //模拟延迟try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } //i-- i++ 先使用 后运算 //th3 //th2 100 System.out.println(Thread.currentThread().getName() + "正在售卖 " + (piao--) + " 张票");}
}
}public class MyTest { public static void main(String[] args) { /*重复票数:由原子性所导致,原子性:不割再分割 i-- 不是原子性操作
synchronized (锁对象){
//需要被同步的代码} //锁对象:可以传任意的所对象
*/
CellRunable cellRunable = new CellRunable();
Thread th1 = new Thread(cellRunable);Thread th2 = new Thread(cellRunable);Thread th3 = new Thread(cellRunable);th1.setName("窗口1");th2.setName("窗口2");th3.setName("窗口3");th1.start();th2.start();th3.start();}}Lock和ReentrantLock
void lock()void unlock()public class CellRunable implements Runnable { static int piao = 100;static Object obj = new Object();static Lock lock = new ReentrantLock();@Override
public void run() {while (true) { lock.lock(); //加锁 if (piao > 0) { try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在售卖 " + (piao--) + " 张票"); } lock.unlock(); //释放锁}
}
}
public class MyTest { public static void main(String[] args) { / A:Lock锁的概述虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象LockB:Lock和ReentrantLockvoid lock ()void unlock ()/CellRunable cellRunable = new CellRunable(); Thread th1 = new Thread(cellRunable); Thread th2 = new Thread(cellRunable); Thread th3 = new Thread(cellRunable); th1.setName("窗口1"); th2.setName("窗口2"); th3.setName("窗口3"); th1.start(); th2.start(); th3.start();}
}
死锁问题概述
如果出现了同步嵌套,就容易产生死锁问题是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象同步代码块的嵌套案例死锁: 两个或者两个以上的线程,在抢占CPU的执行权的时候,都处于等待状态举例: 中国人和美国人一起吃饭中国人使用的筷子美国人使用的刀和叉中国人获取到了美国人的刀美国人获取到了中国人的一根筷子public interface LockeInterface {
//定义两把锁public static final Object objA=new Object();public static final Object objB=new Object();}public class MyThread extends Thread { boolean b;public MyThread(boolean b) {
this.b = b;}@Override
public void run() { if (b) { synchronized (LockeInterface.objA) { System.out.println("true objA 进来了");try { Thread.sleep(10);} catch (InterruptedException e) { e.printStackTrace();}synchronized (LockeInterface.objB) { System.out.println("true objB 进来了");}}} else { synchronized (LockeInterface.objB) { System.out.println("false objB 进来了");synchronized (LockeInterface.objA) { System.out.println("false objA 进来了");}}//}}}public class MyTest { public static void main(String[] args) { //死锁://两个或者两个以上的线程, 在抢占CPU的执行权的时候, 都处于等待状态MyThread th1 = new MyThread(true);MyThread th2 = new MyThread(false);th1.start();th2.start();}}使用步骤:
创建线程池对象创建Runnable实例提交Runnable实例关闭线程池public class MyTest {
public static void main(String[] args) { //线程池:是一个装有一定数量的线程对象的容器,可以帮我们管理这些线程对象,重复利用线程对象,去执行任务//程序开启一个线程,是比较耗费性能的,因为他涉及到更操作系统进行交互。//JDK1.5之后,帮我们提供好了线程池,我们可以直接拿来用//JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法//public static ExecutorService newCachedThreadPool ():根据任务的数量来创建线程对应的线程个数//public static ExecutorService newFixedThreadPool ( int nThreads):固定初始化几个线程//public static ExecutorService newSingleThreadExecutor ():初始化一个线程的线程池//这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者//ExecutorService 线程池//获取线程池对象ExecutorService executorService = Executors.newCachedThreadPool();//给线程池提交任务即可executorService.submit(new MyRunable());executorService.submit(new MyRunable());executorService.submit(new MyRunable());executorService.submit(new MyRunable());executorService.submit(new MyRunable());//关闭线程池executorService.shutdown();
}
}public class MyRunable implements Runnable{//关闭线程池executorService.shutdown();
}
}public class MyTest3 { public static void main(String[] args) throws ExecutionException, InterruptedException { //获取一个线程池。里面只有一个线程对象ExecutorService executorService = Executors.newSingleThreadExecutor();Futurefuture = executorService.submit(new MyCallable());Integer integer = future.get();System.out.println(integer);executorService.shutdown();
}
}public class MyCallable implements Callable<Integer> {return ++i;
}
}public class MyTest3 {
public static void main(String[] args) { //匿名内部类来开启线程//方式1new Thread() {//方式2 new Thread(new Runnable() { @Override public void run() { System.out.println("线程执行了"); } }).start();}
}
开发中
Quartz是一个完全由java编写的开源调度框架。public class MyTest {
public static void main(String[] args) throws ParseException { //定时器:Timer// 一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。Timer timer = new Timer();//可以到时间执行任务//void schedule (TimerTask task, Date time)//安排在指定的时间执行指定的任务。//void schedule (TimerTask task, Date firstTime,long period)//安排指定的任务在指定的时间开始进行重复的固定延迟执行。//void schedule (TimerTask task,long delay)//安排在指定延迟后执行指定的任务。//void schedule (TimerTask task,long delay, long period)//安排指定的任务从指定的延迟后开始进行重复的固定延迟执行。//TimerTask由 Timer 安排为一次执行或重复执行的任务。//boolean cancel ()//取消此计时器任务。MyTimerTask myTimerTask = new MyTimerTask(timer);//timer.schedule(myTimerTask, 3000); //2秒之后执行定时任务// timer.schedule(myTimerTask,2000,1000);//第一次等2秒执行,后面每隔一秒重复执行。//myTimerTask.cancel();取消定时任务//在指定的日期来执行任务String str = "2019-05-17 15:45:00";Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(str);timer.schedule(myTimerTask, date);// timer.cancel();取消定时器
}
}public class MyTimerTask extends TimerTask { Timer timer;public MyTimerTask(Timer timer) { this.timer=timer;}
@Override
public void run() { System.out.println("砰~~~ 爆炸了");//timer.cancel();}}转载于:https://blog.51cto.com/13852519/2398883