一、线程相关概念:
1、程序(program):
是为完成特定任务、用某种语言编写的一组指令的集合,即我们写的代码。
2、进程:
(1)进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。
(2)进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程.
3、什么是线程:
(1)线程由进程创建的,是进程的一个实体
(2)一个进程可以拥有多个线程
4、单线程:
同一个时刻,只允许执行一个线程。
5、多线程:
同一个时刻,可以执行多个线程,比如:一个qq进程,可以同时打开多个聊天窗口,一个迅雷进程,可以同时下载多个文件。
6、并发:
同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简单地说,单核cpu实现的多任务就是并发
7、并行:(有同伴一起走)
同一个时刻,多个任务同时执行,多核cpu可以实现并行
·查看电脑CPU的方式:
方式一:右击屏幕底下的长条形任务栏,打开“任务管理器”,“打开资源管理器”

方式二:右击“我的电脑”,选择“管理”,“设备管理器”,“处理器”
方式三:用java代码查看
- package event_;
-
- public class CpuNum {
- public static void main(String[] args) {
- Runtime runtime = Runtime.getRuntime();
- //获取当前电脑的cpu数量/核心数
- int cpuNums = runtime.availableProcessors();
- System.out.println("当前有cpu个数:" + cpuNums);
- }
- }
- //当前有cpu个数:8
我的是4核8线程
二、线程基本使用
1、创建线程的两种方式
(1)继承Thread类,重写run方法

- package threaduse;
-
- public class Thread01 {
- public static void main(String[] args) throws Exception{
- //创建Cat对象,可以当做线程使用
- Cat cat=new Cat();
- cat.start();//启动线程
-
- //当main线程启动一个子线程Thread-0,主线程不会阻塞,会继续执行
- //这时,主线程和子线程是交替执行
- System.out.println("主线程继续执行"+Thread.currentThread().getName());
- for(int i=0;i<10;i++){
- System.out.println("主线程 i="+i);
- //让主线程休眠
- Thread.sleep(1000);
- }
- }
- }
-
- //1、当一个类继承了Thread,该类就可以当做线程使用
- //2、要重写run方法,写上自己的业务逻辑
- //3、run方法是Thread类实现了Runnable接口用的
- /* @Override
- public void run() {
- Runnable task = holder.task;
- if (task != null) {
- task.run();
- }
- }
- */
- class Cat extends Thread{
- int times=0;
- public void run() {
- //如果不加循环,代码执行一次就退出了
- while(true){
- //该线程每隔1秒,在控制台输出"喵喵,我是小猫咪"
- System.out.println("喵喵,我是小猫咪"+(++times)+" 线程名:"+Thread.currentThread().getName());
- //让线程休眠1秒
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- if(times==10){
- break;
- }
- }
- }
- }
- //主线程继续执行main
- //主线程 i=0
- //喵喵,我是小猫咪1 线程名:Thread-0
- //喵喵,我是小猫咪2 线程名:Thread-0
- //主线程 i=1
- //喵喵,我是小猫咪3 线程名:Thread-0
- //主线程 i=2
- //喵喵,我是小猫咪4 线程名:Thread-0
- //主线程 i=3
- //喵喵,我是小猫咪5 线程名:Thread-0
- //主线程 i=4
- //喵喵,我是小猫咪6 线程名:Thread-0
- //主线程 i=5
- //主线程 i=6
- //喵喵,我是小猫咪7 线程名:Thread-0
- //主线程 i=7
- //喵喵,我是小猫咪8 线程名:Thread-0
- //主线程 i=8
- //喵喵,我是小猫咪9 线程名:Thread-0
- //主线程 i=9
- //喵喵,我是小猫咪10 线程名:Thread-0
- 深入解析cat.start();方法
- //源码:
- /*1、
- void start(ThreadContainer container) {
- synchronized (this) {
- start0();
- }
- }
- 2、
- //start0()是酵方法,是JVM调用,底层是c/c++实现
- //真正实现多线程的效果,是start0(),而不是run()
- private native void start0();
- */
- cat.start();//启动线程--->最终会执行cat的run方法
-
- cat.run();//如果直接调用run()方法,这里的run()方法就是一个普通的方法,没有真正地启动一个线程
- //等执行完这个run()方法后,才继续往下走,此时已经不再是多线程了
- //在main里调用run()方法,输出的就是main的线程名,不是要启动的线程名
- //输出:喵喵,我是小猫咪1 线程名:main
补:主线程决定进程开启,最后结束的线程决定进程结束
(2)实现Runinable接口,重写run方法


- package threaduse;
-
- public class Thread02 {
- public static void main(String[] args) {
- Dog dog=new Dog();
- //dog.start();报错,这里不能调用start
- //创建了Thread对象,把dog对象(实现Runnable),放入Thread
- Thread thread = new Thread(dog);
- thread.start();
-
- Tiger tiger = new Tiger();
- ThreadProxy threadProxy = new ThreadProxy(tiger);
- threadProxy.start();
- }
- }
- class Animal{}
- class Tiger extends Animal implements Runnable{
-
- @Override
- public void run() {
- System.out.println("老虎嗷嗷叫...");
- }
- }
-
- //线程代理类,模拟了一个极简的Thread类
- class ThreadProxy implements Runnable{//
- private Runnable target=null;//类型是Runnable
- @Override
- public void run() {
- if (target != null) {
- target.run();//动态绑定,运行类型是传进来的参数的类型
- }
- }
-
- public ThreadProxy(Runnable target) {
- this.target = target;
- }
- public void start(){
- start0();//这个方法才是真正实现多线程的方法
- }
- public void start0(){
- run();
- }
- }
- class Dog implements Runnable{
- int count=0;
- @Override
- public void run() {
- while(true){
- System.out.println("小狗汪汪叫...hi"+(++count)+Thread.currentThread().getName());
- try {
- Thread.sleep(1000);//休眠一秒
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- if (count == 10) {
- break;
- }
- }
- }
- }
线程这样设计的意义,最根本的意义就在于解耦,权责分明:把创建线程交给Thread类,创建线程任务交给实现了Runnable接口的任意类
这样做的好处:
(1)解除类的单继承限制,更好地实现多线程;
(2)解耦之前,线程任务改动需要修改run()方法,解耦后,只需要修改外部类,实现非侵入式的修改,提高代码的可扩展性,这是接口的主要好处
(3)线程任务执行完后,不需要再次创建、销毁线程,只需要实现Runnable接口提交新的的线程任务即可,提高性能节省开销,后面的线程池设计主要体现这一点
2、应用线程

- package threaduse;
-
- import javax.swing.plaf.TableHeaderUI;
- @SuppressWarnings({"all"})
- public class Thread03 {
- public static void main(String[] args) {
- T1 t1 = new T1();
- T2 t2 = new T2();
- Thread thread1=new Thread(t1);
- Thread thread2= new Thread(t2);
- thread1.start();
- thread2.start();
- }
- }
- class T1 implements Runnable{
- int count=0;
- @Override
- public void run() {
- while (true) {
- System.out.println("hello, world! "+(++count)+Thread.currentThread().getName());
-
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- if (count == 10) {
- break;
- }
- }
- }
- }
- class T2 implements Runnable{
- int count=0;
- @Override
- public void run() {
- while (true) {
- System.out.println("hi "+(++count)+Thread.currentThread().getName());
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- if (count == 5) {
- break;
- }
- }
- }
- }
- //hello, world! 1Thread-0
- //hi 1Thread-1
- //hello, world! 2Thread-0
- //hi 2Thread-1
- //hi 3Thread-1
- //hello, world! 3Thread-0
- //hello, world! 4Thread-0
- //hi 4Thread-1
- //hello, world! 5Thread-0
- //hi 5Thread-1
- //hello, world! 6Thread-0
- //hello, world! 7Thread-0
- //hello, world! 8Thread-0
- //hello, world! 9Thread-0
- //hello, world! 10Thread-0

3、继承Thread和实现Runnable的区别:
(1)从java的设计来看,通过继承Thread或实现Runnable接口来创建线程本质上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口,都是通过调用start()----->最终实现调用start0();
(2)实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制,建议使用
- T3 t3 = new T3("Hello");//一个对象
- Thread thread01=new Thread(t3);
- Thread thread02=new Thread(t3);
- thread01.start();
- thread02.start();
- System.out.println("主线程完毕");
4、
- //使用Thread
- package ticket;
-
- public class SellTicket {
- public static void main(String[] args) {
- SellTicket01 sellTicket1 = new SellTicket01();
- SellTicket01 sellTicket2 = new SellTicket01();
- SellTicket01 sellTicket3 = new SellTicket01();
-
- //三个售票窗口
- sellTicket1.start();
- sellTicket2.start();
- sellTicket3.start();
- }
- }
- class SellTicket01 extends Thread{
- private static int ticketNum=10;//让多个线程共享ticketNum
- public void run(){
- while(true){
- if (ticketNum <= 0) {
- System.out.println("售票结束");
- break;
- }
- try {
- Thread.sleep(50);
- } catch (Exception e) {
- e.printStackTrace();
- }
- System.out.println("窗口 "+Thread.currentThread().getName()+" 售出一张票"
- +" 剩余票数="+(--ticketNum));
- }
- }
- }
- //窗口 Thread-1 售出一张票 剩余票数=9
- //窗口 Thread-0 售出一张票 剩余票数=7
- //窗口 Thread-2 售出一张票 剩余票数=8
- //窗口 Thread-2 售出一张票 剩余票数=6
- //窗口 Thread-0 售出一张票 剩余票数=4
- //窗口 Thread-1 售出一张票 剩余票数=5
- //窗口 Thread-2 售出一张票 剩余票数=3
- //窗口 Thread-1 售出一张票 剩余票数=2
- //窗口 Thread-0 售出一张票 剩余票数=1
- //窗口 Thread-1 售出一张票 剩余票数=0
- //窗口 Thread-2 售出一张票 剩余票数=0
- //售票结束
- //窗口 Thread-0 售出一张票 剩余票数=-1
- //售票结束
- //售票结束
-
- //出现了超卖现象,因为最后的时候,三个线程同时进入到while循环,相差时间太短
- //使用Runnable
- package ticket;
-
- public class SellTicket {
- public static void main(String[] args) {
- SellTicket02 sellTicket = new SellTicket02();
- //三个售票窗口
- new Thread(sellTicket).start();
- new Thread(sellTicket).start();
- new Thread(sellTicket).start();
- }
- }
- //使用Thread
- class SellTicket02 implements Runnable{
- private static int ticketNum=10;//让多个线程共享ticketNum
- public void run(){
- while(true){
- if (ticketNum <= 0) {
- System.out.println("售票结束");
- break;
- }
- try {
- Thread.sleep(5000);
- } catch (Exception e) {
- e.printStackTrace();
- }
- System.out.println("窗口 "+Thread.currentThread().getName()+" 售出一张票"
- +" 剩余票数="+(--ticketNum));
- }
- }
- }
- //窗口 Thread-1 售出一张票 剩余票数=9
- //窗口 Thread-0 售出一张票 剩余票数=8
- //窗口 Thread-2 售出一张票 剩余票数=8
- //窗口 Thread-2 售出一张票 剩余票数=7
- //窗口 Thread-1 售出一张票 剩余票数=7
- //窗口 Thread-0 售出一张票 剩余票数=6
- //窗口 Thread-0 售出一张票 剩余票数=4
- //窗口 Thread-1 售出一张票 剩余票数=5
- //窗口 Thread-2 售出一张票 剩余票数=3
- //窗口 Thread-1 售出一张票 剩余票数=2
- //窗口 Thread-2 售出一张票 剩余票数=2
- //窗口 Thread-0 售出一张票 剩余票数=1
- //窗口 Thread-2 售出一张票 剩余票数=0
- //售票结束
- //窗口 Thread-1 售出一张票 剩余票数=-1
- //售票结束
- //窗口 Thread-0 售出一张票 剩余票数=0
- //售票结束
-
- //出现了超卖现象,因为最后的时候,三个线程同时进入到while循环,相差时间太短
出现了问题:票数超卖,多个线程同时进入循环,都检测到了最后的一个资源,所以都卖了出去,出现了超卖现象。
5、线程终止:
(1)基本说明:
1)当线程完成任务后,会自动退出
2)还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式
(2)应用案例:![]()
6、线程常用方法:


补:中断可以类比闹钟来理解

- //解释join:线程插队
- package threaduse;
-
- public class ThreadMethod02 {
- public static void main(String[] args) throws InterruptedException {
- T3 t3 = new T3();
- t3.start();
-
- for(int i=1;i<=20;i++){
- Thread.sleep(1000);
- System.out.println("主线程(小弟)吃了 "+i+" 包子");
- if(i==5){
- System.out.println("主线程(小弟)让了子线程(老大)先吃");
- t3.join();//这里相当于让t2线程先执行完毕
- System.out.println("子线程(老大)吃完了,主线程(小弟)接着吃...");
- }
- }
- }
- }
- class T3 extends Thread{
- public void run(){
- for(int i=1;i<=20;i++){
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("子线程(老大)吃了 " +i+" 包子");
- }
- }
- }
- //主线程(小弟)吃了 1 包子
- //子线程(老大)吃了 1 包子
- //主线程(小弟)吃了 2 包子
- //子线程(老大)吃了 2 包子
- //子线程(老大)吃了 3 包子
- //主线程(小弟)吃了 3 包子
- //子线程(老大)吃了 4 包子
- //主线程(小弟)吃了 4 包子
- //子线程(老大)吃了 5 包子
- //主线程(小弟)吃了 5 包子
- //主线程(小弟)让了子线程(老大)先吃
- //子线程(老大)吃了 6 包子
- //子线程(老大)吃了 7 包子
- //子线程(老大)吃了 8 包子
- //子线程(老大)吃了 9 包子
- //子线程(老大)吃了 10 包子
- //子线程(老大)吃了 11 包子
- //子线程(老大)吃了 12 包子
- //子线程(老大)吃了 13 包子
- //子线程(老大)吃了 14 包子
- //子线程(老大)吃了 15 包子
- //子线程(老大)吃了 16 包子
- //子线程(老大)吃了 17 包子
- //子线程(老大)吃了 18 包子
- //子线程(老大)吃了 19 包子
- //子线程(老大)吃了 20 包子
- //子线程(老大)吃完了,主线程(小弟)接着吃...
- //主线程(小弟)吃了 6 包子
- //主线程(小弟)吃了 7 包子
- //主线程(小弟)吃了 8 包子
- //主线程(小弟)吃了 9 包子
- //主线程(小弟)吃了 10 包子
- //主线程(小弟)吃了 11 包子
- //主线程(小弟)吃了 12 包子
- //主线程(小弟)吃了 13 包子
- //主线程(小弟)吃了 14 包子
- //主线程(小弟)吃了 15 包子
- //主线程(小弟)吃了 16 包子
- //主线程(小弟)吃了 17 包子
- //主线程(小弟)吃了 18 包子
- //主线程(小弟)吃了 19 包子
- //主线程(小弟)吃了 20 包子
- //解释yeild:礼让,不一定成功
- package threaduse;
-
- public class ThreadMethod02 {
- public static void main(String[] args) throws InterruptedException {
- T3 t3 = new T3();
- t3.start();
-
- for(int i=1;i<=20;i++){
- Thread.sleep(1000);
- System.out.println("主线程(小弟)吃了 "+i+" 包子");
- if(i==5){
- System.out.println("主线程(小弟)让了子线程(老大)先吃");
- Thread.yield();//礼让,不一定成功...
- System.out.println("子线程(老大)吃完了,主线程(小弟)接着吃...");
- }
- }
- }
- }
- class T3 extends Thread{
- public void run(){
- for(int i=1;i<=20;i++){
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("子线程(老大)吃了 " +i+" 包子");
- }
- }
- }
- //主线程(小弟)吃了 1 包子
- //子线程(老大)吃了 1 包子
- //子线程(老大)吃了 2 包子
- //主线程(小弟)吃了 2 包子
- //子线程(老大)吃了 3 包子
- //主线程(小弟)吃了 3 包子
- //子线程(老大)吃了 4 包子
- //主线程(小弟)吃了 4 包子
- //主线程(小弟)吃了 5 包子
- //主线程(小弟)让了子线程(老大)先吃
- //子线程(老大)吃了 5 包子
- //子线程(老大)吃完了,主线程(小弟)接着吃...
- //主线程(小弟)吃了 6 包子
- //子线程(老大)吃了 6 包子
- //子线程(老大)吃了 7 包子
- //主线程(小弟)吃了 7 包子
- //主线程(小弟)吃了 8 包子
- //子线程(老大)吃了 8 包子
- //子线程(老大)吃了 9 包子
- //主线程(小弟)吃了 9 包子
- //主线程(小弟)吃了 10 包子
- //子线程(老大)吃了 10 包子
- //主线程(小弟)吃了 11 包子
- //子线程(老大)吃了 11 包子
- //主线程(小弟)吃了 12 包子
- //子线程(老大)吃了 12 包子
- //主线程(小弟)吃了 13 包子
- //子线程(老大)吃了 13 包子
- //主线程(小弟)吃了 14 包子
- //子线程(老大)吃了 14 包子
- //主线程(小弟)吃了 15 包子
- //子线程(老大)吃了 15 包子
- //主线程(小弟)吃了 16 包子
- //子线程(老大)吃了 16 包子
- //主线程(小弟)吃了 17 包子
- //子线程(老大)吃了 17 包子
- //主线程(小弟)吃了 18 包子
- //子线程(老大)吃了 18 包子
- //主线程(小弟)吃了 19 包子
- //子线程(老大)吃了 19 包子
- //主线程(小弟)吃了 20 包子
- //子线程(老大)吃了 20 包子
7、练习题:

//我的代码
- package threaduse;
-
- public class ThreadMethodExercise {
- public static void main(String[] args) throws InterruptedException {
- T4 t4 = new T4();
- Thread thread = new Thread(t4);
- for(int i=1;i<=10;i++){
- System.out.println("hi "+i);
- if (i == 5) {
- thread.start();
- thread.join();
- }
- Thread.sleep(1000);
- }
- System.out.println("主线程结束...");
- }
- }
- class T4 implements Runnable{
- public void run(){
- for(int i=1;i<=10;i++){
- System.out.println("hello"+i);
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- System.out.println("子线程结束...");
- }
- }
- //hi 1
- //hi 2
- //hi 3
- //hi 4
- //hi 5
- //hello1
- //hello2
- //hello3
- //hello4
- //hello5
- //hello6
- //hello7
- //hello8
- //hello9
- //hello10
- //子线程结束...
- //hi 6
- //hi 7
- //hi 8
- //hi 9
- //hi 10
- //主线程结束...
8、用户线程和守护线程:
(1)用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
(2)守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
(3)常见的守护线程:垃圾回收机制
- package threaduse;
-
- public class ThreadMethod03 {
- public static void main(String[] args) throws InterruptedException {
- MyDaemonThread myDaemonThread = new MyDaemonThread();
- //如果我们希望当main线程结束后,子线程自动结束
- //只需将子线程设置守线程即可
- myDaemonThread.setDaemon(true);
- myDaemonThread.start();
- for(int i=1;i<=10;i++){
- System.out.println("宝强在辛苦地工作...");
- Thread.sleep(1000);
- }
- }
- }
- class MyDaemonThread extends Thread{
- public void run(){
- for(;;){//无限循环
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("马蓉和宋喆居心叵测聊天,哈哈哈~~~");
- }
- }
- }
- //宝强在辛苦地工作...
- //马蓉和宋喆居心叵测聊天,哈哈哈~~~
- //宝强在辛苦地工作...
- //宝强在辛苦地工作...
- //马蓉和宋喆居心叵测聊天,哈哈哈~~~
- //马蓉和宋喆居心叵测聊天,哈哈哈~~~
- //宝强在辛苦地工作...
- //宝强在辛苦地工作...
- //马蓉和宋喆居心叵测聊天,哈哈哈~~~
- //马蓉和宋喆居心叵测聊天,哈哈哈~~~
- //宝强在辛苦地工作...
- //马蓉和宋喆居心叵测聊天,哈哈哈~~~
- //宝强在辛苦地工作...
- //宝强在辛苦地工作...
- //马蓉和宋喆居心叵测聊天,哈哈哈~~~
- //马蓉和宋喆居心叵测聊天,哈哈哈~~~
- //宝强在辛苦地工作...
- //马蓉和宋喆居心叵测聊天,哈哈哈~~~
- //宝强在辛苦地工作...
- //马蓉和宋喆居心叵测聊天,哈哈哈~~~
9、线程的生命周期


·如上为官方的文档,所以线程一共有6种状态
- package threaduse;
-
- public class ThreadState {
- public static void main(String[] args) throws InterruptedException {
- T5 t5 = new T5();
- System.out.println(t5.getName()+" 状态"+ t5.getState());
- t5.start();
- while (Thread.State.TERMINATED != t5.getState()) {
- System.out.println(t5.getName()+" 状态"+ t5.getState());
- Thread.sleep(500);
- }
- System.out.println(t5.getName()+" 状态"+ t5.getState());
- }
- }
- class T5 extends Thread{
- public void run(){
- while (true) {
- for(int i=0;i<10;i++){
- System.out.println("hi "+i);
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- break;
- }
-
- }
- }
- //Thread-0 状态NEW
- //Thread-0 状态RUNNABLE
- //hi 0
- //Thread-0 状态TIMED_WAITING
- //hi 1
- //Thread-0 状态TIMED_WAITING
- //Thread-0 状态TIMED_WAITING
- //hi 2
- //Thread-0 状态TIMED_WAITING
- //Thread-0 状态TIMED_WAITING
- //hi 3
- //Thread-0 状态TIMED_WAITING
- //Thread-0 状态TIMED_WAITING
- //hi 4
- //Thread-0 状态TIMED_WAITING
- //Thread-0 状态TIMED_WAITING
- //hi 5
- //Thread-0 状态TIMED_WAITING
- //Thread-0 状态TIMED_WAITING
- //hi 6
- //Thread-0 状态TIMED_WAITING
- //Thread-0 状态TIMED_WAITING
- //hi 7
- //Thread-0 状态TIMED_WAITING
- //Thread-0 状态TIMED_WAITING
- //hi 8
- //Thread-0 状态TIMED_WAITING
- //Thread-0 状态TIMED_WAITING
- //hi 9
- //Thread-0 状态TIMED_WAITING
- //Thread-0 状态TIMED_WAITING
- //Thread-0 状态TERMINATED
10、线程的同步:
(1)在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性
(2)或者理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作
(3)
(4)解决售票超卖问题:
- //使用Runnable
- package ticket;
-
- public class SellTicket {
- public static void main(String[] args) throws InterruptedException {
- SellTicket02 sellTicket = new SellTicket02();
- //三个售票窗口
- new Thread(sellTicket).start();
- new Thread(sellTicket).start();
- new Thread(sellTicket).start();
- }
- }
- //使用Thread
- class SellTicket02 implements Runnable{
- private static int ticketNum=100;
- private boolean loop=true;
-
- public boolean isLoop() {
- return loop;
- }
-
- public void setLoop(boolean loop) {
- this.loop = loop;
- }
-
- public synchronized void sell(){//同步方法,在同一时刻,只能有一个线程来执行sell方法
- if (ticketNum <= 0) {
- System.out.println("售票结束");
- loop=false;
- return;
- }
- try {
- Thread.sleep(50);
- } catch (Exception e) {
- e.printStackTrace();
- }
- System.out.println("窗口 "+Thread.currentThread().getName()+" 售出一张票"
- +" 剩余票数="+(--ticketNum));
- }
-
- public void run(){
- while(loop){
- sell();
- }
- }
- }
- //窗口 Thread-0 售出一张票 剩余票数=99
- //窗口 Thread-0 售出一张票 剩余票数=98
- //窗口 Thread-0 售出一张票 剩余票数=97
- //窗口 Thread-0 售出一张票 剩余票数=96
- //窗口 Thread-0 售出一张票 剩余票数=95
- //窗口 Thread-0 售出一张票 剩余票数=94
- //窗口 Thread-0 售出一张票 剩余票数=93
- //窗口 Thread-0 售出一张票 剩余票数=92
- //窗口 Thread-0 售出一张票 剩余票数=91
- //窗口 Thread-0 售出一张票 剩余票数=90
- //窗口 Thread-0 售出一张票 剩余票数=89
- //窗口 Thread-2 售出一张票 剩余票数=88
- //窗口 Thread-1 售出一张票 剩余票数=87
- //窗口 Thread-1 售出一张票 剩余票数=86
- //窗口 Thread-1 售出一张票 剩余票数=85
- //窗口 Thread-2 售出一张票 剩余票数=84
- //窗口 Thread-2 售出一张票 剩余票数=83
- //窗口 Thread-2 售出一张票 剩余票数=82
- //窗口 Thread-2 售出一张票 剩余票数=81
- //窗口 Thread-2 售出一张票 剩余票数=80
- //窗口 Thread-2 售出一张票 剩余票数=79
- //窗口 Thread-2 售出一张票 剩余票数=78
- //窗口 Thread-2 售出一张票 剩余票数=77
- //窗口 Thread-2 售出一张票 剩余票数=76
- //窗口 Thread-2 售出一张票 剩余票数=75
- //窗口 Thread-0 售出一张票 剩余票数=74
- //窗口 Thread-0 售出一张票 剩余票数=73
- //窗口 Thread-0 售出一张票 剩余票数=72
- //窗口 Thread-0 售出一张票 剩余票数=71
- //窗口 Thread-0 售出一张票 剩余票数=70
- //窗口 Thread-0 售出一张票 剩余票数=69
- //窗口 Thread-0 售出一张票 剩余票数=68
- //窗口 Thread-0 售出一张票 剩余票数=67
- //窗口 Thread-0 售出一张票 剩余票数=66
- //窗口 Thread-0 售出一张票 剩余票数=65
- //窗口 Thread-0 售出一张票 剩余票数=64
- //窗口 Thread-0 售出一张票 剩余票数=63
- //窗口 Thread-0 售出一张票 剩余票数=62
- //窗口 Thread-0 售出一张票 剩余票数=61
- //窗口 Thread-0 售出一张票 剩余票数=60
- //窗口 Thread-0 售出一张票 剩余票数=59
- //窗口 Thread-0 售出一张票 剩余票数=58
- //窗口 Thread-2 售出一张票 剩余票数=57
- //窗口 Thread-2 售出一张票 剩余票数=56
- //窗口 Thread-2 售出一张票 剩余票数=55
- //窗口 Thread-2 售出一张票 剩余票数=54
- //窗口 Thread-1 售出一张票 剩余票数=53
- //窗口 Thread-2 售出一张票 剩余票数=52
- //窗口 Thread-2 售出一张票 剩余票数=51
- //窗口 Thread-2 售出一张票 剩余票数=50
- //窗口 Thread-2 售出一张票 剩余票数=49
- //窗口 Thread-2 售出一张票 剩余票数=48
- //窗口 Thread-2 售出一张票 剩余票数=47
- //窗口 Thread-2 售出一张票 剩余票数=46
- //窗口 Thread-0 售出一张票 剩余票数=45
- //窗口 Thread-0 售出一张票 剩余票数=44
- //窗口 Thread-0 售出一张票 剩余票数=43
- //窗口 Thread-2 售出一张票 剩余票数=42
- //窗口 Thread-2 售出一张票 剩余票数=41
- //窗口 Thread-2 售出一张票 剩余票数=40
- //窗口 Thread-2 售出一张票 剩余票数=39
- //窗口 Thread-1 售出一张票 剩余票数=38
- //窗口 Thread-1 售出一张票 剩余票数=37
- //窗口 Thread-2 售出一张票 剩余票数=36
- //窗口 Thread-2 售出一张票 剩余票数=35
- //窗口 Thread-2 售出一张票 剩余票数=34
- //窗口 Thread-2 售出一张票 剩余票数=33
- //窗口 Thread-2 售出一张票 剩余票数=32
- //窗口 Thread-2 售出一张票 剩余票数=31
- //窗口 Thread-2 售出一张票 剩余票数=30
- //窗口 Thread-2 售出一张票 剩余票数=29
- //窗口 Thread-2 售出一张票 剩余票数=28
- //窗口 Thread-0 售出一张票 剩余票数=27
- //窗口 Thread-0 售出一张票 剩余票数=26
- //窗口 Thread-0 售出一张票 剩余票数=25
- //窗口 Thread-0 售出一张票 剩余票数=24
- //窗口 Thread-0 售出一张票 剩余票数=23
- //窗口 Thread-0 售出一张票 剩余票数=22
- //窗口 Thread-2 售出一张票 剩余票数=21
- //窗口 Thread-2 售出一张票 剩余票数=20
- //窗口 Thread-2 售出一张票 剩余票数=19
- //窗口 Thread-2 售出一张票 剩余票数=18
- //窗口 Thread-1 售出一张票 剩余票数=17
- //窗口 Thread-2 售出一张票 剩余票数=16
- //窗口 Thread-2 售出一张票 剩余票数=15
- //窗口 Thread-2 售出一张票 剩余票数=14
- //窗口 Thread-0 售出一张票 剩余票数=13
- //窗口 Thread-2 售出一张票 剩余票数=12
- //窗口 Thread-2 售出一张票 剩余票数=11
- //窗口 Thread-1 售出一张票 剩余票数=10
- //窗口 Thread-1 售出一张票 剩余票数=9
- //窗口 Thread-1 售出一张票 剩余票数=8
- //窗口 Thread-1 售出一张票 剩余票数=7
- //窗口 Thread-1 售出一张票 剩余票数=6
- //窗口 Thread-2 售出一张票 剩余票数=5
- //窗口 Thread-2 售出一张票 剩余票数=4
- //窗口 Thread-2 售出一张票 剩余票数=3
- //窗口 Thread-2 售出一张票 剩余票数=2
- //窗口 Thread-0 售出一张票 剩余票数=1
- //窗口 Thread-0 售出一张票 剩余票数=0
- //售票结束
- //售票结束
- //售票结束

11、互斥锁:
(1)基本介绍:
1)Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
2)每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
3)关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问
4)同步的局限性:导致程序的执行效率要降低
5)同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
6)同步方法(静态的)的锁为当前类本身。
- class SellTicket02 implements Runnable {
- //...
- //1、public synchronized void sell(){}就是一个同步方法
- public synchronized void sell01() {//同步方法,在同一时刻,只能有一个线程来执行sell方法,靠抢夺得到
- if (ticketNum <= 0) {
- System.out.println("售票结束");
- loop = false;
- return;
- }
- try {
- Thread.sleep(50);
- } catch (Exception e) {
- e.printStackTrace();
- }
- System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
- + " 剩余票数=" + (--ticketNum));
- }
-
- //2、这里锁在this对象
- public void sell02() {//同步方法,在同一时刻,只能有一个线程来执行sell方法,靠抢夺得到
-
- synchronized (this) {
- if (ticketNum <= 0) {
- System.out.println("售票结束");
- loop = false;
- return;
- }
- }
- try {
- Thread.sleep(50);
- } catch (Exception e) {
- e.printStackTrace();
- }
- System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
- + " 剩余票数=" + (--ticketNum));
- }
-
- //3、也可以在代码块上写synchronized,同步代码块
- Object object=new Object();
- public void sell03() {//同步方法,在同一时刻,只能有一个线程来执行sell方法,靠抢夺得到
-
- synchronized (object) {
- if (ticketNum <= 0) {
- System.out.println("售票结束");
- loop = false;
- return;
- }
- }
- try {
- Thread.sleep(50);
- } catch (Exception e) {
- e.printStackTrace();
- }
- System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
- + " 剩余票数=" + (--ticketNum));
- }
-
- //静态的同步方法的锁为当前类本身
- //1、
- public synchronized static void sell04() {}//锁是加在SellTicket02.class
-
- //2、
- public static void sell05(){
- synchronized(SellTicket02.class){
- System.out.println("sell05");
- }
- }
- //...
- }
(2) 注意事项和细节
1)同步方法如果没有使用static修饰:默认锁对象为this
2)如果方法使用static修饰,默认锁对象:当前类.class
3)实现的落地步骤:
需要先分析上锁的代码
选择同步代码块或同步方法
要求多个线程的锁对象为同一个即可!
12、线程的死锁
(1)基本介绍:
多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生
- package threaduse;
-
- public class DeadLock {
- public static void main(String[] args) {
- //模拟死锁现象
- DeadLockDemo A = new DeadLockDemo(true);
- A.setName("A线程");
- DeadLockDemo B = new DeadLockDemo(false);
- B.setName("B线程");
- A.start();
- B.start();
- }
- }
- class DeadLockDemo extends Thread{
- static Object o1=new Object();//保证多线程,共享一个对象,这里使用static
- static Object o2=new Object();
- boolean flag;
-
- public DeadLockDemo(boolean flag) {
- this.flag = flag;
- }
- public void run(){
- //下面业务逻辑分析:
- //1、如果flag为T,线程A就会先得到/持有o1对象锁,然后尝试去获取o2对象锁
- //2、如果线程A得不到o2对象锁,就会Blocked
- //3、如果flag为T,线程B就会先得到/持有o1对象锁,然后尝试去获取o2对象锁
- //4、如果线程B得不到o2对象锁,就会Blocked
- if (flag) {
- synchronized (o1){//对象互斥锁,下面就是同步代码
- System.out.println(Thread.currentThread().getName()+"进入1");
- synchronized (o2){//这里获得li对象的监视权
- System.out.println(Thread.currentThread().getName()+"进入2");
- }
- }
- }else{
- synchronized (o2){//对象互斥锁,下面就是同步代码
- System.out.println(Thread.currentThread().getName()+"进入3");
- synchronized (o1){//这里获得li对象的监视权
- System.out.println(Thread.currentThread().getName()+"进入4");
- }
- }
- }
- //我的理解:
- //有线程走if,拿到了o1的锁,等待着o2的锁,没拿到就没法释放资源,但同时有线程走else,拿到了o2的锁,等待着o1的锁,所以它们永远不可能等到对方
- }
- }
- //输出:(卡住了)
- //A线程进入1
- //B线程进入3
13、释放锁:
下面操作会释放锁:
(1)当前线程的同步方法、同步代码块执行结束
上厕所,完事出来
(2)当前线程的同步代码块、同步方法中遇到break, return
没有正常地完事,经理叫他修改bug,不得已出来
(3)当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
没有正常地完事,发现忘带纸,不得已出来
(4)当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁
没有正常完事,觉得需要酝酿下,所以出来等会再进去
下面操作不会释放锁:
(1)线程执行同步代码块或同步方法时,程序调用Thread.sleep(), Thread.yield()方法暂停当前线程的执行,不会释放锁
上厕所,太困了,在坑位上眯了一会
(2)线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁
提示:应尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用