一、线程基本概述
1.1、进程和线程
进程:一个应用程序一般都是一个进程,正在进行的程序
每一个进程最少都有一个线程,都有一个执行顺序,该顺序是一个执行路径或者一个控制单元
线程:进程中一个独立的控制单元,线程控制着进程的执行。
windows中的任务管理器,可以查看进程,linux下通过ps命令
线程是进程的最小单位
线程依赖于进程
线程随着进程的创建和创建,随着进程的结束而消亡
如迅雷:可以同时开启多个下载,就是多线程
多个程序同时执行,时CPU在很快的切换,看上去是同时执行,实际上是在CPU在切换执行。
多线程存在的意义:可以让程序中的内容同时执行。
二、继承线程的创建
2.1、继承Thread,重写run方法
package com.pb.thread.demo1; /** * * @author Denny * 继承Thread 类并重写run方法来创建线程类 * */ public class MyThread extends Thread { private int count; /* * 重写run 方法 * @see java.lang.Thread#run() */ @Override public void run(){ /* * 要运行的代码块或者方法,在这里调用,或者直接写在这里 */ while(count<10){ count++; System.out.println("My thread run方法启动了"+count); } } public static void main(String[] args) { //声明线程类 MyThread mt=new MyThread(); //启动线程,JVM调用run方法 mt.start(); //主线程 for(int x=0;x<10;x++){ System.out.println("main thread"+x); } } }
多个线程都在抢占CPU的执行权,谁抢到,谁就执行
在某一时刻,只能有一个程序在运行
CPU在做着快速切换,以达到看上去是同时运行的效果。
多线程一个特性:随机性,谁抢到谁执行.
三、线程的运行和线程状态
3.1、调用start()方法
使该线程开始执行,Java虚拟机调用该线程的run()方法
为什么要重写run方法.
Thread类用于描述线程。
这个类定义一个功能,用于存储线程要运行的代码。该存储功能就是run方法.
也就是说,Thread类中的run方法是用于存储线程要运行的代码
如果直接调用run()方法,就是调用对象的普通方法一样.,仅仅是对象调用方法.线程创建了并没有运行。
只有start()才可以运行线程。
3.2、线程状态
新生:创建线程 new Thread()或者子类
运行:正在运行的线程,调用start()方法
冻结:已经创建的线程,但非运行状态的线程。sleep(long 毫秒), wait()线程等待,直到notify()唤醒才可以继续运行
临时状态阻塞:具备运行资格,但没有执行权,就是还没有被CPU切换到.
消亡:线程死亡stop(),或者线程运行完成。
四、获取线程对象
4.1、获取当前线程对象和名称
继承thread类实现线程,局部变量都有单独的一份不能共享数据
package com.pb.thread.demo1; /** * * @author Denny * 继承Thread 类并重写run方法来创建线程类 *Thread.currentThread(),获取当前线程对象 *继承thread类实现线程,局部变量都有单独的一份不能共享数据 */ public class MyThread extends Thread { public MyThread(String name){ super(name); } /* * 重写run 方法 * @see java.lang.Thread#run() */ @Override public void run(){ /* * 要运行的代码块或者方法,在这里调用,或者直接写在这里 */ for(int x=0;x<10;x++){ System.out.println("My thread run的名字:"+Thread.currentThread().getName()+","+x); } } public static void main(String[] args) { //声明线程类 MyThread mt1=new MyThread("mt1"); // mt1.setName("张三"); 设置线程名称 MyThread mt2=new MyThread("mt2"); //启动线程,JVM调用run方法 mt1.start(); mt2.start(); //主线程 for(int x=0;x<10;x++){ System.out.println(currentThread().getName()+","+x); } } }
示例:
五、接口实现线程
5.1、实现Runnable接口
重写run方法()
实现接口的方式最大的好处是可以共享数据
示例:多窗口同时售票,
package com.pb.thread.demo1; /** * * @author Administrator 多个窗口同时卖票 * */ public class Ticket implements Runnable { private int tick = 100; public void run() { while (true) { if (tick > 0) { System.out.println(Thread.currentThread().getName()+"卖:" + tick--); }else{ break; } } } public static void main(String[] args) { //声明线程类 Ticket ticket=new Ticket(); //创建线程对象,并将类做为参数 Thread t1=new Thread(ticket); t1.setName("一号窗口,"); Thread t2=new Thread(ticket); t2.setName("二号窗口,"); Thread t3=new Thread(ticket); t3.setName("三号窗口,"); Thread t4=new Thread(ticket); t4.setName("四号窗口,"); t1.start(); t2.start(); t3.start(); t4.start(); } }
结果:
1 一号窗口,卖:100 2 一号窗口,卖:98 3 一号窗口,卖:97 4 一号窗口,卖:96 5 一号窗口,卖:95 6 一号窗口,卖:94 7 二号窗口,卖:99 8 二号窗口,卖:92 9 一号窗口,卖:93 10 一号窗口,卖:88 11 一号窗口,卖:87 12 一号窗口,卖:86 13 二号窗口,卖:89 14 二号窗口,卖:84 15 二号窗口,卖:83 16 二号窗口,卖:82 17 二号窗口,卖:81 18 二号窗口,卖:80 19 二号窗口,卖:79 20 二号窗口,卖:78 21 二号窗口,卖:77 22 二号窗口,卖:76 23 二号窗口,卖:75 24 二号窗口,卖:74 25 二号窗口,卖:73 26 二号窗口,卖:72 27 二号窗口,卖:71 28 二号窗口,卖:70 29 二号窗口,卖:69 30 二号窗口,卖:68 31 二号窗口,卖:67 32 二号窗口,卖:66 33 二号窗口,卖:65 34 二号窗口,卖:64 35 二号窗口,卖:63 36 二号窗口,卖:62 37 二号窗口,卖:61 38 二号窗口,卖:60 39 二号窗口,卖:59 40 二号窗口,卖:58 41 二号窗口,卖:57 42 二号窗口,卖:56 43 二号窗口,卖:55 44 二号窗口,卖:54 45 二号窗口,卖:53 46 二号窗口,卖:52 47 二号窗口,卖:51 48 二号窗口,卖:50 49 二号窗口,卖:49 50 二号窗口,卖:48 51 二号窗口,卖:47 52 二号窗口,卖:46 53 二号窗口,卖:45 54 二号窗口,卖:44 55 二号窗口,卖:43 56 二号窗口,卖:42 57 二号窗口,卖:41 58 二号窗口,卖:40 59 二号窗口,卖:39 60 二号窗口,卖:38 61 二号窗口,卖:37 62 二号窗口,卖:36 63 二号窗口,卖:35 64 二号窗口,卖:34 65 二号窗口,卖:33 66 二号窗口,卖:32 67 二号窗口,卖:31 68 二号窗口,卖:30 69 二号窗口,卖:29 70 二号窗口,卖:28 71 二号窗口,卖:27 72 二号窗口,卖:26 73 二号窗口,卖:25 74 二号窗口,卖:24 75 二号窗口,卖:23 76 二号窗口,卖:22 77 二号窗口,卖:21 78 三号窗口,卖:90 79 四号窗口,卖:91 80 三号窗口,卖:19 81 二号窗口,卖:20 82 一号窗口,卖:85 83 二号窗口,卖:16 84 三号窗口,卖:17 85 四号窗口,卖:18 86 三号窗口,卖:13 87 三号窗口,卖:11 88 三号窗口,卖:10 89 三号窗口,卖:9 90 三号窗口,卖:8 91 三号窗口,卖:7 92 三号窗口,卖:6 93 三号窗口,卖:5 94 三号窗口,卖:4 95 三号窗口,卖:3 96 三号窗口,卖:2 97 三号窗口,卖:1 98 二号窗口,卖:14 99 一号窗口,卖:15 100 四号窗口,卖:12
两种创建线程方式:
优点 | 缺点 | |
继承Thread类 | 1.编写简单 2.可以使用this关键字直接访问当前线程 | 无法继承其它类 无法实现数据共享 |
实现Runnable接口 | 1.可以继承其它类 2.多个线程之间可以使用同一个Runnable对象 3.可以共享数据 | 编程方式稍微复杂,如需访问当前线程,需要调用Thread类的currentThread()方法 |
六、线程安全
6.1、还是上面的例子
加上sleep(1000)睡觉1秒
package com.day10.thread.demo1; public class Ticket implements Runnable { private int num=100; @Override public void run() { while(true){ if(num>0){ try { Thread.sleep(1000); //当前线程睡觉1秒,毫秒单位 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+",卖出"+num--+"号票"); } } } public static void main(String[] args) { Ticket t=new Ticket(); Thread t1=new Thread(t); Thread t2=new Thread(t); Thread t3=new Thread(t); Thread t4=new Thread(t); t1.start(); t2.start(); t3.start(); t4.start(); } }
结果:
Picked up JAVA_TOOL_OPTIONS: -javaagent:/usr/share/java/jayatanaag.jar Thread-0,卖出100号票 Thread-2,卖出99号票 Thread-1,卖出98号票 Thread-3,卖出97号票 Thread-0,卖出96号票 Thread-2,卖出95号票 Thread-1,卖出94号票 Thread-3,卖出93号票 Thread-0,卖出92号票 Thread-2,卖出91号票 Thread-1,卖出90号票 Thread-3,卖出89号票 Thread-0,卖出88号票 Thread-2,卖出87号票 Thread-1,卖出86号票 Thread-3,卖出85号票 Thread-0,卖出84号票 Thread-2,卖出83号票 Thread-1,卖出82号票 Thread-3,卖出81号票 Thread-0,卖出80号票 Thread-2,卖出79号票 Thread-1,卖出78号票 Thread-3,卖出77号票 Thread-0,卖出76号票 Thread-2,卖出75号票 Thread-1,卖出74号票 Thread-3,卖出73号票 Thread-0,卖出72号票 Thread-2,卖出71号票 Thread-1,卖出70号票 Thread-3,卖出69号票 Thread-0,卖出68号票 Thread-2,卖出67号票 Thread-1,卖出66号票 Thread-3,卖出65号票 Thread-0,卖出64号票 Thread-2,卖出63号票 Thread-1,卖出62号票 Thread-3,卖出61号票 Thread-0,卖出60号票 Thread-2,卖出59号票 Thread-1,卖出58号票 Thread-3,卖出57号票 Thread-0,卖出56号票 Thread-2,卖出55号票 Thread-1,卖出54号票 Thread-3,卖出53号票 Thread-0,卖出52号票 Thread-2,卖出51号票 Thread-1,卖出50号票 Thread-3,卖出49号票 Thread-0,卖出48号票 Thread-2,卖出47号票 Thread-1,卖出46号票 Thread-3,卖出45号票 Thread-0,卖出44号票 Thread-2,卖出43号票 Thread-1,卖出42号票 Thread-3,卖出41号票 Thread-0,卖出40号票 Thread-2,卖出39号票 Thread-1,卖出38号票 Thread-3,卖出37号票 Thread-0,卖出36号票 Thread-2,卖出35号票 Thread-1,卖出34号票 Thread-3,卖出33号票 Thread-0,卖出32号票 Thread-2,卖出31号票 Thread-1,卖出30号票 Thread-3,卖出29号票 Thread-0,卖出28号票 Thread-2,卖出27号票 Thread-1,卖出26号票 Thread-3,卖出25号票 Thread-0,卖出24号票 Thread-2,卖出23号票 Thread-1,卖出22号票 Thread-3,卖出21号票 Thread-0,卖出20号票 Thread-2,卖出19号票 Thread-1,卖出18号票 Thread-3,卖出17号票 Thread-0,卖出16号票 Thread-2,卖出15号票 Thread-1,卖出14号票 Thread-3,卖出13号票 Thread-0,卖出12号票 Thread-2,卖出11号票 Thread-1,卖出10号票 Thread-3,卖出9号票 Thread-0,卖出8号票 Thread-2,卖出7号票 Thread-1,卖出6号票 Thread-3,卖出5号票 Thread-0,卖出4号票 Thread-2,卖出3号票 Thread-1,卖出2号票 Thread-3,卖出1号票 Thread-0,卖出0号票 Thread-2,卖出-1号票 Thread-1,卖出-2号票
发现多卖票了,
为什么?因为卖票当前线程睡眠了1秒,其它线程就可以把这张票已经卖出了,
条件检查1>0,如果4个人同时检查,条件成立,就继续卖票就出现了,负数
怎么解决?给线程加锁
1. 多个线程在操作共享的数据。
2. 操作共享数据的线程代码有多条。
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。
解决方式:就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程不可以参与运算。必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。
看下面的同步synchronized
七、同步代码块
7.1、synchronized
同步代码块的格式:
synchronized(对象){
需要被同步的代码;
}
同步的好处:解决了线程的安全问题。
同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
同步的前提:必须有多个线程并使用同一个锁。
修改上面的代码
package com.day10.thread.demo1; public class Ticket implements Runnable { private int num=100; @Override public void run() { while(true){ synchronized(this){ if(num>0){ try { Thread.sleep(1000); //当前线程睡觉1秒,毫秒单位 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+",卖出"+num--+"号票"); }else{ break; } } } } public static void main(String[] args) { Ticket t=new Ticket(); Thread t1=new Thread(t); Thread t2=new Thread(t); Thread t3=new Thread(t); Thread t4=new Thread(t); t1.start(); t2.start(); t3.start(); t4.start(); } }
八、同步函数
8.1、同步方法
package com.day10.thread.demo2; public class Account { //余额 private double blance=500; //取款 public void withDraw(int num){ blance=blance-num; } public double getBlance() { return blance; } public void setBlance(double blance) { this.blance = blance; } } package com.day10.thread.demo2; public class AccountThread implements Runnable { private Account account=new Account(); @Override public void run() { for (int i = 0; i < 5; i++) { if(account.getBlance()<0){ System.out.println("透支了!!"); } makWithDraw(100); //每次取100 } } /* * 取款的方法 */ public void makWithDraw(int num){ //判断余额是不是大于要取款的数 if(account.getBlance()>=num){ System.out.println(Thread.currentThread().getName()+":准备取款!"); try { Thread.sleep(1000); //等待1秒 account.withDraw(num); System.out.println(Thread.currentThread().getName()+":取款完成!,当前余额为:"+account.getBlance()); } catch (InterruptedException e) { e.printStackTrace(); } }else{ System.out.println(Thread.currentThread().getName()+" 余额不足以支付当前取款, 余额为: "+account.getBlance()); } } } package com.day10.thread.demo2; public class Demo { public static void main(String[] args) { AccountThread at=new AccountThread(); Thread t1=new Thread(at); t1.setName("张三"); Thread t2=new Thread(at); t2.setName("张三老婆"); t1.start(); t2.start(); } }
结果:
张三:准备取款! 张三老婆:准备取款! 张三:取款完成!,当前余额为:400.0 张三:准备取款! 张三老婆:取款完成!,当前余额为:300.0 张三老婆:准备取款! 张三:取款完成!,当前余额为:200.0 张三:准备取款! 张三老婆:取款完成!,当前余额为:100.0 张三老婆:准备取款! 张三:取款完成!,当前余额为:0.0 张三 余额不足以支付当前取款, 余额为: 0.0 张三 余额不足以支付当前取款, 余额为: 0.0 张三老婆:取款完成!,当前余额为:-100.0 透支了!! 张三老婆 余额不足以支付当前取款, 余额为: -100.0 透支了!! 张三老婆 余额不足以支付当前取款, 余额为: -100.0
加上同步关键字
/* * 取款的方法 */ public synchronized void makWithDraw(int num){ //判断余额是不是大于要取款的数 if(account.getBlance()>=num){ System.out.println(Thread.currentThread().getName()+":准备取款!"); try { Thread.sleep(1000); //等待1秒 account.withDraw(num); System.out.println(Thread.currentThread().getName()+":取款完成!,当前余额为:"+account.getBlance()); } catch (InterruptedException e) { e.printStackTrace(); } }else{ System.out.println(Thread.currentThread().getName()+" 余额不足以支付当前取款, 余额为: "+account.getBlance()); } }
结果:
张三:准备取款! 张三:取款完成!,当前余额为:400.0 张三老婆:准备取款! 张三老婆:取款完成!,当前余额为:300.0 张三:准备取款! 张三:取款完成!,当前余额为:200.0 张三:准备取款! 张三:取款完成!,当前余额为:100.0 张三:准备取款! 张三:取款完成!,当前余额为:0.0 张三 余额不足以支付当前取款, 余额为: 0.0 张三老婆 余额不足以支付当前取款, 余额为: 0.0 张三老婆 余额不足以支付当前取款, 余额为: 0.0 张三老婆 余额不足以支付当前取款, 余额为: 0.0 张三老婆 余额不足以支付当前取款, 余额为: 0.0
没有发生透支现象
同步代码块:
/* * 取款的方法 */ public void makWithDraw(int num){ synchronized(account){ //判断余额是不是大于要取款的数 if(account.getBlance()>=num){ System.out.println(Thread.currentThread().getName()+":准备取款!"); try { Thread.sleep(1000); //等待1秒 account.withDraw(num); System.out.println(Thread.currentThread().getName()+":取款完成!,当前余额为:"+account.getBlance()); } catch (InterruptedException e) { e.printStackTrace(); } }else{ System.out.println(Thread.currentThread().getName()+" 余额不足以支付当前取款, 余额为: "+account.getBlance()); } } }
结果同上,没有发生透支现象
1. 同步函数的锁是固定的this。
2. 同步代码块的锁是任意的对象。
建议使用同步代码块。
九、同步锁
9.1、同锁
同步方法同步对象是this,同步代码块也可以使用this来同步
十、静态同步
10.1、静态同步
不是this,因为静态方法中也不可以定义this
静态进内存,内存中没有本类对象,但是一定有该类对应的字节码文件对象
静态的同步函数使用的锁是该函数所属字节码文件对象,可以用getClass方法获取,也可以用当前类名.class表示。
synchronizid(类名.class){ 或者对象.getClass
}
十一、单例模式加上同步懒汉式
11.1、懒汉式加锁
package com.day10.thread.demo2; /** * * @author denny *单例模式 *饿汉式,不存在复生成对象的问题 *懒汉工,加synchronize */ public class SingleDemo { private static SingleDemo singleDemo; private SingleDemo(){ } public static SingleDemo getNewIntance(){ if(singleDemo==null){//判断是不是空,不是空就直接返回,是空就加锁 synchronized(SingleDemo.class){ //加锁 //this.getClass() 直接写对象singleDemo.getClass()也行 if(singleDemo==null){ singleDemo=new SingleDemo(); } } } return singleDemo; } }
十二、死锁
12.1、死锁常见情景之一:同步的嵌套。
class MyLock{ static Object lockA=new Object(); static Object lockB=new Object(); } public class Demo implements Runnable { private boolean flag; public Demo(boolean flag) { this.flag = flag; } @Override public void run() { if (flag) { while (true) { synchronized (MyLock.lockA) { System.out.println("if.........lockA"); synchronized (MyLock.lockB) { System.out.println("if.........lockB"); } } } } else { while(true){ synchronized (MyLock.lockB) { System.out.println("else.........lockB"); synchronized (MyLock.lockA) { System.out.println("else.........lockA"); } } } } } public static void main(String[] args) { Thread t1 = new Thread(new Demo(true)); Thread t2 = new Thread(new Demo(false)); t1.start(); t2.start(); } }
建议将要锁的对象,声明为static