多线程
1. 进程和线程
1.1 进程
进程就是电脑中一个运行的软件,一个正在运行的程序
在做的一件事情就是一个进程
1.2 线程
线程理解为一个进程中步骤
我们在做一件事情时,分很多步骤,每一个步骤就是一个线程,同一时刻只能做一件事情
做菜是一个进程,买菜,洗菜,切菜,炒菜就是一个一个的线程。
注意:
进程间不能共享数据段地址,但同进程的线程之间可以。
QQ软件不能使用谷歌内存中数据,360不能使用QQ内存中的数据。
同一个进程中的线程,可以贡献数据,会产生数据不安全的情况。
1.3 进程是如何执行的
2. 创建线程
2.1 继承Thread类
class 类 extends Thread { public void run() { //这个线程被cpu选中执行时,执行的业务代码 } }
run方法是使用当前类创建线程,被cpu选中时,实行的业务代码
package com.qfedu; public class Demo01 { //main方法 就是一个线程(主线程) public static void main(String[] args) throws InterruptedException { MyThread mt = new MyThread(); mt.start(); // 开启了一个线程,和主线程共同竞争cpu执行时间 for (int i = 0; i < 100; i++) { System.out.println("main--"+i); } } } /* * 创建线程 * 1. 创建类继承Thread * 2. 重写run方法 该线程执行的业务代码 */ class MyThread extends Thread { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("自定义线程--"+i); } } }
2.2 实现Runnable接口
实现Runnable接口,重写run方法
class 类 implements Runnable { public void run() { //这个线程被cpu选中执行时,执行的业务代码 } } Thread t = new Thread(类的对象);
package com.qfedu; public class Demo02 { public static void main(String[] args) { Runnable runnable = new YourThread(); Thread t = new Thread(runnable, "线程1"); Thread t2 = new Thread(runnable, "线程2"); t.start(); // 开启了一个线程,和主线程共同竞争cpu执行时间 t2.start(); // 开启了一个线程,和线程1共同竞争cpu执行时间 } } /* * 实现Runnable接口 * 重写run方法 该线程执行的业务代码 */ class YourThread implements Runnable { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+"--"+i); } } }
3. 线程的状态
基本状态:
新建状态:创建线程对象
就绪状态:线程对象执行start()方法
运行状态:被cpu选中,执行run方法,如果在分配的时间片内运行结束,进入死亡状态,如果没有运行 结束,回到就绪状态。
死亡状态:run方法执行结束,进入死亡状态
package com.qfedu; public class Demo03 { public static void main(String[] args) { //1. 新建状态 System.out.println("新建状态"); HerThread mt = new HerThread(); /* * 2. 就绪状态 * * 这个线程可以和这个进程中的其他线程,共同竞争cpu的运行时间 * * 如果竞争到了,就执行它的run方法中代码 会进入运行状态 * - 如果在运行的时间片段内,run方法没有执行结束,那么就回到就绪状态 * - 如果run方法执行结束,该线程就over了 */ System.out.println("就绪状态"); mt.start(); } } class HerThread extends Thread { @Override public void run() { System.out.println("运行状态"); System.out.println("死亡状态"); } }
4. 卖票实例
四个窗口各卖100张票
四个窗口共卖100张票
package com.qfedu; public class Demo04 { /* * 四个窗口,每个窗口各卖100张票 */ public static void main(String[] args) { MyTicket t1 = new MyTicket("窗口1"); MyTicket t2 = new MyTicket("窗口2"); MyTicket t3 = new MyTicket("窗口3"); MyTicket t4 = new MyTicket("窗口4"); t1.start(); t2.start(); t3.start(); t4.start(); } } class MyTicket extends Thread { private int count = 100; public MyTicket(String name) { super(name); } public void run() { while(true) { if(count <= 0) { break; } System.out.println(Thread.currentThread().getName()+"卖了,第"+(100 - --count)+"张票"); } } }
package com.qfedu; public class Demo04_2 { /* * 四个窗口,共卖100张票 */ public static void main(String[] args) { Runnable runnable = new YourTicket(); Thread t1 = new Thread(runnable, "窗口1"); Thread t2 = new Thread(runnable, "窗口2"); Thread t3 = new Thread(runnable, "窗口3"); Thread t4 = new Thread(runnable, "窗口4"); t1.start(); t2.start(); t3.start(); t4.start(); } } class YourTicket implements Runnable { private int count = 100; @Override public void run() { while(true) { if(count <= 0) { break; } System.out.println(Thread.currentThread().getName()+"卖了,第"+(100 - --count)+"张票"); } } }
5. 线程常用方法
//休眠,啥都不干1000毫秒,进入等待状态,1000毫秒后回到就绪状态 public static native void sleep(long millis) throws InterruptedException; //加入,随机就变成了顺序,什么t线程执行结束,主线程才会回到就绪状态 public final void join() throws InterruptedException {} //主动放弃cpu资源,回到就绪状态 public static native void yield(); //设置线程调用优先级 1-10 默认是5 public final void setPriority(int newPriority) {} //是否是守护线程 非守护线程执行结束 守护线程自动结束 public final void setDaemon(boolean on) {}
线程在执行sleep(long millis) , join() 后进入等待状态
线程在执行yield()后,回到就绪状态
package com.qfedu; public class Demo05 { public static void main(String[] args) throws InterruptedException { for(int i=1; i<100; i++) { /* * 休眠,啥都不干1000毫秒,进入等待状态,1000毫秒后回到就绪状态 * * 在等待的过程中,是不释放锁的 */ Thread.sleep(1000); System.out.println("吃西瓜,现在是第" + i +"个"); } } }
package com.qfedu; public class Demo06 { /* * 30个桃子 * * 20个西瓜 * * 开始随机的吃,如果桃子已经吃了10个,还有西瓜,那就先把西瓜吃完,才能吃桃子 * * * 随机 改成 顺序 */ public static void main(String[] args) throws InterruptedException { EatWatermelon t = new EatWatermelon(); t.start(); for(int i=1; i<=30; i++) { System.out.println("吃了第"+i+"个桃子"); if(i == 10) { //加入,随机就变成了顺序,什么t线程执行结束,主线程才会回到就绪状态 t.join(); } } } } //吃西瓜线程 class EatWatermelon extends Thread { @Override public void run() { for(int i=1; i<=20; i++) { System.out.println("吃了第"+i+"个西瓜"); } } }
package com.qfedu; public class Demo07 { public static void main(String[] args) { YieldThread yt = new YieldThread(); yt.start(); for(int i=1; i<=100; i++) { System.out.println("main--"+i); if(i%3 == 0) { Thread.yield(); // 如果是3的倍数,就主动放弃cpu资源,回到就绪状态 } } } } class YieldThread extends Thread { @Override public void run() { for(int i=1; i<=100; i++) { System.out.println("YieldThread--"+i); if(i%8 == 0) { yield(); // 如果是8的倍数,就主动放弃cpu资源,回到就绪状态 } } } }
package com.qfedu; public class Demo08 { public static void main(String[] args) { DaemonThread t1 = new DaemonThread(); t1.setName("非守护线程"); t1.setPriority(10); DaemonThread t2 = new DaemonThread(); t2.setName("守护线程"); t2.setDaemon(true); t2.setPriority(1); t1.start(); t2.start(); } } class DaemonThread extends Thread { @Override public void run() { for(int i=1; i<=100; i++) { System.out.println(getName() + "--"+i); } } }
6. 线程安全
一个进程中的多个线程在执行的过程中可能会对一个变量进行操作,这样就有可能出现数据混乱的问题,可以通过对程序加锁来解决这种情况。
synchronized (对象) { //代码块 }
被加锁的代码,在同一时刻只能由一个线程执行
想要执行加锁的代码,必须 获取 对象的锁
package com.qfedu; public class Demo09 { public static void main(String[] args) { Runnable runnable = new HerTicket(); Thread t1 = new Thread(runnable, "窗口1"); Thread t2 = new Thread(runnable, "窗口2"); Thread t3 = new Thread(runnable, "窗口3"); Thread t4 = new Thread(runnable, "窗口4"); t1.start(); t2.start(); t3.start(); t4.start(); } } class HerTicket implements Runnable { private int count = 100; Object o = new Object(); @Override public void run() { while(true) { //对代码块进行加锁,这个代码块在一个时刻,只能由一个线程执行 synchronized (o) { if(count <= 0) { break; } System.out.println(Thread.currentThread().getName()+"卖了,第"+(100 - --count)+"张票"); } } } }