1、什么是进程
程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。而进程是程序在处理机上的一
次执行过程,它是一个动态的概念。即一个独立运行的程序就可以看作为一个进程。
进程是一个具有一定独立功能的程序,一个实体,每一个进程都有它自己的地址空间。
2、进程的状态
进程执行时的间断性,决定了进程可能具有多种状态。事实上,运行中的进程具有以下三种基本状态。
1)就绪状态(Ready)
2)运行状态(Running)
3)阻塞状态( Blocked)
3、线程
线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干程序又可以划分成若干个线程。
线程:是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个线程(单线程程序)。
一个程序可以同时执行多个任务,来提高效率。例如:
(1)、同时下载多个电影
(2)、同时与多人聊天
并行:就是两个任务同时运行(多个CPU)
并发:是指两个人任务同时请求运行,而处理器次只能接受一 个任务, 就会把两个任务安排轮流执行,由于CPU时间片运行时间较短,就会感觉两个任务在同时执行。
线程实现的两种方式
在Java中如果要想实现多线程的操作,有两种实现方法:
(1) 一种是继承Thread类
class MyThread extends Thread{
public void run(){//逻辑处理}
MyThread mt = new MyThread();
mt.start();
(2)另外一种是实现Runnable接口
class MyRunnable implements Runnable{
public void run(){//逻辑处理}
MyRunnable mr = new MyRunnable();
Thread t= new Thread(mr);
t.start();
public class Demo1 {
/**
* 建议使用接口方式
* 因为接口可以实现多个,但是类智能继承一个
*
* start()与run()?
*
* 1,run方法是Runnable接口中定义的,start方法是Thread类定义的。
* 所有实现Runnable的接口的类都需要重写run方法,run方法是线程默认要执行的方法,是绑定操作系统的,也是线程执行的入口。
* start方法是Thread类的默认执行入口,Thread又是实现Runnable接口的。
* 要使线程Thread启动起来,需要通过start方法,表示线程可执行状态,调用start方法后,则表示Thread开始执行,此时run变成了Thread的默认要执行普通方法。
*
* 2)通过start()方法,直接调用run()方法可以达到多线程的目的。
* 通常,系统通过调用线程类的start()方法来启动一个线程,此时该线程处于就绪队列,而非运行状态,这也就意味着这个线程可以被JVM来调度执行。
* 在调度过程中,JVM会通过调用线程类的run()方法来完成试机的操作,当run()方法结束之后,此线程就会终止。
* 如果直接调用线程类的run()方法,它就会被当做一个普通的函数调用,程序中任然只有主线程这一个线程。
* 也就是说,star()方法可以异步地调用run()方法,但是直接调用run()方法确实同步的,因此也就不能达到多线程的目的。
*
* 注: run()和start()的区别可以用一句话概括:单独调用run()方法,是同步执行;通过start()调用run(),是异步执行。
*
* 线程休眠?
* 在当前线程的执行中,暂停指定的毫秒数,释放CPU的时间片
*/
public static void main(String[] args) {
//继承Thread类
thread1 t1 = new thread1();
t1.start();
//实现Runnable接口
Thread t2 = new Thread(new thread2());
t2.start();
}
}
class thread1 extends Thread{
@Override
public void run() {
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class thread2 implements Runnable{
@Override
public void run() {
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程的加入与中断
join():加入线程,让调用的时间先执行,可以指定时间。
interrupt():中断标记,休眠抛出异常会清除该标记
interrupted():判断是否有中断标记
public class Demo2 {
/**
* join()方法主要是同步,使线程之间由并行执行变为串行执行
*/
public static void main(String[] args) {
Thread t = new Thread(new thread3());//join interrupt() interrupted()
// thread4 thread4 = new thread4();
// Thread t2 = new Thread(thread4);//自定义中断
// t2.start();
for (int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+"---"+i);
if(i == 10){
//join():此时让t线程先走,之后再让主线程走
try {
t.start();
t.join();
} catch (InterruptedException e) {
}
// thread4.setFlag(false);//自定义的中断标志
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class thread3 implements Runnable{
@Override
public void run() {
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"---"+i);
if(i == 5){
/**
* 先有一个中断标记,但是在线程休眠的时候,被中断会导致抛出异常且使这个中断失效
* 所以在抛异常的地方再给一个标记,这样Thread.interrupted()在判断时才能根据标记判断
* 否则抛出异常,标记就清除了
*
* 不过最好使用自定义的中断方式
*/
Thread.currentThread().interrupt();
if(Thread.interrupted()){//一个是否中断的标记
break;//此处才为真正的中断
}
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();//只是一个中断标记,并不会真的中断
e.printStackTrace();
}
}
}
}
//自定义中断
class thread4 implements Runnable{
private boolean flag;
public thread4() {
this.flag = true;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
int i =0;
while (flag==true){
System.out.println(Thread.currentThread().getName()+"---"+i++);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
}
}
线程的守护(Deamon)与yeild
setDaemon(boolean on) 将此线程标记为daemon线程或用户线程。
isDaemon() 测试这个线程是否是守护线程。
yield() 暂停当前正在执行的线程对象,并执行其他线程。(用的非常少)其他的方法与优先级
long getld() 返回该线程的标识符。
String gtName() 返回该线程的名称。
void setName(String name) 改变线程名称,使之与参数name相同。
boolean isAlive() 测试线程是否处于活动状态。
void setPriority(int newPriority) 更改线程的优先级。
static int MAX_PRIORITY 线程可以具有的最高优先级。
static int MIN_PRIORITY 线程可以具有的最低优先级。
static int NORM_PRIORITY 分配给线程的默认优先级。
public class Demo3 {
/**
* setDaemon(boolean on) 将此线程标记为daemon线程或用户线程。
* isDaemon() 测试这个线程是否是守护线程。
* yield() 暂停当前正在执行的线程对象,并执行其他线程。
*
* 线程可以分为守护线程和用户线程,当唯一的线程为守护线程时,JVM虚拟机退出
*
* long getld() 返回该线程的标识符。
* String gtName() 返回该线程的名称。
* void setName(String name) 改变线程名称,使之与参数name相同。
* boolean isAlive() 测试线程是否处于活动状态。
* void setPriority(int newPriority) 更改线程的优先级。
* static int MAX_PRIORITY 线程可以具有的最高优先级。
* static int MIN_PRIORITY 线程可以具有的最低优先级。
* static int NORM_PRIORITY 分配给线程的默认优先级。
*
*/
public static void main(String[] args) {
Thread t1 = new Thread(new thread5());
//设置高的优先级可以提高该线程抢CPU时间片的概率
t1.setPriority(Thread.MAX_PRIORITY);
t1.setName("刘铂西的私人线程");
t1.setDaemon(true);//把线程设置为守护线程
System.out.println(t1.isAlive());//活动状态--start以前 false
t1.start();
System.out.println(t1.isAlive());//活动状态--start后 true
//main线程
for (int i=0;i<50;i++){
System.out.println(Thread.currentThread().getName()+"-->"+i);
if(i == 5){
Thread.yield();//让出本次CPU时间片,下次我还抢!
}
try {
Thread.sleep(100);//主线程走的更快,只留下守护线程JVM就不会再工作了
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class thread5 implements Runnable{
@Override
public void run() {
for (int i=0;i<50;i++){
System.out.println(Thread.currentThread().getName()+"---"+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程的同步(重点)
1、多线程共享数据
在多线程的操作中,多个线程有可能同时处理同一个资源,这就是多线程中的共享数据。
2、线程同步(在多线程编程时,一些敏感数据不允许被 多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问, 以保证数据的完整性)
解决数据共享问题,必须使用同步,所谓同步就是指多个线程在同一个时间段内只能有一个线程执行指定代码,其他线程要等待此线程完成之后才可以继续执行。线程进行同步,有以下两种方法:
(1)同步代码块
synchronized(要同步的对象){要同步的操作;}
(2)同步方法
public synchronized void method(){要同步的操作;}
(3) Lock ( ReentrantLock){加锁}
以多个窗口抢票为例,没有同步的错误示范:
public class Demo4 {
/**
* 未同步
*/
public static void main(String[] args) {
//模拟售票
thread6 thread6 = new thread6();
Thread t1 = new Thread(thread6);//第一个窗口
Thread t2 = new Thread(thread6);//第二个窗口
t1.start();
t2.start();
}
}
class thread6 implements Runnable{
private int ticket = 10;//售票
@Override
public void run() {
for (int i=0;i<20;i++){
if(ticket>0){
ticket--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("您购买的票剩余"+ticket+"张");
}
}
}
}
结果:
线程同步下的代码:
public class Demo4 {
/**
* 已同步
*/
public static void main(String[] args) {
//模拟售票
thread6 thread6 = new thread6();
Thread t1 = new Thread(thread6);//第一个窗口
Thread t2 = new Thread(thread6);//第二个窗口
t1.start();
t2.start();
}
}
class thread6 implements Runnable{
private int ticket = 10;//售票
//private Object object = new Object();//线程加锁,一个标志,能让线程知道操作的是同步的数据
@Override
public void run() {
for (int i=0;i<20;i++){
//synchronized (object){//虚拟机实现的过程,一般用this
synchronized (this){
if(ticket>0) {
ticket--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(ticket == 0){
System.out.println("票售光了!!!");
}else {
System.out.println("您购买的票剩余" + ticket + "张");
}
}
}
}
}
}
结果:
也可以直接抽取成一个实现了synchronized 接口的方法,在使用时直接调用即可
//同步方法:同步的对象是当前的对象
private synchronized void extracted() {
if(ticket>0) {
ticket--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(ticket == 0){
System.out.println("票售光了!!!");
}else {
System.out.println("您购买的票剩余" + ticket + "张");
}
}
}
使用LOCK --- 更加灵活的代码控制
//LocK --- ReentrantLock 互斥锁 --- 用锁会更加灵活,锁和解锁都是由代码主动操作,比如符合特定条件就加锁或解锁(当然此处不做展示..)
private ReentrantLock lock = new ReentrantLock();
private void extracted2() {
lock.lock();//锁
/**
* 最好搭配 try catch finally 控制锁的释放来避免死锁
*/
try {
if(ticket>0) {
ticket--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(ticket == 0){
System.out.println("票售光了!!!");
}else {
System.out.println("您购买的票剩余" + ticket + "张");
}
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();//释放锁
}
}
3、 同步的准则
当编写synchronized块时,有几个简单的准则可以遵循,这些准则在避免死锁和性能危险的风险方面大有帮助:
(1)使代码块保持简短。把不随线程变化的预处理和后处理移出synchronized块。
(2)不要阻塞。如InputStream.read()。
(3)在持有锁的时候,不要对其它对象调用该对象的同步方法。(其他对象的锁不释放,本线程的锁也不能释放,干等待...)
过多的同步有可能出现死锁, 死锁的操作一般是在程序运行的时候 才有可能出现。
多线程中要进行资源的共享,就需要同步,但同步过多,就可能造成死锁。死锁案例:
public class Demo5 {
/**
* 死锁 --- 造成死锁的根本原因就是同步
* 过多的同步有可能出现死锁,死锁的操作一般是在程序运行的时候 才有可能出现。
* 多线程中要进行资源的共享,就需要同步,但同步过多,就可能造成死锁。
*
* 该案例死锁的根本原因是:两者之间都相互调用了同步的方法,两者都相互等待
*/
public static void main(String[] args) {
new DeadThread();
}
}
class Customer{
public synchronized void say(Waiter w){
System.out.println("顾客说:先吃饭再买单!");
w.doService();
}
public synchronized void doService(){
System.out.println("顾客说:同意了,先买单再吃饭!");
}
}
class Waiter{
public synchronized void say(Customer c){
System.out.println("服务员说:先买单再吃饭!");
c.doService();
}
public synchronized void doService(){
System.out.println("服务员说:同意了,先吃饭再买单!");
}
}
class DeadThread implements Runnable{
Customer c = new Customer();
Waiter w = new Waiter();
public DeadThread(){
new Thread(this).start();
w.say(c);
}
@Override
public void run() {
c.say(w);
}
}
生产者与消费者案例
多线程的开发中有一个最经典的操作案例,就是生产者-消费者,生产者不断生产产品,消
费者不断取走产品。列如:饭店里的有一个厨师和一个服务员,这个服务员必须等待厨师准备好膳食。当厨师准备好时,他会通知服务员,之后服务员上菜,然后返回继续等待。这是一个任务协作的示例,厨师代表生产者,而服务员代表消费者。
错误实例:
public class Demo6 {
/**
* 消费者与生产者
* 错误实例
*/
public static void main(String[] args) {
//产品
Food food = new Food();
//生产者
Producter producter = new Producter(food);
//消费者
Customers customers = new Customers(food);
//两个进程同时消费与生产
Thread p = new Thread(producter);
Thread c = new Thread(customers);
p.start();
c.start();
}
}
//消费者
class Customers implements Runnable{
private Food food;
public Customers(Food food){
this.food = food;
}
@Override
public void run() {
for (int i=0;i<10;i++){
food.get();
}
}
}
//生产者
class Producter implements Runnable{
private Food food;
public Producter(Food food){
this.food = food;
}
@Override
public void run() {
for (int i=1;i<=10;i++){
if(i%2==0){//只生产两种菜
food.set("老八小汉堡","好吃的不得了!");
}else {
food.set("老八大趴鸭","吃了难回家!!");
}
}
}
}
//产品
class Food{
private String name;//名字
private String desc;//,描述
//生产产品
public void set(String name,String desc){
this.setName(name);
try {
Thread.sleep(500);//模拟生产时间
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setDesc(desc);
}
//消费产品
public void get(){
try {
Thread.sleep(500);//模拟消费时间
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName()+"-->"+this.getDesc());
}
public Food() {
}
public Food(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
结果完全混乱:
问题出现在Food对象的set和get方法上,导致两个线程相互争而导致的数据 ‘脏乱’,但是如果只给方法加了同步只会将Food对象的两个属性保持一致,但是线程之间的竞争还会导致不能正常生产。给两个方法加了同步后的运行结果如下图所示:
正确示例加锁和同步:
public class Demo6 {
/**
* 消费者与生产者
*
* 同步
*/
public static void main(String[] args) {
//产品
Food food = new Food();
//生产者
Producter producter = new Producter(food);
//消费者
Customers customers = new Customers(food);
//两个进程同时消费与生产
Thread p = new Thread(producter);
Thread c = new Thread(customers);
p.start();
c.start();
}
}
//消费者
class Customers implements Runnable{
private Food food;
public Customers(Food food){
this.food = food;
}
@Override
public void run() {
for (int i=0;i<10;i++){
food.get();
}
}
}
//生产者
class Producter implements Runnable{
private Food food;
public Producter(Food food){
this.food = food;
}
@Override
public void run() {
for (int i=1;i<=10;i++){
if(i%2==0){//只生产两种菜
food.set("秘制小汉堡","好吃的不得了!");//双数生汉堡
}else {
food.set("酱香大趴鸭","吃了难回家!!");//单数生产趴鸭
}
}
}
}
//产品
class Food{
private String name;//名字
private String desc;//,描述
private boolean flag = true;//true表示可以生产,false表示可以消费,两者互斥
//生产产品
public synchronized void set(String name,String desc){
if(flag==false){
try {
/**
* sleep()是不释放监视器的所有权
* 而wait()是释放监视器的所有权
*/
this.wait();//线程进入等待,释放监视器的所有权(就是对象锁)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//因为被锁住了,无需else,被唤醒就继续
this.setName(name);
try {
Thread.sleep(500);//模拟生产时间
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setDesc(desc);
//生产完,修改锁的标志并唤醒其他的线程
flag=false;
this.notify();
}
//消费产品
public synchronized void get(){
if(flag==true){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(500);//模拟消费时间
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName()+"-->"+this.getDesc());
flag=true;
this.notify();
}
public Food() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
通过这,可以简单了解到wait和sleep的区别:
sleep:让线程进入休眠状态,让出CPU的时间片,不释放对象监视器的所有权(对象锁)。
wait:让线程进入等待状态,让出CPU的时间片,并释放对象监视器的所有权,等待其它线程通过notify方法来唤醒。①.(最重要区别)sleep() 方法没有释放锁,而 wait() 方法释放了锁;
②.wait()通常被用于线程间交互/通信,sleep() 通常被用于暂停执行 ;
③.wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的notify() 或者 notifyAll() 方法。
④.sleep() 方法执行完成后,线程会自动苏醒。或者可以使用 wait(long timeout) 超时后线程会自动苏醒
来自:wait与sleep
线程的生命周期看以下博客内容
线程池:线程池