进程
进程就是正在运行的程序
如果一个程序(进程)只有一条执行路径,那么程序就是单线程程序
如果一个程序(进程)有多条执行路径,那么该程序就是多线程程序
进程是系统进行资源分配和调用的独立单位,每一个进程都有它自己的内存空间和系统资源
多进程可以在一个时间段内执行多个任务(并不是同时进行,是CPU在做着程序间的高效切换),可以提高CPU的使用率
线程
线程是程序的执行单元,执行路径,是程序使用cpu的最基本单位
多线程不是提高程序的执行速度,而是提高应用程序的使用率
如果一个进程的执行路径(线程)较多,那么会更有几率抢到CPU资源
线程的执行有随机性
并行和并发
并行是在某一个时间内同时运行多个程序,并发是在某一个时间点同时运行多个程序
Java程序的运行原理
由java命令启动JVM,JVM启动就相当于启动了一个进程,接着由该进程创建了一个主线程去调用main方法
JVM虚拟机的启动是多线程的,原因是垃圾回收线程也要先启动,否则很容易会出现内存溢出,所以最低启动了两个线程
Java实现多线程的程序
由于线程是依赖进程而存在的,所以先创建一个进程,进程由系统创建,所以需要调用系统功能创建一个进程,而Java是不能直接调用系统功能的,所以没有办法直接实现多线程程序,但是Java可以去调用C/C++写好的程序来实现多线程程序,由C/C++去调用系统功能创建进程,然后由Java去调用,提供一些类供我们使用,从而实现多线程程序
实现多线程程序的两种方式
方法1:继承Thread类
- 自定义类MyThread继承Thread类
- MyThread类里面重写run()
由于不是类中的所有代码都能被线程执行,java提供了Thread类中的run()用来包含那些被线程执行的代码 - 创建对象
- 启动线程
run()和start()的区别:run()仅仅是封装被线程执行的代码,直接调用是普通方法;start()首先启动了线程,然后再由jvm去调用该线程的run()方法import itcast1.MyThread; public class MyThreadDemo { public static void main(String[] args) { MyThread mt1 = new MyThread(); MyThread mt2 = new MyThread(); mt1.start(); mt2.start(); } }
public class MyThread extends Thread{ //重写run方法 public void run() { for(int x=0;x<300;x++) { System.out.println(x); } } }
获取线程的名称:public final String getName
设置线程的名称:public final String setName(String name)
线程休眠:public static void sleep(long millis)
礼让线程(等待该线程终止):public final void join()
后台线程(标记为守护线程):public final void setDaemon(boolean on)
中断线程:public final void stop():让线程终止,但是还可以使用
public void interrupt():中断线程,把线程的状态终止,并抛出一个异常
线程优先级仅仅表示线程获取的CPU时间片的几率,存在随机性
方法2:实现Runnable接口
- 自定义类MyRunnable接口
- 重写run()方法
- 创建MyRunnable的对象
- 创建Thread类的对象,并把步骤3的对象作为构造参数传递
import itcast1.MyRunnable;
public class MyRunnableDemo {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
//将mr作为构造参数传递
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
t1.start();
t2.start();
}
}
public class MyRunnable implements Runnable{
//重写run()方法
public void run() {
for(int x=0;x<300;x++) {
//获取当前执行线程的名称
System.out.println(Thread.currentThread().getName()+"---"+x);
}
}
}
方式2实现接口方式的好处:
解决java单继承带来的局限性(如果有一个类已经有父类,就无法继承Thread)
适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想(比如方式1 定义了一个成员变量,在调用时就有两个成员变量,而方式2只有一个)
两种方式实现电影院售票
方法1
public class SellTicket extends Thread{
//定义100张票
//private int tickets = 100;
//为了让多个线程对象共享这100张票,用static修饰
private static int tickets = 100;
public void run() {
//模拟一直有票
while(true) {
if(tickets > 0) {
System.out.println(getName()+"正在出售第"+(tickets--)+"张票");
}
}
}
}
/**
* 继承Thread类实现
* @author Emotion
*
*/
public class SellTicketDemo {
public static void main(String[] args) {
//三个售票窗口,此时建了三个tickets变量,其实不太合适,顾方法2好一些
SellTicket st1 = new SellTicket();
SellTicket st2 = new SellTicket();
SellTicket st3 = new SellTicket();
//给线程对象起名字
st1.setName("窗口1");
st2.setName("窗口2");
st3.setName("窗口3");
//启动线程
st1.start();
st2.start();
st3.start();
}
}
方法2
如何解决线程安全问题?
- 是否是多线程环境
- 是否有共享数据
- 是否有多条语句操作共享数据
1和2改变不了,所以改变3
具体思想:把多条语句操作共享数据包成一个整体,让某个线程在执行的时候,别人不能来执行
即Java提供的同步机制
多个线程必须是同一把锁(即同一个对象)
/*
* 通过加入延迟后发现
* 1.相同的票卖了多次
* CPU的一次操作必须是原子性的(即最简单基本的操作,比如当A操作的时候B就不操作)
* 2.出现了负数票
* 随机性和延迟导致
*/
public class SellTicket implements Runnable{
//定义100张票
private int tickets = 100;
//创建锁对象
private Object obj = new Object();
public void run() {
while(true) {
synchronized (obj) {
if(tickets > 0) {
//为了模拟更真实的场景,稍作休息
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在售卖第"+(tickets--)+"张票");
}
}
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
Thread t3 = new Thread(st);
t1.start();
t2.start();
t3.start();
}
}
同步
同步代码块:
synchronized(对象){
需要同步的代码;
}
同步的好处:解决了多线程的安全问题
同步的弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率;容易产生死锁
同步的两种方式:同步代码块和同步方法
同步方法的格式及锁对象问题:(如果锁对象是this,就可以考虑使用同步方法,否则能使用同步代码块的尽量使用同步代码块)
- 同步方法关键字加载方法上
- 同步方法锁对象是this
静态方法锁对象是类的字节码文件对象(class)
Lock锁
可以明确知道在哪里上了锁,在哪里释放了锁
- void lock():获取锁
- void unlock():释放锁
ReentrantLock是Lock的实现类
死锁
两个或两个以上的线程在争夺资源的过程中,发生的一种相互等待的现象
线程间通信问题
不同种类的线程间针对同一个资源的操作
线程池
线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用,使用线程池可以提高性能
JDK5之后有Executors工厂类来产生线程池
public class MyRunnable implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
for(int x=0; x<100; x++) {
System.out.println(Thread.currentThread().getName()+":"+x);
}
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorsDemo {
public static void main(String[] args) {
//创建一个线程池对象,控制要创建几个线程对象
ExecutorService pool = Executors.newFixedThreadPool(2);
//可以执行Runnable对象或Callable对象代表的线程
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
//结束线程池
pool.shutdown();
}
}
实现多线程程序的方式3(和线程池结合):Callable接口
用Callable接口的话和上面步骤差不多,但是有以下好处和弊端:
- 可以有返回值
- 可以抛出异常
- 代码比较复杂,所以一般不用
定时器
可以在指定的时间做某件事情,还可以重复的做某件事情,依赖Timer和TimerTask这两个类来实现
Timer定时,TimerTask做任务
常见问题
sleep()和wait()方法的区别
- sleep():不释放锁;必须指定时间
- wait():释放锁;可以不指定时间,也可以指定
为什么wait(),notify()等方法定义在Object类中?
因为这些方法的调用是依赖于锁对象的,而同步代码块的锁对象是任意锁,而Object代码是任意的对象,所以要定义在这里面
状态转换图