多线程的基本用法和问题

来源:互联网 发布:中航工业301所待遇知乎 编辑:程序博客网 时间:2024/06/01 13:52


1.多线程的引入

什么是线程* 线程是程序执行的一条路径, 一个进程中可以包含多条线程* 多线程并发执行可以提高程序的效率, 可以同时完成多项工作
(假如我们电脑同时做着几件事:qq,听歌,下载。但cpu不是同时执行的,他是在这三个任务里面分别执行一段,来回切换,由于他的速度很快.表面上看起来是同时执行的。当我们打开很多个任务时,我们就会发现cpu的风扇很快,噪音很大,因为cpu需要不停的分配时间,因此说明:我们多线程的底层都是单线程,只不过多线程是利用了cpu的空余时间)

2.多线程并行和并发的区别

* 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)* 并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。(就是1所述)

3.Java程序运行原理和JVM的启动是多线程的吗

A:Java程序运行原理* Java命令会启动java虚拟机,启动JVM,* 等于启动了一个应用程序,也就是启动了一个进程。* 该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。B:JVM的启动是多线程的吗* JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。


public class a {/** * @param args * 证明jvm是多线程的 */public static void main(String[] args) {for(int i = 0; i < 100000; i++) {new Demo();}for(int i = 0; i < 10000; i++) {System.out.println("我是主线程的执行代码");}}}class Demo {@Overridepublic void finalize() {System.out.println("垃圾被清扫了");}}



类 Thread

java.lang.Object——java.lang.Thread所有已实现的接口:Runnable public class Thread extends Object implements Runnable线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。 每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程。每个线程都可以或不可以标记为一个守护程序。当某个线程中运行的代码创建一个新 Thread 对象时,该新线程的初始优先级被设定为创建线程的优先级,并且当且仅当创建线程是守护线程时,新线程才是守护程序。 每个线程都有一个标识名,多个线程可以同名。如果线程创建时没有指定标识名,就会为其生成一个新名称。 

public Thread()分配新的 Thread 对象。这种构造方法与 Thread(null, null, gname) 具有相同的作用,其中 gname 是一个新生成的名称。自动生成的名称的形式为 "Thread-"+n,其中的 n 为整数。 

public Thread(String name)分配新的 Thread 对象。这种构造方法与 Thread(null, null, name) 具有相同的作用。 参数:name - 新线程的名称。

public Thread(Runnable target)分配新的 Thread 对象这种构造方法与 Thread(null, target,gname) 具有相同的作用

public void start()使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 结果是两个线程并发地运行;当前线程(从调用返回给 start 方法)和另一个线程(执行其 run 方法)。 *多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。 抛出: IllegalThreadStateException - 如果线程已经启动。


多线程程序实现的方式1

1.继承Thread* 定义类继承Thread* 重写run方法* 把新线程要做的事写在run方法中* 创建线程对象* 开启新线程, 内部会自动执行run方法


public class b {/** * @param args */public static void main(String[] args) {MyThread mt = new MyThread();//4,创建Thread类的子类对象//mt.run();//先执行run方法并没有开启线程,他就是一个普通方法,先输出a后输出b.mt.start();//5开启线程,先b后a交替出现,开启线程也需要时间for(int i = 0; i < 1000; i++) {System.out.println("bbbbbbbbbbb");}}}class MyThread extends Thread {//1,继承Threadpublic void run() {//2,重写run方法for(int i = 0; i < 1000; i++) {//3,将要执行的代码写在run方法中System.out.println("aaa");}}}


多线程程序实现的方式2

2.实现Runnable* 定义类实现Runnable接口* 实现run方法* 把新线程要做的事写在run方法中* 创建自定义的Runnable的子类对象* 创建Thread对象, 传入Runnable* 调用start()开启新线程, 内部会自动调用Runnable的run()方法


接口 Runnable

已知实现类:  Thread, TimerTask public interface RunnableRunnable 接口应该由那些打算通过某一线程执行其实例的类来实现。 类必须定义一个称为 run 的无参数方法。 

void run()使用实现接口 Runnable 的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的 run 方法。 方法 run 的常规协定是,它可能执行任何所需的动作。 


public class c  {/** * @param args */public static void main(String[] args) {MyRunnable mr = new MyRunnable();//4,创建Runnable的子类对象//Runnable target = mr;mr = 0x0011Thread t = new Thread(mr);//5,将其当作参数传递给Thread的构造函数t.start();//6,开启线程for(int i = 0; i < 1000; i++) {System.out.println("bb");}}}class MyRunnable implements Runnable {//1,定义一个类实现Runnable@Overridepublic void run() {//2,重写run方法for(int i = 0; i < 1000; i++) {//3,将要执行的代码写在run方法中System.out.println("aaaaaaaaaaaa");}}}


实现Runnable的原理查看源码* 1,看Thread类的构造函数,传递了Runnable接口的引用 * 2,通过init()方法找到传递的target给成员变量的target赋值* 3,查看run方法,发现run方法中有判断,如果target不为null就会调用Runnable接口子类对象的run方法


多线程两种方式的区别* 查看源码的区别:* a.继承Thread : 由于子类重写了Thread类的run(), 当调用start()时, 直接找子类的run()方法* b.实现Runnable : 构造函数中传入了Runnable的引用, 成员变量记住了它,   start()调用run()方法时内部判断成员变量Runnable的引用是否为空,     不为空编译时看的是Runnable的run(),运行时执行的是子类的run()方法* 继承Thread* 好处是:可以直接使用Thread类中的方法,代码简单* 弊端是:如果已经有了父类,就不能用这种方法* 实现Runnable接口* 好处是:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的* 弊端是:不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法,代码复杂



多线程匿名内部类实现线程的两种方式

public class d {/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stub//继承Thread类new Thread() {//1,new 类(){}继承这个类public void run() {//2,重写run方法for(int i = 0; i < 3000; i++) {//3,将要执行的代码,写在run方法中System.out.println("aaaaaa");}}}.start();//4,开启线程//实现Runnable接口new Thread(new Runnable(){       //1,new类(new接口()){}实现这个接口@Overridepublic void run() {//2,重写run方法for(int i = 0; i < 3000; i++) {//3,将要执行的代码,写在run方法中System.out.println("bb");}}}).start(); //4,开启线程}}


多线程获取名字和设置名字

public class e {/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stubnew Thread() {//默认名字Thread-0, ...1 2 3....public void run() {System.out.println(this.getName() + "....aa");}}.start();new Thread("hello") {//通过构造方法给name赋值public void run() {System.out.println(this.getName() + "....bb");this.setName("java");System.out.println(this.getName() + "....cc");}}.start();Thread t1 = new Thread() {public void run() {System.out.println(this.getName() + "....ee");}};Thread t2 = new Thread() {public void run() {this.setName("李四");System.out.println(this.getName() + "....ff");}};t1.setName("张三");t1.start();t2.start();}}


public static Thread currentThread()返回对当前正在执行的线程对象的引用。 


public class f {public static void main(String[] args) {// TODO Auto-generated method stubnew Thread() {public void run() {System.out.println(getName() + "....aaa");}}.start();new Thread(new Runnable() {public void run() {//Thread.currentThread()获取当前正在执行的线程System.out.println(Thread.currentThread().getName() + "...bbb");}}).start();////获取主函数线程的引用,并获取名字System.out.println(Thread.currentThread().getName());//mainThread.currentThread().setName("我是主线程");System.out.println(Thread.currentThread().getName());//没有顺序执行}}

休眠线程

public static void sleep(long millis)throws InterruptedException在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),public static void sleep(long millis,int nanos)throws InterruptedException在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行),


 控制当前线程休眠若干毫秒* 1秒= 1000毫秒*  1秒 = 1000 * 1000 * 1000纳秒 1000000000


public class g {/** * @param args * @throws InterruptedException  */public static void main(String[] args) throws InterruptedException {/*for(int i = 20; i >= 0; i--) {Thread.sleep(1000);System.out.println("倒计时第" +i + "秒");}*///异常catch处理new Thread() {public void run() {for(int i = 0; i < 10; i++) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(getName() + "...aaaaaa");}}}.start();new Thread() {public void run() {for(int i = 0; i < 10; i++) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(getName() + "...bbbbbb");}}}.start();}}


守护线程

public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。  (设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出)参数:on - 如果为 true,则将该线程标记为守护线程。 


public class h {/** * @param args * 守护线程 */public static void main(String[] args) {Thread t1 = new Thread() {public void run() {for(int i = 0; i < 2; i++) {System.out.println(getName() + "...aaaaaaaa");}}};Thread t2 = new Thread() {public void run() {for(int i = 0; i < 50; i++) {System.out.println(getName() + "...bb");}}};t2.setDaemon(true);//设置为守护线程,还有个缓冲时间t1.start();t2.start();}}


加入线程

public final void join() throws InterruptedException等待该线程终止。 public final void join(long millis)throws InterruptedException  等待该线程终止的时间最长为 millis 毫秒。超时为 0 意味着要一直等下去。     


public class i {/** * @param args * join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续 * join(int), 可以等待指定的毫秒之后继续 * 匿名内部类在使用他方法中的局部变量时,这个局部变量必须用final修饰 */public static void main(String[] args) {final Thread t1 = new Thread() {public void run() {for(int i = 0; i < 10; i++) {System.out.println(getName() + "...aaaa");}}};Thread t2 = new Thread() {public void run() {for(int i = 0; i < 10; i++) {if(i == 2) {try {t1.join();//插队,加入//t1.join(1);//插队指定的时间,过了指定时间后,两条线程交替执行} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(getName() + "...bb");}}};t1.start();t2.start();}}


礼让线程

public static void yield()暂停当前正在执行的线程对象,并执行其他线程。   


public class j {/** * yield让出cpu礼让线程 */public static void main(String[] args) {new My_Thread().start();new My_Thread().start();}}class My_Thread extends Thread {public void run() {for(int i = 1; i <= 1000; i++) {if(i % 10 == 0) {Thread.yield();//让出CPUSystem.out.println(getName() + "让出cpu" );}System.out.println(getName() + "..." + i);}}}


优先级

 public final void setPriority(int newPriority) 更改线程的优先级。  默认为5,最小为1,最大为10


public class k {/** * @param args */public static void main(String[] args) {Thread t1 = new Thread(){public void run() {for(int i = 0; i < 100; i++) {System.out.println(getName() + "...aaa" );}}};Thread t2 = new Thread(){public void run() {for(int i = 0; i < 100; i++) {System.out.println(getName() + "...bb" );}}};/*t1.setPriority(10);//设置最大优先级t1.setPriority(1);*/t1.setPriority(Thread.MAX_PRIORITY);//设置最小的线程优先级t2.setPriority(Thread.MIN_PRIORITY);//设置最大的线程优先级t1.start();t2.start();}}



多线程同步代码块

* 1.什么情况下需要同步* 当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.* 如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.* 2.同步代码块* 使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块* 多个同步代码块如果使用相同的锁对象, 那么他们就是同步的


public class L {/** * @param args * 同步代码块 */public static void main(String[] args) {// TODO Auto-generated method stubfinal Printer p = new Printer();/** * 静态只能调用静态,我们是创建了外部类对象,然后在调用他的方法.所以不用静态 * 但如果是内部类,必须静态类... * */new Thread() {public void run() {while(true) {p.print1();}}}.start();new Thread() {public void run() {while(true) {p.print2();}}}.start();}}class Printer {Demo d = new Demo();public void print1(){//System.out.println("a");synchronized(d){//同步代码块,锁机制,锁对象可以是任意的////锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象//因为匿名对象不是同一个对象System.out.print("你");System.out.print("好");System.out.print("世");System.out.print("界");System.out.print("\r\n");}}public void print2(){//System.out.println("b");synchronized(d){System.out.print("我");System.out.print("爱");System.out.print("吃");System.out.print("棒");System.out.print("棒");System.out.print("糖");System.out.print("\r\n");}}}class Demo{}//任意对象


3.使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的


public class L {/** * @param args * 同步代码块 */public static void main(String[] args) {// TODO Auto-generated method stubfinal Printer p = new Printer();new Thread() {public void run() {while(true) {p.print1();}}}.start();new Thread() {public void run() {while(true) {p.print2();}}}.start();}}class Printer {//非静态的同步方法的锁对象是什么?//答:非静态的同步方法的锁对象是this//静态的同步方法的锁对象是什么?//是该类的字节码对象public synchronized void print1(){//同步方法只需要在方法上加synchronized关键字即可////锁对象不能用匿名对象,因为匿名对象不是同一个对象System.out.print("你");System.out.print("好");System.out.print("世");System.out.print("界");System.out.print("\r\n");}public void print2(){synchronized(this){System.out.print("我");System.out.print("爱");System.out.print("吃");System.out.print("棒");System.out.print("棒");System.out.print("糖");System.out.print("\r\n");}}}class Demo{}//任意对象


多线程安全问题

* 多线程并发操作同一数据时, 就有可能出现线程安全问题* 使用同步技术可以解决这种问题, 把操作数据的代码进行同步, 不要多个线程一起操作*  需求:铁路售票,一共100张,通过四个窗口卖完.


public class m {/** * 需求:铁路售票,一共100张,通过四个窗口卖完. * 通过四个线程出售 */public static void main(String[] args) {new Ticket().start();new Ticket().start();new Ticket().start();new Ticket().start();}}class Ticket extends Thread {private static int ticket = 100;//加static,所有对象共享这100张票//private Object obj = new Object();//相当于成员变量,前加static才能做锁对象//private static Object obj = new Object();//如果用引用数据类型成员变量当作锁对象,必须是静态的public void run() {while(true) {synchronized(Ticket.class) {//加this不行,每new一次都是一个新的对象新的锁if(ticket <= 0) {break;}try {Thread.sleep(10);//线程1睡,线程2睡,线程3睡,线程4睡} catch (InterruptedException e) {e.printStackTrace();}System.out.println(getName() + "...这是第" + ticket-- + "号票");}}}}


public class n {/** * @param args * 火车站卖票的例子用实现Runnable接口 */public static void main(String[] args) {// TODO Auto-generated method stubMyTicket mt = new MyTicket();new Thread(mt).start();new Thread(mt).start();new Thread(mt).start();new Thread(mt).start();/*Thread t1 = new Thread(mt);//多次启动一个线程是非法的t1.start();t1.start();t1.start();t1.start();*/}}class MyTicket implements Runnable {private int tickets = 100;//不用static,他不用创建多个对象,只需当做参数传递@Overridepublic void run() {while(true) {//synchronized(Ticket.class),同理,锁对象也可以用thissynchronized(this) {if(tickets <= 0) {break;}try {Thread.sleep(10);//线程1睡,线程2睡,线程3睡,线程4睡} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "...这是第" + tickets-- + "号票");}}}}


public class o {/** * @param args * 需求:铁路售票,一共100张,通过四个窗口卖完. */public static void main(String[] args) {TicketsSeller t1 = new TicketsSeller();TicketsSeller t2 = new TicketsSeller();TicketsSeller t3 = new TicketsSeller();TicketsSeller t4 = new TicketsSeller();t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t4.setName("窗口4");t1.start();t2.start();t3.start();t4.start();}}class TicketsSeller extends Thread {private static int tickets = 100;static Object obj = new Object();public TicketsSeller() {super();}public TicketsSeller(String name) {super(name);}public void run() {while(true) {synchronized(obj) {if(tickets <= 0) break;try {Thread.sleep(10);//线程1睡,线程2睡,线程3睡,线程4睡} catch (InterruptedException e) {e.printStackTrace();}System.out.println(getName() + "...这是第" + tickets-- + "号票");}}}}


多线程死锁

 多线程同步的时候, 如果同步代码嵌套, 使用相同锁, 就有可能出现死锁* 尽量不要嵌套使用



public class p {/** * @param args */private static String s1 = "筷子左";private static String s2 = "筷子右";public static void main(String[] args) {new Thread() {public void run() {while(true) {synchronized(s1) {System.out.println(getName() + "...获取" + s1 + "等待" + s2);synchronized(s2) {System.out.println(getName() + "...拿到" + s2 + "开吃");}}}}}.start();new Thread() {public void run() {while(true) {synchronized(s2) {System.out.println(getName() + "...获取" + s2 + "等待" + s1);synchronized(s1) {System.out.println(getName() + "...拿到" + s1 + "开吃");}}}}}.start();}}





回顾以前说过的线程安全问题(面试常考)* 看源码:Vector,StringBuffer,Hashtable,* Collections.synchroinzed(xxx)方法,同步集合使其线程安全* Vector是线程安全的,ArrayList是线程不安全的* StringBuffer是线程安全的,StringBuilder是线程不安全的* Hashtable是线程安全的,HashMap是线程不安全的







阅读全文
0 0