------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
Java实现多线程有两种方式
1.继承Thread类
public class MyThread extends Thread{ private String name; public MyThread(String name) { this.name = name; } public void run() { for(int i=0;i<5;i++){ System.out.println(name+"运行,i="+i); } } public static void main(String[] args) { MyThread mt1 = new MyThread("线程A"); MyThread mt2 = new MyThread("线程B"); mt1.run(); mt2.run(); } }
上面程序运行完A再运行B,并没有交替运行,此时线程并没有启动,启动线程需要调用Thread的start方法:
public class MyThread extends Thread{ private String name; public MyThread(String name) { this.name = name; } public void run() { for (int i = 0; i < 10; i++) { System.out.println(name+"运行,i="+i); } } public static void main(String[] args) { MyThread mt1 = new MyThread("线程A"); MyThread mt2 = new MyThread("线程B"); mt1.start(); mt2.start(); } }
在启动多线程的时候为什么必须调用start(),而不能调用run()呢?
start()方法在Thread中的定义
public synchronized void start() { if (threadStatus != 0) throw new IllegalThreadStateException(); ... start0(); ... } private native void start0();
以上代码可以发现start()方法被重复调用会报异常。此方法调用了start0(); 此方法是调用了操作系统函数,多线程的实现需要依靠底层操作系统支持。
2.实现Runnable接口
和Thread类构造方法结合,来启动线程
class MyThread implements Runnable{ private String name; public MyThread(String name) { this.name = name; } public void run() { for (int i = 0; i < 50; i++) { System.out.println(name+"运行,"+i); } } public static void main(String[] args) { MyThread mt1 = new MyThread("线程A"); MyThread mt2 = new MyThread("线程B"); Thread t1 = new Thread(mt1); Thread t2 = new Thread(mt2); t1.start(); t2.start(); } }
Thread类和Runnable接口
Thread类是Runnable接口的子类,但他没有完全实现Runnable接口中的run()方法,而是调用了Runnable接口的run方法,也就是说此方法是由Runnable接口的子类完成的,所以如果要通过继承Thread类完成多线程,则必须复写run()方法,以下为部分定义
private Runnable target; public Thread(ThreadGroup group, String name) { init(group, null, name, 0); } private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc){ ... this.target = target; ... } public void run() { if (target != null) { target.run(); } }
Thread类和Runnable接口的子类同时继承Runnable接口,之后将Runnable接口的子类实例放入Thread类中,这种操作模式和代理设计类似
两者区别
1.继承Thread类不能资源共享
class MyThread extends Thread{ private int ticket = 5; public void run() { for (int i = 0; i < 100; i++) { if(ticket > 0){ System.out.println("卖票:ticket="+ticket--); } } } public static void main(String[] args) { MyThread mt1 = new MyThread(); MyThread mt2 = new MyThread(); mt1.start(); mt2.start(); } }
两个线程分别卖了各自的5张票
class MyThread implements Runnable{ private int ticket = 5; public void run() { for (int i = 0; i < 100; i++) { if(ticket > 0){ System.out.println("卖票:ticket="+ticket--); } } } public static void main(String[] args) { MyThread mt = new MyThread(); new Thread(mt).start(); new Thread(mt).start(); }
两个线程共卖了5张票
总结:1.使用Runnable接口可以使多个线程共享同一资源
2. 避免单继承带来的局限
线程的状态
1.创建状态
程序中使用构造方法创建了一个线程对象后,就有了相应的内存空间和其他资源,但还处于不可运行状态。
2.就绪状态
调用start()启动线程后就进入就绪状态,进入线程队列排队,等待cpu服务,已经具备运行条件
3.运行状态
当就绪状态的线程获得处理器资源时就进入了运行状态,自动调用run(),run()定义了线程的操作和功能
4.阻塞状态
当正在执行的线程,被人为挂起suspend(),sleep(),wait()或执行了耗时的I/O操作,会让出cpu并中止自己的执行,进入阻塞状态,不能进入排队队列,只有当引起阻塞的原因解除后,才能转入就绪状态
5.死亡状态
当调用stop()方法时和run()方法执行结束后,线程进入死亡状态,此时不具有继续运行的能力。
线程操作的相关方法
创建线程如果没有为线程指定一个名称,会自动为线程分配一个名称格式为:Thread-x。Thread类中有一个static类型的属性为线程自动命名。
class MyThread implements Runnable{ // 实现Runnable接口 public void run(){ // 覆写run()方法 for(int i=0;i<3;i++){ System.out.println(Thread.currentThread().getName() + "运行,i = " + i) ; // 取得当前线程的名字 } } }; public class CurrentThreadDemo{ public static void main(String args[]){ MyThread mt = new MyThread() ; // 实例化Runnable子类对象 new Thread(mt,"线程").start() ; // 启动线程 mt.run() ; // 直接调用run()方法 } };
运行结果
main运行,i = 0
线程运行,i = 0
main运行,i = 1
线程运行,i = 1
main运行,i = 2
线程运行,i = 2
以上程序中,主方法直接通过Runnable接口的子类对象调用其中的run(),主方法也是一个线程,java中所有线程都是同时启动的,哪个线程先抢占到cpu资源,哪个就先执行。
java程序每次运行至少执行2个进程,一个main进程,一个垃圾收集进程。
判断线程是否启动
class MyThread implements Runnable{ // 实现Runnable接口 public void run(){ // 覆写run()方法 for(int i=0;i<3;i++){ System.out.println(Thread.currentThread().getName() + "运行,i = " + i) ; // 取得当前线程的名字 } } }; public class ThreadAliveDemo{ public static void main(String args[]){ MyThread mt = new MyThread() ; // 实例化Runnable子类对象 Thread t = new Thread(mt,"线程"); // 实例化Thread对象 System.out.println("线程开始执行之前 --> " + t.isAlive()) ; // 判断是否启动 t.start() ; // 启动线程 System.out.println("线程开始执行之后 --> " + t.isAlive()) ; // 判断是否启动 for(int i=0;i<3;i++){ System.out.println("main运行 --> " + i) ; } // 以下的输出结果不确定 System.out.println("代码执行之后 --> " + t.isAlive()) ; // 判断是否启动 } };
主线程运行完了,子线程还在运行
线程的强制运行
线程强制运行期间,其他线程无法运行,必须等待此线程完成后才能执行。
class MyThread implements Runnable{ public void run(){ for(int i=0;i<50;i++){ System.out.println(Thread.currentThread().getName() + "运行,i = " + i) ; } } }; public class ThreadJoinDemo{ public static void main(String args[]){ MyThread mt = new MyThread() ; Thread t = new Thread(mt,"线程"); t.start() ; for(int i=0;i<50;i++){ if(i>10){ try{ t.join() ; // 线程强制运行 }catch(InterruptedException e){} } System.out.println("Main线程运行 --> " + i) ; } } };
运行结果:
Main线程运行 --> 8
线程运行,i = 8
Main线程运行 --> 9
线程运行,i = 9
Main线程运行 --> 10
线程运行,i = 10
线程运行,i = 11
线程运行,i = 12
线程的休眠和中断
class MyThread implements Runnable{ public void run(){ System.out.println("1、进入run()方法") ; try{ Thread.sleep(10000) ; // 线程休眠10秒 System.out.println("2、已经完成了休眠") ; }catch(InterruptedException e){ System.out.println("3、休眠被终止") ; return ; // 返回调用处 } System.out.println("4、run()方法正常结束") ; } }; public class ThreadInterruptDemo{ public static void main(String args[]){ MyThread mt = new MyThread() ; Thread t = new Thread(mt,"线程"); t.start() ; try{ Thread.sleep(2000) ; // 线程休眠2秒 }catch(InterruptedException e){ System.out.println("3、休眠被终止") ; } t.interrupt() ; // 中断线程执行 } };
一个线程运行时,另外一个线程可以通过interupt()方法中断运行状态,休眠一旦中断会进入catch中的代码。
线程的优先级
setPriority(Thread.MIN_PRIORITY); //1 setPriority(Thread.NORM_PRIORITY); //5 setPriority(Thread.MAX_PRIORITY); //10
优先级越高,越有可能被先执行
Thread.currentThread().getPriority();
main函数的优先级为5
线程的礼让
让其他线程先运行
class MyThread implements Runnable{ public void run(){ for(int i=0;i<5;i++){ try{ Thread.sleep(500) ; }catch(Exception e){} System.out.println(Thread.currentThread().getName() + "运行,i = " + i) ; // 取得当前线程的名字 if(i==2){ System.out.print("线程礼让:") ; Thread.currentThread().yield() ; // 线程礼让 } } } }; public class ThreadYieldDemo{ public static void main(String args[]){ MyThread my = new MyThread() ; Thread t1 = new Thread(my,"线程A") ; Thread t2 = new Thread(my,"线程B") ; t1.start() ; t2.start() ; } };
运行结果
线程A运行,i = 0
线程B运行,i = 0
线程A运行,i = 1
线程B运行,i = 1
线程A运行,i = 2
线程B运行,i = 2
线程A运行,i = 3
线程礼让:线程B运行,i = 3
线程礼让:线程A运行,i = 4
线程B运行,i = 4
同步与死锁
通过Runnable接口实现多线程,多个线程操作同一资源,引发资源同步问题
class MyThread implements Runnable{ private int ticket = 5 ; // 假设一共有5张票 public void run(){ for(int i=0;i<100;i++){ if(ticket>0){ // 还有票 try{ Thread.sleep(300) ; // 加入延迟 }catch(InterruptedException e){ e.printStackTrace() ; } System.out.println("卖票:ticket = " + ticket-- ); } } } }; public class SyncDemo01{ public static void main(String args[]){ MyThread mt = new MyThread() ; // 定义线程对象 Thread t1 = new Thread(mt) ; // 定义Thread对象 Thread t2 = new Thread(mt) ; // 定义Thread对象 Thread t3 = new Thread(mt) ; // 定义Thread对象 t1.start() ; t2.start() ; t3.start() ; } };
运行结果:
卖票:ticket = 5
卖票:ticket = 4
卖票:ticket = 3
卖票:ticket = 2
卖票:ticket = 1
卖票:ticket = 0
卖票:ticket = -1
产生-1的原因是线程中加入了延迟操作,一个线程还没有对票进行减操作之前,其他线程已经将票数减少了。
解决方法
1.同步代码块
class MyThread implements Runnable{ private int ticket = 5 ; // 假设一共有5张票 public void run(){ for(int i=0;i<100;i++){ synchronized(this){ // 要对当前对象进行同步 if(ticket>0){ // 还有票 try{ Thread.sleep(300) ; // 加入延迟 }catch(InterruptedException e){ e.printStackTrace() ; } System.out.println("卖票:ticket = " + ticket-- ); } } } } }; public class SyncDemo02{ public static void main(String args[]){ MyThread mt = new MyThread() ; // 定义线程对象 Thread t1 = new Thread(mt) ; // 定义Thread对象 Thread t2 = new Thread(mt) ; // 定义Thread对象 Thread t3 = new Thread(mt) ; // 定义Thread对象 t1.start() ; t2.start() ; t3.start() ; } };
2.同步方法
class MyThread implements Runnable{ private int ticket = 5 ; public void run(){ for(int i=0;i<100;i++){ this.sale() ; // 调用同步方法 } } public synchronized void sale(){ // 声明同步方法 if(ticket>0){ // 还有票 try{ Thread.sleep(300) ; // 加入延迟 }catch(InterruptedException e){ e.printStackTrace() ; } System.out.println("卖票:ticket = " + ticket-- ); } } };
死锁
当两个线程都在等待彼此先完成,造成了程序的停滞,死锁是在程序运行时出现的
class Zhangsan{ // 定义张三类 public void say(){ System.out.println("张三对李四说:“你给我画,我就把书给你。”") ; } public void get(){ System.out.println("张三得到画了。") ; } }; class Lisi{ // 定义李四类 public void say(){ System.out.println("李四对张三说:“你给我书,我就把画给你”") ; } public void get(){ System.out.println("李四得到书了。") ; } }; public class ThreadDeadLock implements Runnable{ private static Zhangsan zs = new Zhangsan() ; // 实例化static型对象 private static Lisi ls = new Lisi() ; // 实例化static型对象 private boolean flag = false ; // 声明标志位,判断那个先说话 public void run(){ // 覆写run()方法 if(flag){ synchronized(zs){ // 同步张三 zs.say() ; try{ Thread.sleep(500) ; }catch(InterruptedException e){ e.printStackTrace() ; } synchronized(ls){ zs.get() ; } } }else{ synchronized(ls){ ls.say() ; try{ Thread.sleep(500) ; }catch(InterruptedException e){ e.printStackTrace() ; } synchronized(zs){ ls.get() ; } } } } public static void main(String args[]){ ThreadDeadLock t1 = new ThreadDeadLock() ; // 控制张三 ThreadDeadLock t2 = new ThreadDeadLock() ; // 控制李四 t1.flag = true ; t2.flag = false ; Thread thA = new Thread(t1) ; Thread thB = new Thread(t2) ; thA.start() ; thB.start() ; } };
生产者和消费者
生产者不断生产,消费者不断从生产者取走数据,但由于线程运行的不确定性,会存在2个问题
1.生产者线程向数据存储空间添加可信息的名称,还没有加入信息的内容,程序就切换到了消费者线程,消费者线程把信息的名称和上一个信息的内容联系到一起
2.生产者放了若干次数据,消费者才开始取数据;或者消费者取完一个数据后,还没等到生产者放入新的数据,又重复取出已取过的数据
class Info{ // 定义信息类 private String name = "zqt"; // 定义name属性 private String content = "黑马学员" ; // 定义content属性 public void setName(String name){ this.name = name ; } public void setContent(String content){ this.content = content ; } public String getName(){ return this.name ; } public String getContent(){ return this.content ; } }; class Producer implements Runnable{ // 通过Runnable实现多线程 private Info info = null ; // 保存Info引用 public Producer(Info info){ this.info = info ; } public void run(){ boolean flag = false ; // 定义标记位 for(int i=0;i<50;i++){ if(flag){ this.info.setName("zqt") ; // 设置名称 try{ Thread.sleep(90) ; }catch(InterruptedException e){ e.printStackTrace() ; } this.info.setContent("黑马学员") ; // 设置内容 flag = false ; }else{ this.info.setName("itheima") ; // 设置名称 try{ Thread.sleep(90) ; }catch(InterruptedException e){ e.printStackTrace() ; } this.info.setContent("www.itheima.com") ; // 设置内容 flag = true ; } } } }; class Consumer implements Runnable{ private Info info = null ; public Consumer(Info info){ this.info = info ; } public void run(){ for(int i=0;i<50;i++){ try{ Thread.sleep(90) ; }catch(InterruptedException e){ e.printStackTrace() ; } System.out.println(this.info.getName() + " --> " + this.info.getContent()) ; } } }; public class ThreadCaseDemo01{ public static void main(String args[]){ Info info = new Info(); // 实例化Info对象 Producer pro = new Producer(info) ; // 生产者 Consumer con = new Consumer(info) ; // 消费者 new Thread(pro).start() ; new Thread(con).start() ; } };
运行结果
itheima --> 黑马学员
zqt --> www.itheima.com
itheima --> 黑马学员
zqt --> www.itheima.com
itheima --> 黑马学员
itheima --> 黑马学员
解决方法:
定义同步方法,并且不直接调用setter和getter方法
class Info{ // 定义信息类 private String name = "zqt"; // 定义name属性 private String content = "黑马学员" ; // 定义content属性 public synchronized void set(String name,String content){ this.setName(name) ; // 设置名称 try{ Thread.sleep(300) ; }catch(InterruptedException e){ e.printStackTrace() ; } this.setContent(content) ; // 设置内容 } public synchronized void get(){ try{ Thread.sleep(300) ; }catch(InterruptedException e){ e.printStackTrace() ; } System.out.println(this.getName() + " --> " + this.getContent()) ; } public void setName(String name){ this.name = name ; } public void setContent(String content){ this.content = content ; } public String getName(){ return this.name ; } public String getContent(){ return this.content ; } }; class Producer implements Runnable{ // 通过Runnable实现多线程 private Info info = null ; // 保存Info引用 public Producer(Info info){ this.info = info ; } public void run(){ boolean flag = false ; // 定义标记位 for(int i=0;i<50;i++){ if(flag){ this.info.set("zqt","黑马学员") ; // 设置名称 flag = false ; }else{ this.info.set("itheima","www.itheima.com") ; // 设置名称 flag = true ; } } } }; class Consumer implements Runnable{ private Info info = null ; public Consumer(Info info){ this.info = info ; } public void run(){ for(int i=0;i<50;i++){ this.info.get() ; } } }; public class ThreadCaseDemo02{ public static void main(String args[]){ Info info = new Info(); // 实例化Info对象 Producer pro = new Producer(info) ; // 生产者 Consumer con = new Consumer(info) ; // 消费者 new Thread(pro).start() ; new Thread(con).start() ; } };
zqt --> 黑马学员
itheima --> www.itheima.cn
itheima --> www.itheima.cn
itheima --> www.itheima.cn
zqt --> 黑马学员
zqt --> 黑马学员
以上代码解决了信息错乱,但仍然存在重复读取的问题,解决这个问题需要用到Object类
等待和唤醒
生产者每生产一个就要等待消费者取走,消费者每取走一个就要等待生产者生产
class Info{ // 定义信息类 private String name = "zqt"; // 定义name属性 private String content = "黑马学员" ; // 定义content属性 private boolean flag = false ; // 设置标志位 public synchronized void set(String name,String content){ if(!flag){ try{ super.wait() ; }catch(InterruptedException e){ e.printStackTrace() ; } } this.setName(name) ; // 设置名称 try{ Thread.sleep(300) ; }catch(InterruptedException e){ e.printStackTrace() ; } this.setContent(content) ; // 设置内容 flag = false ; // 改变标志位,表示可以取走 super.notify() ; } public synchronized void get(){ if(flag){ try{ super.wait() ; }catch(InterruptedException e){ e.printStackTrace() ; } } try{ Thread.sleep(300) ; }catch(InterruptedException e){ e.printStackTrace() ; } System.out.println(this.getName() + " --> " + this.getContent()) ; flag = true ; // 改变标志位,表示可以生产 super.notify() ; } public void setName(String name){ this.name = name ; } public void setContent(String content){ this.content = content ; } public String getName(){ return this.name ; } public String getContent(){ return this.content ; } }; class Producer implements Runnable{ // 通过Runnable实现多线程 private Info info = null ; // 保存Info引用 public Producer(Info info){ this.info = info ; } public void run(){ boolean flag = false ; // 定义标记位 for(int i=0;i<50;i++){ <span style="white-space:pre"> </span>if(i%2==0) this.info.set("zqt","黑马学员") ; // 设置名称 }else{ this.info.set("itheima","www.itheima.com") ; // 设置名称 } } } }; class Consumer implements Runnable{ private Info info = null ; public Consumer(Info info){ this.info = info ; } public void run(){ for(int i=0;i<50;i++){ this.info.get() ; } } }; public class ThreadCaseDemo03{ public static void main(String args[]){ Info info = new Info(); // 实例化Info对象 Producer pro = new Producer(info) ; // 生产者 Consumer con = new Consumer(info) ; // 消费者 new Thread(pro).start() ; new Thread(con).start() ; } };
运行结果:
zqt --> 黑马学员
itheima --> www.itheima.com
zqt --> 黑马学员
itheima --> www.itheima.com
zqt --> 黑马学员
线程的生命周期其他方法
suspend(),resume(),stop() 为 @Deprecated声明 不推荐使用,容易造成死锁
那么如何停止一个线程运行呢?
设置标志位停止线程的运行。
class MyThread implements Runnable{ private boolean flag = true ; // 定义标志位 public void run(){ int i = 0 ; while(this.flag){ System.out.println(Thread.currentThread().getName() +"运行,i = " + (i++)) ; } } public void stop(){ this.flag = false ; // 修改标志位 } }; public class StopDemo{ public static void main(String args[]){ MyThread my = new MyThread() ; Thread t = new Thread(my,"线程") ; t.start() ; try{ Thread.sleep(30) ; }catch(Exception e){ } my.stop() ; // 修改标志位,停止运行 } };
总结:
线程被暂停的原因:wait(),sleep(),join()
暂停解除的原因:notify(),sleep()时间到
练习:1.设计4个线程对象,两个线程每次对j加一,两个线程每次对j减一
class ThreadTest { private int j; public static void main(String args[]) { ThreadTest tt = new ThreadTest(); Inc inc = tt.new Inc(); Dec dec = tt.new Dec(); for (int i = 0; i < 2; i++) { Thread t = new Thread(inc); t.start(); t = new Thread(dec); t.start(); } } private synchronized void inc() { j++; System.out.println(Thread.currentThread().getName() + "-inc:" + j); } private synchronized void dec() { j--; System.out.println(Thread.currentThread().getName() + "-dec:" + j); } class Inc implements Runnable { public void run() { for (int i = 0; i < 100; i++) { inc(); } } } class Dec implements Runnable { public void run() { for (int i = 0; i < 100; i++) { dec(); } } } }