进程的概念:
进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。例如在Windows系统中,一个运行的exe就是一个进程。
线程的概念:
线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。
一、Java中的线程
Java编写程序都运行在在Java虚拟机(JVM)中,在JVM的内部,程序的多任务是通过线程来实现的。每用java命令启动一个java应用程序,就会启动一个JVM进程。在同一个JVM进程中,有且只有一个进程,就是它自己。在这个JVM环境中,所有程序代码的运行都是以线程来运行。每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程。每个线程都可以或不可以标记为一个守护程序。当某个线程中运行的代码创建一个新Thread
对象时,该新线程的初始优先级被设定为创建线程的优先级,并且当且仅当创建线程是守护线程时,新线程才是守护程序。同时JVM也负责线程的调度。线程调度是值按照特定的机制为多个线程分配CPU的使用权。调度的模式有两种:分时调度和抢占式调度。分时调度是所有线程轮流获得CPU使用权,并平均分配每个线程占用CPU的时间;抢占式调度是根据线程的优先级别来获取CPU的使用权。JVM的线程调度模式采用了抢占式模式。
1.多线程实现方式一 继承 Thread类
public class TestThread1 extends Thread { public void run() { for(int i=0;i<10;i++){ System.out.println("TestThread1=======>"+i); } } } class TestThread2 extends Thread{ public void run() { for(int i=0;i<10;i++){ System.out.println("TestThread2========>"+i); } } } //测试 public static void main(String[] args) { TestThread1 th1=new TestThread1(); TestThread2 th2=new TestThread2(); th1.start(); th2.start(); } //结果 TestThread1=======>0 TestThread2========>0 TestThread1=======>1 TestThread2========>1 TestThread1=======>2 TestThread2========>2 TestThread1=======>3 TestThread2========>3 TestThread1=======>4 TestThread2========>4 TestThread1=======>5 TestThread2========>5 TestThread1=======>6 TestThread2========>6 TestThread1=======>7 TestThread2========>7 TestThread1=======>8 TestThread2========>8 TestThread1=======>9 TestThread2========>9
2、实现 Runnable 接口 (静态代理模式)
public class RunnableTest1 implements Runnable{ public void run() { for(int i=0;i<10;i++){ System.out.println("RunnableTest1==============>"+i); } } } class RunnableTest2 implements Runnable { public void run() { for(int i=0;i<10;i++){ System.out.println("RunnableTest2==============>"+i); } } } //测试 public static void main(String[] args) { //创建真实角色 RunnableTest1 rt1=new RunnableTest1(); RunnableTest2 rt2=new RunnableTest2(); //创建代理角色+真实角色的引用 Thread thread1=new Thread(rt1); Thread thread2=new Thread(rt2); //启动 thread1.start(); thread2.start(); } //结果 RunnableTest1==============>0 RunnableTest1==============>1 RunnableTest2==============>0 RunnableTest1==============>2 RunnableTest1==============>3 RunnableTest1==============>4 RunnableTest2==============>1 RunnableTest1==============>5 RunnableTest2==============>2 RunnableTest1==============>6 RunnableTest1==============>7 RunnableTest2==============>3 RunnableTest1==============>8 RunnableTest2==============>4 RunnableTest1==============>9 RunnableTest2==============>5 RunnableTest2==============>6 RunnableTest2==============>7 RunnableTest2==============>8 RunnableTest2==============>9
两种实现方式的区别和联系:
在程序开发中只要是多线程肯定永远以实现Runnable接口为主,因为实现Runnable接口相比继承Thread类有如下好处:
1 //资源共享 2 public class TrainTest implements Runnable { 3 private int num=5; 4 public void run() { 5 while(true){ 6 if(num<=0){ 7 break; 8 } 9 System.out.println(Thread.currentThread().getName()+"抢到第"+num--+"张票"); 10 } 11 } 12 public static void main(String[] args) { 13 TrainTest tt=new TrainTest(); 14 Thread t1=new Thread(tt, "NNNN"); 15 Thread t2=new Thread(tt, "AAAA"); 16 Thread t3=new Thread(tt, "MMMMM"); 17 t1.start(); 18 t2.start(); 19 t3.start(); 20 } 21 } 22 MMMMM抢到第5张票 23 NNNN抢到第4张票 24 AAAA抢到第3张票 25 NNNN抢到第1张票 26 MMMMM抢到第2张票
3、使用 Callable 接口
常用的Thread类在run方法执行完之后是没有返回值的,要实现子线程完成任务后返回值给主线程需要借助第三方转存。
Callable接口则提供了一种有返回值的多线程实现方法。
package com.jalja.jdk17.thread; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class CallableTest implements Callable<Integer> { private String name;//名称 private long time;//延时时间 private boolean flag=true; private int step=0;//步 public CallableTest() { super(); } public CallableTest(String name) { super(); this.name = name; } public CallableTest(String name, long time) { super(); this.name = name; this.time = time; } public Integer call() throws Exception { while(flag){ Thread.sleep(time);//延时 走的快慢 step++; } return step; } public String getName() { return name; } public void setName(String name) { this.name = name; } public long getTime() { return time; } public void setTime(long time) { this.time = time; } public boolean isFlag() { return flag; } public void setFlag(boolean flag) { this.flag = flag; } public int getStep() { return step; } public void setStep(int step) { this.step = step; } public static void main(String[] args) throws Exception{ //创建线程 ExecutorService es=Executors.newFixedThreadPool(2); CallableTest ct1=new CallableTest("乌龟",100); CallableTest ct2=new CallableTest("兔子",50); //获取值 Future<Integer>result1=es.submit(ct1); Future<Integer>result2=es.submit(ct2); Thread.sleep(2000); ct1.setFlag(false); ct2.setFlag(false); int num1=result1.get(); int num2=result2.get(); System.out.println("乌龟跑---->"+num1+"步"); System.out.println("兔子跑---->"+num2+"步"); //停止服务 es.shutdown(); } } //结果 乌龟跑---->20步 兔子跑---->40步
4、线程的终止
停止线程的方式:
1) 自然终止:线程体正常执行完毕
2) 外部干涉:
a.定义线程体使用标识
b.在线程体中使用标识
c.提供对外改变标识的方法
d.外部根据条件调用 该方法
//2外部干涉 public class ThreadColse implements Runnable { // 定义线程体使用标识 private boolean flag=true; public void run() { //在线程体中使用标识 while(flag){ System.out.println("执行------》"); } } //提供对外改变标识的方法 public void stop(){ flag=false; } public static void main(String[] args) { ThreadColse tc=new ThreadColse(); new Thread(tc).start(); //外部根据条件调用 该方法 for(int i=0;i<20;i++){ if(i==10){ tc.stop(); } System.out.println("运行了——————————》"+i); } } }
二、线程的状态
1、创建状态。在生成线程对象,并没有调用该对象的start方法。
2、就绪状态。当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时cpu并没有调度它。
3、运行状态。线程调度程序将处于就绪状态的线程设置为当前线程(cpu调度该线程),此时线程就进入了运行状态,开始运行run函数当中的代码。
4、阻塞状态。线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。
a.join 合并线程 b、yield() 暂停自己的线程 c、 sleep() 线程进入休眠 不释放锁
5、死亡状态。如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡。对于已经死亡的线程,无法再使用start方法令其进入就绪。
三、线程同步
为什么需要同步
public class TrainTest implements Runnable { private int num=5; public void run() { while(true){ if(num<=0){ break; } try { new Thread().sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"抢到第"+num--+"张票"); } } public static void main(String[] args) { TrainTest tt=new TrainTest(); Thread t1=new Thread(tt, "NNNN"); Thread t2=new Thread(tt, "AAAA"); Thread t3=new Thread(tt, "MMMMM"); t1.start(); t2.start(); t3.start(); } } NNNN抢到第4张票 AAAA抢到第5张票 MMMMM抢到第3张票 NNNN抢到第2张票 AAAA抢到第1张票 MMMMM抢到第0张票 出现了 第0张票 这样就出现了资源错误的现象
同步方法
使用 synchronized
public class TrainTest implements Runnable { private int num=5; private boolean flag=true; public void run() { while(flag){ test(); } } private synchronized void test(){ if(num<=0){ flag=false; return; } try { new Thread().sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"抢到第"+num--+"张票"); } public static void main(String[] args) { TrainTest tt=new TrainTest(); Thread t1=new Thread(tt, "NNNN"); Thread t2=new Thread(tt, "AAAA"); Thread t3=new Thread(tt, "MMMMM"); t1.start(); t2.start(); t3.start(); } } NNNN抢到第5张票 NNNN抢到第4张票 NNNN抢到第3张票 NNNN抢到第2张票 NNNN抢到第1张票 这样 就没有 出现第 0张票 的现象了, 但观察程序会发现运行速度慢了很多,因为线程出现等待的现象
线程的死锁:
死锁的原因是由于 两个线程相互等待 对方已被锁定的资源
package com.jalja.jdk17.thread; //过多的同步方法 会 造成死锁 public class SSTest { public static void main(String[] args) { Object g=new Object(); Object m=new Object(); Test01 t1=new Test01(g,m); Test02 t2=new Test02(g,m); Thread th1=new Thread(t1); Thread th2=new Thread(t2); th1.start(); th2.start(); } } class Test01 implements Runnable{ Object goods; Object money; public Test01(Object goods, Object money) { super(); this.goods = goods; this.money = money; } private void test(){ synchronized (goods) { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (money) { } } System.out.println("一手给货"); } public void run() { while(true){ test(); } } } class Test02 implements Runnable{ Object goods; Object money; public Test02(Object goods, Object money) { super(); this.goods = goods; this.money = money; } private void test(){ synchronized (money) { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (goods) { } } System.out.println("一手给钱"); } public void run() { while(true){ test(); } } }
解决死锁问题
package com.jalja.jdk17.Polymorphic; /** * 解决死锁的方式 * 生产者消费着模式 信号灯法 * @author Administrator * */ public class Movies { private String pic; // true :生产者 生产,消费者等待,生产完毕后通知消费者消费 //false:消费者 消费 生产者等待,消费者完成消费后通知生产 private boolean flag=true; public synchronized void play(String pic){ if(!flag){ try { this.wait();//等待 释放锁 } catch (InterruptedException e) { e.printStackTrace(); } } //开始生产 try { Thread.sleep(500);//模拟生产耗时500毫秒 } catch (InterruptedException e) { e.printStackTrace(); } this.pic=pic;//生产出资源 System.out.println("生产=======》"+pic); //通知消费者消费 this.notify(); //生产者停止生产 this.flag=false; } public synchronized void watch(){ if(flag){//消费者等待 try { this.wait();//等待 释放锁 } catch (InterruptedException e) { e.printStackTrace(); } } //开始消费 try { Thread.sleep(500);//模拟消费耗时500毫秒 } catch (InterruptedException e) { e.printStackTrace(); } this.pic=pic;//消费资源 System.out.println("消费=======》"+pic); //通知生产 this.notifyAll(); //消费者停止消费 this.flag=true; } } package com.jalja.jdk17.Polymorphic; public class Player implements Runnable { private Movies movies; public Player(Movies movies) { super(); this.movies = movies; } public void run() { for(int i=0;i<5;i++){ if(i%2==0){ movies.play("《无间道》"); }else{ movies.play("《道道道道道道》"); } } } } package com.jalja.jdk17.Polymorphic; public class Watcher implements Runnable { private Movies movies; public Watcher(Movies movies) { super(); this.movies = movies; } public void run() { for(int i=0;i<5;i++){ movies.watch(); } } public static void main(String[] args) { Movies m=new Movies(); Player p=new Player(m); Watcher w=new Watcher(m); Thread th1=new Thread(p); Thread th2=new Thread(w); th1.start(); th2.start(); } } //结果 生产=======》《无间道》 消费=======》《无间道》 生产=======》《道道道道道道》 消费=======》《道道道道道道》 生产=======》《无间道》 消费=======》《无间道》 生产=======》《道道道道道道》 消费=======》《道道道道道道》 生产=======》《无间道》 消费=======》《无间道》