多线程入门学习

编程入门 行业动态 更新时间:2024-10-07 16:20:55

<a href=https://www.elefans.com/category/jswz/34/1767532.html style=多线程入门学习"/>

多线程入门学习

多线程学习

  • 多线程入门学习
    • 线程创建的两种方式
      • 继承Thread类
      • 实现Runnable接口
      • 比较:
      • 火车票例子
    • 线程的生命周期
    • 如何停止线程
    • 如何中断线程
    • notify、join、yield的方法说明
    • 线程的异常处理
    • 死锁的解决方案
      • 死锁
      • 例子
      • 解决方案
    • 线程的守护神----守护线程
      • java线程有两类
        • 用户线程
        • 守护线程
        • 应用
        • 如何设置守护线程
          • 注意事项:
          • 实例
          • jstack生成线程快照
  • 详解synchronized关键字
    • 作用
    • 不使用并发手段的后果
    • 两种用法
      • 对象锁
        • 锁对象
        • 锁对象办法
      • 类锁
        • 概念
        • 本质
        • 效果
        • 使用方式
    • 多线程访问同步方法的7种情况(面试常考)
      • 总结

多线程入门学习

线程创建的两种方式

继承Thread类

实现Runnable接口

比较:

  • Runnable方式可以避免Thread方式由于java单继承特性带来的缺陷
  • Runnable的代码可以被多个线程(Thread实例)共享,适合与多个线程处理统一资源的情况

火车票例子

package demo.thread;class MyThread extends Thread {private int ticketsCont = 5;//一共有5张火车票private String name;//窗口,也即是线程的名字public MyThread(String name) {this.name = name;}@Overridepublic void run() {while(ticketsCont > 0) {ticketsCont--;//如果还有票,就卖掉一张System.out.println(name + "卖了1张票,剩余票数为:" + ticketsCont);}}
}public class TicketsThread {public static void main(String[] args) {//创建三个线程,模拟三个窗口MyThread mt1 = new MyThread("窗口1");MyThread mt2 = new MyThread("窗口2");MyThread mt3 = new MyThread("窗口3");//启动三个线程,开始买票mt1.start();mt2.start();mt3.start();}
}

本来只有5张票,现在每个线程都有5张票,一共变成15张了,这不是我们想要的结果,改成实现Runnable接口就可以解决!

package demo.runnable;class MyThread implements Runnable {private int ticketsCont = 5;//一共有5张火车票@Overridepublic void run() {while(ticketsCont > 0) {ticketsCont--;//如果还有票,就卖掉一张System.out.println(Thread.currentThread().getName() + "卖了1张票,剩余票数为:" + ticketsCont);}}
}public class TicketsRunnable {public static void main(String[] args) {MyThread mt = new MyThread();Thread th1 = new Thread(mt, "窗口1");Thread th2 = new Thread(mt, "窗口2");Thread th3 = new Thread(mt, "窗口3");//启动线程,开始买票th1.start();th2.start();th3.start();}
}

线程的生命周期

  1. 创建:new一个线程
  2. 就绪:创建了线程对象后,调用了线程的start()方法(注意:此时线程只是进入了线程队列,等待获取cpu服务,具备了运行的条件,但不一定开始运行!)
    运行:处于就绪状态的的线程,一旦获取了CPU资源,便进入到运行状态,开始执行run()方法里面的逻辑
  3. 终止:线程的run()方法执行完毕,或者线程调用了stop()方法(这种方法被淘汰掉了),线程便进入终止状态
  4. 阻塞:一个正在运行的线程在某些情况下,由于某种原因而暂时让出了CPU资源,暂停了自己的执行,便进入了阻塞状态,如调用了sleep()方法

如何停止线程

使用 stop() 方法强行终止线程,但是不推荐使用这个方法,该方法已被弃用

如何中断线程

使用 interrupt 方法中断线程,调用 interrupt() 方法仅仅是在当前线程中打一个停止的标记,并不是真的停止线程。也就是说,线程中断并不会立即终止线程,而是通知目标线程,有人希望你终止。至于目标线程收到通知后会如何处理,则完全由目标线程自行决定。

public class InterruptThread extends Thread{public static void main(String[] args) {try {InterruptThread t = new InterruptThread();t.start();Thread.sleep(3000);t.interrupt();} catch (InterruptedException e) {e.printStackTrace();}}@Overridepublic void run() {super.run();for(int i = 0; i <= 100000; i++) {System.out.println("i=" + i);}}}

notify、join、yield的方法说明

join:join是Thread的实例方法,让指定的线程先执行完再执行其他线程,而且会阻塞主线程
notify:notify是Object的方法,唤醒一个waiting态的线程,这个线程呢,必须是用同一把锁进入waiting态的
yield:yield是Thread的静态方法,在某个线程里调用Thread.yield(),会使这个线程由正在运行的running状态转变为等待cpu时间片的runable状态。

线程的异常处理

在Java中,线程方法的异常(无论是checked还是unchecked exception),都应该在线程代码边界之内(run方法内)进行try catch并处理掉。一个异常被抛出后,如果没有被捕获处理,则会一直向上抛。异常一旦被Thread.run() 抛出后,就不能在程序中对异常进行捕获,最终只能由JVM捕获。

class MyThread extends Thread{public void run(){System.out.println("Throwing in " +"MyThread");throw new RuntimeException();}
}
class Main {public static void main(String[] args){MyThread t = new MyThread();t.start();try{Thread.sleep(1000);}catch (Exception x){System.out.println("Caught it" + x);}System.out.println("Exiting main");}
}

死锁的解决方案

死锁

两个或多个线程互相持有对方需要的锁而导致这些线程全部处于永久阻塞状态。如:线程A持有对象1的锁,等待对象2的锁;线程B持有对象2的锁,等待对象1的锁。

例子

package com.sxt.syn;
/** 死锁:过多的同步可能造成相互不释放资源* 从而相互等待,一般发生于同步中持有多个对象的锁* 避免:不要在同一个代码块中,同时持有多个对象的锁*/
public class DeadLock {public static void main(String[] args) {Markup g1=new Markup(1,"美女");Markup g2=new Markup(0,"妹子");g1.start();g2.start();
}
}
//口红
class Lipstick{}
//镜子
class Mirror{}
//化妆
class Markup extends Thread{static Lipstick lipstick=new Lipstick();//用static实现多个对象共用同一份资源。static Mirror mirror=new Mirror();//用static实现多个对象共用同一份资源。//选择int choice;//名字String girl;public Markup(int choice,String girl) {this.choice=choice;this.girl=girl;}@Overridepublic void run() {//化妆markup();}//相互持有对方的对象锁-->可能造成死锁private void markup() {if(choice==0) {synchronized(lipstick) {//获得口红的锁System.out.println(this.girl+"涂口红");//1秒后获得镜子的锁try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}/*synchronized(mirror) {System.out.println(this.girl+"照镜子");}*/}//将代码挪下来,不要锁套锁就不会造成死锁synchronized(mirror) {System.out.println(this.girl+"照镜子");}}else {synchronized(mirror) {//获得镜子的锁System.out.println(this.girl+"照镜子");//2秒后获得口红的锁try {Thread.sleep(2000);//这里故意实现时间间隔,才可能造成死锁} catch (InterruptedException e) {e.printStackTrace();}/*synchronized(lipstick) {System.out.println(this.girl+"涂口红");}*/}synchronized(lipstick) {System.out.println(this.girl+"涂口红");}}}}

解决方案

不要在同一个代码块中,同时持有多个对象的锁

线程的守护神----守护线程

java线程有两类

用户线程

简介:运行在前台,执行具体的任务。程序的主线程,连接网络的子线程等都是用户线程

守护线程

简介:运行在后台,为其他前台线程服务
特点:一旦所有用户线程都结束运行,守护线程会随JVM一起结束工作

应用

数据库连接池中的监测线程、jvm启动后的检测线程
最常见的守护线程:垃圾回收线程

如何设置守护线程

可以通过调用Thread类的setDaemon(true)来设置当前的线程为守护线程

注意事项:
  1. setDaemon(true)方法必须在start()方法之前调用,否则会抛出IllegalThreadStateException异常
  2. 在守护线程产生的新线程也是守护线程
  3. 不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑
实例
package demo.daemon;import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.Scanner;class DaemonThread implements Runnable {@Overridepublic void run() {System.out.println("进入守护线程" + Thread.currentThread().getName());try {writeToFile();} catch (Exception e) {e.printStackTrace();}System.out.println("退出守护线程" + Thread.currentThread().getName());}private void writeToFile() throws Exception {File fileName = new File("d:" + File.separator + "daemon.txt");OutputStream os = new FileOutputStream(fileName, true);int count = 0;while(count < 999) {os.write(("\r\nword" + count).getBytes());System.out.println("守护线程" + Thread.currentThread().getName()+ "向文件写入了word" + count++);Thread.sleep(1000);}}
}public class DaemonThreadDemo {public static void main(String[] args) {System.out.println("进入主线程" + Thread.currentThread().getName());DaemonThread daemonThread = new DaemonThread();Thread thread = new Thread(daemonThread);thread.setDaemon(true);//守护线程开始写文件thread.start();//阻塞主线程Scanner scanner = new Scanner(System.in);scanner.next();//主线程执行完后,守护线程没执行完也得退出!System.out.println("退出主线程" + Thread.currentThread().getName());}
}
jstack生成线程快照

作用:生成jvm当前时刻线程的快照(threaddump,即是当前线程中所有线程信息)
目的:帮助定位程序问题出现的原因,如长时间停顿、cpu占用率过高等
使用:
cmd进入java安装目录的bin下,使用jstack [-l] pid
-l:打印锁的一些信息,可不加。
pid:进程id,可通过任务管理器查看javaw.exe(class运行中才出现)的pid

详解synchronized关键字

作用

能够保证在同一时刻最多只有一个线程执行该段代码,以达到保证并发安全的效果

不使用并发手段的后果

package demo.runnable;public class DisappearRequest1 implements Runnable {static DisappearRequest1 instance = new DisappearRequest1();static int i = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(instance);Thread t2 = new Thread(instance);t1.start();t2.start();t1.join();t2.join();//由于t1和t2的join(),main线程要等待t1和t2执行完才继续执行System.out.println(i);}@Overridepublic void run() {for(int j = 0; j < 100000; j++) {i++;}}
}

两个线程同时i++,最后结果会比预计的少
原因:i++,看上去是一个操作,实际上包含三个操作。读取i,将i加1,将i的值写入到内存中

两种用法

对象锁

包括方法锁(默认锁对象为this当前实例对象)和同步代码块锁(自己指定锁对象)

锁对象

可以锁this,也可以创建一个对象,然后锁定这个对象

package demo.runnable;public class SynObjectCodeBlock2 implements Runnable {static SynObjectCodeBlock2 instance = new SynObjectCodeBlock2();Object lock = new Object();Object lock1 = new Object();Object lock2 = new Object();@Overridepublic void run() {//synchronized(this) { //lock或者this效果一样/*synchronized(lock) {System.out.println("我是对象锁的代码块形式,我叫" +Thread.currentThread().getName());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "运行结束");}*/synchronized(lock1) {System.out.println("我是lock1,我叫" +Thread.currentThread().getName());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "lock1运行结束");}synchronized(lock2) {System.out.println("我是lock2,我叫" +Thread.currentThread().getName());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "lock2运行结束");}}public static void main(String[] args) {Thread t1 = new Thread(instance);Thread t2 = new Thread(instance);t1.start();t2.start();while(t1.isAlive() || t2.isAlive()) {}System.out.println("finished");}
}
锁对象办法

synchronized修饰普通方法,锁对象默认为this

package demo.runnable;public class SynObjectMethod3 implements Runnable {static SynObjectMethod3 instance = new SynObjectMethod3();@Overridepublic void run() {//由于方法加了synchronized,//一个线程要等待另一个线程执行完才能执行这个办法method();}public synchronized void method() {System.out.println("我是对象锁的方法修饰符形式,我叫" +Thread.currentThread().getName());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "运行结束");}public static void main(String[] args) {Thread t1 = new Thread(instance);Thread t2 = new Thread(instance);t1.start();t2.start();while(t1.isAlive() || t2.isAlive()) {}System.out.println("finished");}
}

类锁

概念

指synchronized修饰静态的方法或指定锁为Class对象(java类可能有很多个对象,但是只有一个Class对象)

本质

就是Class对象的锁而已

效果

类锁只能在同一时刻被一个对象拥有(不同的Runnable实例对应的类锁只有一个)

使用方式

synchronized加在静态方法上

package demo.runnable;/*** 类锁的第一种方式,static形式*/
public class SynClassStatic4 implements Runnable {//注意两个实例static SynClassStatic4 instance1 = new SynClassStatic4();static SynClassStatic4 instance2 = new SynClassStatic4();@Overridepublic void run() {method();}public static synchronized void method() {System.out.println("我是类锁的第一种方式->static形式,我叫" +Thread.currentThread().getName());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "运行结束");}public static void main(String[] args) {Thread t1 = new Thread(instance1);Thread t2 = new Thread(instance2);t1.start();t2.start();while(t1.isAlive() || t2.isAlive()) {}System.out.println("finished");}
}

synchronized(*.class){}

package demo.runnable;/*** 类锁的第2种方式,synchronized(*.class){}*/
public class SynClassClass5 implements Runnable {//注意两个实例static SynClassClass5 instance1 = new SynClassClass5();static SynClassClass5 instance2 = new SynClassClass5();@Overridepublic void run() {method();}public void method() {synchronized(SynClassClass5.class) {System.out.println("我是类锁的第2种方式->synchronized(*.class){},我叫" +Thread.currentThread().getName());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "运行结束");}}public static void main(String[] args) {Thread t1 = new Thread(instance1);Thread t2 = new Thread(instance2);t1.start();t2.start();while(t1.isAlive() || t2.isAlive()) {}System.out.println("finished");}
}

多线程访问同步方法的7种情况(面试常考)

1.两个线程同时访问一个对象的的同步(普通)方法:能锁住,不同时发生
2. 两个线程同时访问两个对象的的同步(普通)方法:不能锁住,同时发生
3.两个线程访问的是synchronized的静态方法:能锁住
4.同时访问同步方法和非同步方法:非同步方法不受影响

package demo.runnable;/*** 同时访问同步办法和非同步方法*/
public class SynYesandNo6 implements Runnable {static SynYesandNo6 instance = new SynYesandNo6();@Overridepublic void run() {if(Thread.currentThread().getName().equals("Thread-0")) {method1();} else {method2();}}public synchronized void method1() {System.out.println("我是加锁,我叫" +Thread.currentThread().getName());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "运行结束");}public void method2() {System.out.println("我是没加锁,我叫" +Thread.currentThread().getName());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "运行结束");}public static void main(String[] args) {Thread t1 = new Thread(instance);Thread t2 = new Thread(instance);t1.start();t2.start();while(t1.isAlive() || t2.isAlive()) {}System.out.println("finished");}
}

5.访问同一个对象的不同的普通同步方法

package demo.runnable;/*** 同时访问同步办法和非同步方法*/
public class SynDifferentMethod7 implements Runnable {static SynDifferentMethod7 instance = new SynDifferentMethod7();@Overridepublic void run() {if(Thread.currentThread().getName().equals("Thread-0")) {method1();} else {method2();}}public synchronized void method1() {System.out.println("我是方法1,我叫" +Thread.currentThread().getName());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "运行结束");}public synchronized void method2() {System.out.println("我是方法2,我叫" +Thread.currentThread().getName());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "运行结束");}public static void main(String[] args) {Thread t1 = new Thread(instance);Thread t2 = new Thread(instance);t1.start();t2.start();while(t1.isAlive() || t2.isAlive()) {}System.out.println("finished");}
}

6.同时访问静态synchronized和非静态synchronized方法:不能锁住,可以同时发生

package demo.runnable;/*** 同时访问静态同步办法和普通同步方法*/
public class SynStaticAndNormal8 implements Runnable {static SynStaticAndNormal8 instance = new SynStaticAndNormal8();@Overridepublic void run() {if(Thread.currentThread().getName().equals("Thread-0")) {method1();} else {method2();}}public static synchronized void method1() {System.out.println("我是静态加锁方法1,我叫" +Thread.currentThread().getName());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "运行结束");}public synchronized void method2() {System.out.println("我是非静态加锁方法2,我叫" +Thread.currentThread().getName());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "运行结束");}public static void main(String[] args) {Thread t1 = new Thread(instance);Thread t2 = new Thread(instance);t1.start();t2.start();while(t1.isAlive() || t2.isAlive()) {}System.out.println("finished");}
}

7.方法抛出异常后,会释放锁

总结

1.一把锁只能同时被一个线程获取,没有拿到锁的线程必须等待(对应第1、5种情况)
2.每个实例都对应有自己的一把锁,不同实例之间互不影响;例如:锁对象是*.class以及synchronized修饰的是static方法的时候,所有对象共用同一把类锁(对应第2、3、4、6种情况);
3.无论是方法正常执行完毕或者方法抛出异常,都会释放锁(对应第7种情况)

更多推荐

多线程入门学习

本文发布于:2024-03-23 17:28:39,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1740873.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:多线程   入门

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!