多线程01
A.进程
要学习多线程,得学习线程,但线程是依赖于进程而存在的,所以先学习进程
1)什么是进程
通过查看任务管理器,发现正在运行的程序就是一个进程
2)多进程有什么意义
现在的计算机是一个多进程计算机,在去做一件事情的同时可以做另一件事情;提高CPU的使用率
打游戏---------开启了游戏进程
听音乐---------开启了音乐进程
在打游戏的同时去听音乐,这两个进程是同时发生的吗?
不是同时在进行,感觉他们在同时进行,这个因为CPU在两者之间进行着高效的切换(抢占CPU的执行权)
B.线程
1)什么是线程
线程是依赖于进程而存在的,把线程可以看作是进程中的某一个任务
比如迅雷软件,扫雷(开始--->玩的同时--->计时)
2)CPU执行权
具有随机性!(每一个线程在抢占CPU的执行权具有随机性)
3)单线程
程序的执行路径只有一条
4)多线程
程序的执行路径有多条
5)并行和并发
都是同时的意思
前者是逻辑上的同时,指的是在同一个时间内(时间段)同时发生
后者是物理上的同时,指的是在同一个时间点(10:30:30进行开抢:购物商城...)上同时发生
6)面试题
Jvm:Java虚拟机是多线程的吗?
程序能够被执行:是因为里面main()方法,main()被Jvm所识别,启动Jvm,实质上式启动了主线程(main方法所在的就是主线程),通过以下代码:两个输出语句之间做了多次创建对象,程序依然能够执行打印出来,Java语言会自动启动垃圾回收期(垃圾回收线程),保证内存不会溢出
java虚拟机是多线程,至少开启了两个线程:
main():主线程
垃圾回收线程:保证及时的回收不用的对象
public class ThreadDemo { public static void main(String[] args) { System.out.println("hello"); new Object(); new Object(); new Object(); new Object(); new Object(); new Object(); new Object(); //............new Object(); System.out.println("world"); } }
C.实现多线程—Thread
1)如何实现
Java语言是不能直接调用系统功能(用系统功能创建对象)
Java语言可以通过C/C++底层已经调用了系统功能
Java提供了一个类:Thread类
Java虚拟机允许应用程序并发的运行多个执行线程
2)多线程开发步骤
a.自定义一个类,该类继承自Thread类
b.重写该类中的run()方法
// 1)自定义该类继承字Thread public class MyThread extends Thread { // 2)重写run方法 @Override public void run() { // 该方法里面需要执行一些耗时的操作:循环语句.../sleep() for (int i = 0; i < 10; i++) { System.out.println(getName() + ":" + i); // getName()获取线程名称,为了区分不同线程,这里先用,后面详细讲解 } } }
c.主线程中去创建该类对象
d.启动线程
public class ThreadDemo { public static void main(String[] args) { // 创建对象 // MyThread my = new MyThread(); // 有对象了,启动线程,执行耗时的操作! // my.run(); // 在调用一次 // my.run(); // 发现:调用run()方法,每次调用,执行完毕之后 // 并没有出现线程抢占CPU的执行权出现的一种随机性 /** * 调用run(),其实相当于调用了一个普通方法 * 启动线程:start()方法,调用start(),其实是让Jvm去调用线程的run方法() * 来实现执行多线程的一种随机性 */ // 启动线程 // my.start(); // 在启动一个线程 // java.lang.IllegalThreadStateException:非法线程状态异常: // 因为已经启动了一次了,不能使用同一个线程对象将start()调用多次 // my.start(); // 正确写法 创建两个线程 MyThread mt1 = new MyThread(); MyThread mt2 = new MyThread(); // 启动线程 mt1.start(); mt2.start(); } }
多次运行,观察结果
后面为了方便,只创建一个类
3)获取线程名称
public final String getName():返回该线程的名称
public final void setName(String name):改变线程名称,使之与参数 name 相同
有参构造的方式
//有参构造的方式 public MyThread(String name){ super(name) ; }
注意:这两个方法其实是面向对象里得set/get方法,在Thread里已经存在
如果自定义成员变量name,那么写set/get方法时会报错
public class ThreadDemo { public static void main(String[] args) { // 创建两个线程 MyThread mt1 = new MyThread(); MyThread mt2 = new MyThread(); // 设置线程名称 mt1.setName("线程1"); mt2.setName("线程2"); // 启动线程 mt1.start(); mt2.start(); } }
4)获取当前正在执行的线程名称
public static Thread currentThread():返回当前正在执行的线程名称
System.out.println(Thread.currentThread().getName());// main
D.Thread常用方法
1)sleep():休眠(暂停)
public static void sleep(long millis):在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
import java.util.Date; class ThreadSleep extends Thread { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(getName() + ":" + ",执行时间:" + new Date()); try { // 休眠1秒 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class ThreadSleepDemo { public static void main(String[] args) { // 创建三个现成对象 ThreadSleep ts1 = new ThreadSleep(); ThreadSleep ts2 = new ThreadSleep(); ThreadSleep ts3 = new ThreadSleep(); // 给三个线程设置名称 ts1.setName("线程1"); ts2.setName("线程2"); ts3.setName("线程3"); // 启动线程 ts1.start(); ts2.start(); ts3.start(); } }
2)join():等待该线程终止
public final void join():等待该线程终止
class ThreadJoin extends Thread { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(getName() + ":" + i); } } } public class ThreadJoinDemo { public static void main(String[] args) { ThreadJoin tj1 = new ThreadJoin(); ThreadJoin tj2 = new ThreadJoin(); ThreadJoin tj3 = new ThreadJoin(); tj1.setName("线程1"); tj2.setName("线程2"); tj3.setName("线程3"); tj1.start(); try { tj1.join();// 等待tj1线程终止 } catch (InterruptedException e) { e.printStackTrace(); } // 当线程1运行结束,后两个才开始争夺CPU执行权 tj2.start(); tj3.start(); } }
3)Yeild():暂停当前(中断),执行其他
public static void yield():暂停当前正在执行的线程对象,并执行其他线程。
线程的执行具有随机性,并且不能保证该线程永远暂停,可能抢占到了CPU的执行权就会执行它...
class ThreadYeild extends Thread { @Override public void run() { for (int i = 0; i < 10; i++) { // 当执行i == 4 时,线程中断,执行其他线程 if (i == 4) { Thread.yield(); System.out.println(getName() + ":中断"); } System.out.println(getName() + ":" + i); } } } public class ThreadYeildDemo { public static void main(String[] args) { ThreadYeild ty1 = new ThreadYeild(); ThreadYeild ty2 = new ThreadYeild(); ThreadYeild ty3 = new ThreadYeild(); ty1.setName("线程1"); ty2.setName("线程2"); ty3.setName("线程3"); ty1.start(); ty2.start(); ty3.start(); } }
4)stop()和interrupt()
public final void stop():强制停止
public void interrupt():中断线程,中断当前线程的这种状态(打破了一种状态)
import java.util.Date; class ThreadStop extends Thread { @Override public void run() { System.out.println("开始执行:" + new Date()); // 睡眠10s try { Thread.sleep(10000); } catch (InterruptedException e) { System.out.println("线程被强行中止..."); } System.out.println("结束执行:" + new Date()); } } public class ThreadStopDemo { public static void main(String[] args) { ThreadStop ts = new ThreadStop(); ts.start(); // 休眠3s,不执行就终止 try { Thread.sleep(3000); // ts.stop();// 过时了,但可以用 ts.interrupt(); } catch (InterruptedException e) { e.printStackTrace(); } } }
5)setDemon():守护线程
public final void setDaemon(boolean on):将该线程标记为守护线程或用户线程
当正在运行的线程都是守护线程时,Java 虚拟机退出
该方法必须在启动线程前调用!
on:设置为true,则表示该线程是守护线程!
class ThreadDemon extends Thread { @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println(getName() + ":" + i); } } } public class ThreadDemonDemo { public static void main(String[] args) { ThreadDemon td1 = new ThreadDemon(); ThreadDemon td2 = new ThreadDemon(); ThreadDemon td3 = new ThreadDemon(); td1.setName("主 公"); td2.setName("忠臣1"); td3.setName("忠臣2"); td2.setDaemon(true); td3.setDaemon(true); td1.start(); try { // 主公执行完,只剩守护线程,不会立即结束,但再执行几次,程序终止 td1.join(); } catch (InterruptedException e) { e.printStackTrace(); } td2.start(); td3.start(); } }
E.实现多线程—Runnable(开发常用)
1)步骤
a.创建声明Runnable接口的类
b.实现Rannable里面的run方法
// 自定义这个类实现Runnable接口,实现run()方法 public class MyRunnable implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) { // getName()只有在Thread中有 System.out.println(Thread.currentThread().getName() + ":" + i); } } }
c.主线程中创建自定义类对象
d.创建Thread类对象,将第三步的对象作为参数进行传递来启动线程
public class ThreadDemo { public static void main(String[] args) { MyRunnable mr = new MyRunnable(); // Thread t1 = new Thread(mr); // Thread t2 = new Thread(mr); // 第二种方法 Thread t1 = new Thread(mr, "线程1"); Thread t2 = new Thread(mr, "线程2"); t1.start(); t2.start(); } }
2)两种方法的区别
3)生命周期
F.synchornized同步机制
1)需求
某电影院目前正在上映贺岁大片(战狼2),共有100张票,而它有3个售票窗口售票,多线程实现
这不是最终版,最终版在后面
自己尝试,会出现什么问题?自己尝试public class SellTicket implements Runnable { //定义门票的变量 private int tickets = 100 ; @Override public void run() { //为了模拟电影院一一直有票 while(true){ if(tickets>0){ System.out.println(Thread.currentThread().getName()+"正在出售第:"+(tickets--) +"张门票"); } } } }
比如:两个窗口卖的重复;总门票数超过100张(余票出现负数)
后面讲解怎么解决
2)线程安全的检测条件
通过刚才电影院卖票案例,线程不安全(线程安全的检测条件):
1)看我们当前的环境是否属于多线程程序:是
2)当前的多线程程序中是否有共享数据:是
3)是否多条语句对共享数据进行操作:是 tickets
如何让线程安全?
要实现线程安全,那么就必须改变第三个条件(前两者不能改变)
将对共享数据的操作的多条语句包装起来
java提供了一个同步机制:sychronized
3)同步机制的使用
sychronized(对象){
多条语句对共享数据进行操作;
}
对象是谁啊?不是随意的在sychronized里面进行创建对象
同步机制:每一个线程只能使用同一个对象(sychronized当作一个锁:使用的是同一把锁)
基本完成:最终版在下一条
测试类public class MovieTickets implements Runnable { // 定义门票变量,不让外界看到 private static int tickets = 100; // 在成员变量中创建对象 Object obj = new Object(); @Override public void run() { // 建立循环,票卖完终止 while (tickets > 0) { // 同步代码块(不会出现两个窗口卖出同一张票) // 门(开和关) // t1线程抢占CPU的执行权,执行t1线程 // t2线程抢占的CPU的执行权,执行t2线程,它要进去,发现门管着,所以等待着 // 先创建对象 synchronized (obj) {// t1线程进来,把门关了(单词:考点!) if (tickets > 0) {// 这行条件必须有(保证余票不会出现负数) System.out.println(Thread.currentThread().getName() + "正在出售电影《战狼Ⅱ》第" + (100 - (--tickets)) + "张票,余票" + tickets + "张!"); try { // 每次购票,不会瞬间同步,要延时100ms(出现线程重复的) Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } } }
public class TicketsDemo { public static void main(String[] args) { MovieTickets mt = new MovieTickets(); Thread t1 = new Thread(mt, "窗口1"); Thread t2 = new Thread(mt, "窗口2"); Thread t3 = new Thread(mt, "窗口3"); t1.start(); t2.start(); t3.start(); } }
继承Thread方法自己测试
同步代码块锁对象可以是什么样的对象?
//在成员变量中创建对象 private Object obj = new Object() ; //创建Demo类对象不,并且外界不能访问 private Demo d = new Demo() ;
5)同步方法可以是任意的对象(重点)
如果一个方法进来之后是同步代码块,那么该方法就可以定义为同步方法,他的锁对象是this
静态的同步方法的锁对象是:类名.class(java中的反射机制)
最终版代码(测试类同上一条)
public class MovieTickets implements Runnable { // 定义门票变量 private static int tickets = 100; @Override public void run() { while (tickets > 0) { // 封装成方法 getTickets(); } } // private void getTickets() { // //该方法一进来就是一个同步代码块,一个方法可以是同步方法吗? // synchronized (d){//t1线程进来,把门关了(单词:考点!) // if(tickets>0){ // try { // Thread.sleep(100);//t1线程睡了100毫秒 // } catch (InterruptedException e) { // e.printStackTrace(); // } // System.out.println(Thread.currentThread().getName()+"正在出售第:"+ (tickets--)); // } // } // } // 设置同步方法 private static synchronized void getTickets() { if (tickets > 0) { System.out.println(Thread.currentThread().getName() + "正在出售电影《战狼Ⅱ》第" + (100 - (--tickets)) + "张票,余票" + tickets + "张!"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }
F.线程安全
1)和线程安全相关的类有哪些呢?
StringBuffer
Vector<T>:一般不用,使用:public synchronizedList(List<T> list)
Hashtable<K,V>
import java.util.ArrayList; import java.util.Collections; import java.util.Hashtable; import java.util.List; import java.util.Vector; public class Demo { public static void main(String[] args) { StringBuffer sb = new StringBuffer(); Vector<String> v = new Vector<String>(); Hashtable<String, String> hs = new Hashtable<String, String>(); List<String> list = new ArrayList<String>(); List<String> list2 = Collections.synchronizedList(list); } }