首页 > 代码库 > 线程的全面讲解
线程的全面讲解
进程:
是一个正在执行的程序。
每一个进程执行都有一个执行顺序,该顺是一个执行
线程: 就是进程中的一个独立的控制单元。线程在控制着进程的执行。
jvm 启动时会有一个进程java.exe
该进程中至少一个线程负责java程序的执行。
而且这个线程运行的代码存在与main方法中。该线程称之为主线程
扩展:其实更细节的说明jvm。其实至少有两个线程:主线程 负责垃圾回收机制的线程。
class Test { public static void main(String[] args) { MyThread thread = new MyThread();//创建好了一个线程 thread.start(); // thread.start(); // thread.start(); } } class MyThread extends Thread { public int sum = 100; /* 为什么要覆盖run方法? */ public void run(){ while(true){ System.out.println(Thread.currentThread().getName()+"----"+sum--); } } }
上面的代码thread.start()方法被就、调用多次会出现IllegalThreadStateException异常。
eg:就像一个运动员在起跑线上等待着发号命令,当裁判发号了命令运动员起跑了
但是裁判过了一会儿又发了命令这样运动员肯定会疯了。所以肯会抛异常。
假如在调用方法是d.run()方法那仅仅是对象调用方法(和普通调用一样),而线程创键了,但并没有被开启。
但是d.start()方法是一是通过底层代码开启一个新的子线程,并且调用子线程所要执行的代码块。
run方法和main方法是一样的效果:都是封装了线程要执行的代码块。只是被主次线程调用的区别。
为什么要覆盖run方法?
Thread类用于描述线程。
该类就是定义了一个功能,用于存储线程要运行的代码。该存储功能就是run方法
也就是说Thread类中的run方法,用于存储线程要运行的代码
目的:将自定义代码存储run方法,让线程运行。
线程的几种状态:
第二种创建线程方式:
在创建线程就应该明确要执行那段代码块
public class TestThread2 { public static void main(String[] args) { MyRunner runner = new MyRunner();//创建要被执行的代码对象 Thread thread = new Thread(runner);//创建线程时要明确要执行的代码 thread.start(); thread.start(); thread.start(); } } class MyRunner implements Runnable { public int sum = 100; public void run() { sychronized(对象){//实现同步效果 if (sum > 0) { while (true ) { System. out.println(Thread.currentThread().getName() + "----" + sum++); } } } } }
总结:两种创建线程的区别。
实现的方式:避免了单继承的局限性,而且线程代码存在接口的子类的run方法。
继承的方式:线程代码存在thread的子类中的run方法中。
为什么要将runnnable接口的子类对象传递给thread的构造函数。
因为:自定义的run方法所属的对象是Runnable接口的子类对象。
所以要让线程去指定指定对象的run方法。就必须明确该run方法所属的对象。
在实际的开发当中使用第二种方式比较多。
因为:定义Runnerable的子类不仅仅可以实现他,
而且可以继承其他类。避免了第一种的局限性,单一性。
线程安全问题:
问题原因是:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分
还没有执行完,另外一个线程参与进来的执行,导致共享数据的错误
解决方法:
对于多条操作共享数据的语句,只能让一个线程都执行完,在执行的过程中
其它线程不可以参与执行。
synchronizied(对象){需要同步的代码}
怎样知道要同步哪些代码呢? 参与操作共享数据的据应该放入同步代码块中去。
对象如同锁,持有锁的线程可以在同步中执行。没有持有锁的即使占有cpu也不能执行同步中的代码。
eg : 火车上的卫生间-----经典
同步的前提:
1. 必须有两个或两个以上的线程。
2. 必须多个线程使用同一个锁。
必须保证同步中只有一个线程在运行。
好处:避免了多线程的安全问题
弊端:多线程需要判断锁,较为消耗资源。
同步函数用的是哪一个锁呢?
函数线程要被对象调用,那么函数都有一个所属对象引用:this
所以同步函数的锁是this
package xyxysjxy.thread; public class TestThread3 { public static void main(String args[]) { MyRunner2 mrs1 = new MyRunner2(); Thread t1 = new Thread(mrs1); t1.start(); try { Thread. sleep(10); } catch (Exception e) { } mrs1. flag = true ; Thread t2 = new Thread(mrs1); t2.start(); } } class MyRunner2 implements Runnable { public int ticket = 100; public Object o = new Object(); public boolean flag = false; @Override public void run() { if (ticket > 0) { if (flag ) { while (true ) { if (ticket > 0) synchronized (o ) {//this才可以同步 try{Thread}catch(Exception e){} System. out.println(Thread.currentThread().getName() + "*****" + ticket--); } } } else { if(ticket > 0) show(); } } } public synchronized void show() { System. out.println(Thread.currentThread().getName() + "***" + ticket --); } }
通过上面的结果你会发现他们两个并没有达到同步的效果:
同步的前提是: 1.至少有两个以上的线程同步 2. 必须是同一把锁。
上面的锁不是同意把锁所以不能达到同步
同一把锁不一定必须去用到同一段代码。
静态同步函数的的锁是:class对象 因为静态方法中也不能定义this
静态进内存时,内存中没有本类对象,但一定有该类对应的字节码文件对象
类名.class 该对象类型是class
静态的同步方法中使用的锁是字节码文件对象。
单例设计模式---懒汉式。饿汉式
静态方法没有this super 因为他的调用不需要对象。是封装在字节码文件当中的。
package xyxysjxy.thread; public class TestThread4 { public static void main(String[] args) { Thread t1 = new Thread() { @Override public void run() { Demo D = Demo.getTestThread (); } }; t1. start(); Thread t2 = new Thread() { @Override public void run() { Demo D = Demo.getTestThread (); } }; t2. start(); } } class Demo { public static int flag = 0; private static Demo t = null; private Demo() { } public static Demo getTestThread() { if (t == null) { try { Thread. sleep(100); } catch (Exception e) { } flag++; System. out.println("flag = " + flag); t = new Demo(); return t ; } return t ; } }
输出的结果是:flag = 2, flag =2.
所以出现了线程的不安全问题,本来是单例的但是出现不只是一个对象
解决方法:加上一个静态锁:
package xyxysjxy.thread; public class TestThread4 { public static void main(String[] args) { Thread t1 = new Thread() { @Override public void run() { Demo D = Demo.getTestThread (); } }; t1.start(); Thread t2 = new Thread() { @Override public void run() { Demo D = Demo.getTestThread (); } }; t2.start(); } } class Demo { public static int flag = 0; private static Demo t = null; private Demo() { } public static Demo getTestThread() { if (t == null) {//为了高效点要进行两重判断,线程越安全,效率越低 synchronized (Demo.class) {// 不能用this,因为静态中不存在thi if (t == null) { try { Thread. sleep(100); } catch (Exception e) { } flag++; System. out.println("flag = " + flag); t = new Demo(); return t ; } } } return t ; } } 线程死锁:持有对方的锁 package xyxysjxy.thread; public class TestThread5 { public static void main(String[] args) { MyRunner3 mr3 = new MyRunner3(false); MyRunner3 mr4 = new MyRunner3( true); Thread t1 = new Thread(mr3); t1.start(); Thread t2 = new Thread(mr4); t2.start(); } } class MyRunner3 implements Runnable { private boolean b ; public MyRunner3( boolean b) { this.b = b; } @Override public void run() { if (b ) { synchronized (Suo.s1 ) { System. out.println("if suo si" ); synchronized (Suo.s2 ) { System. out.println("if suo s2"); } } } else { synchronized (Suo.s2 ) { System. out.println("else suo s2" ); synchronized (Suo.s1 ) { System. out.println("else suo s1"); } } } } } class Suo { static Suo s1 = new Suo(); static Suo s2 = new Suo(); }
线程间通讯:
其实就是多线程在操作同一个资源,但是操作的动作不同
wait:
notify:
notifyAll:
都使用在同步中,因为要对持有监视器(锁)的线程操作。
所以要使用同步中,因为只有同步才具有锁。
为什么这些操作线程的方法要定义在object中呢?
因为这些方法在操作同步中的线程时,都必须要标识他们所有操作线程持有的锁。
只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒。
不可以对不同的锁中的线程进行唤醒。
也就是说,等待和唤醒必须是同一个锁。
而锁可以是任意的对象,所以可以被任意对像调用的方法定义在object类中。
生产者消费者问题:
在该代码中注意一下几点:
1. 必须在判断flag时用while进行判断,if不能进行循环判断对于多个消费者和生产者是不行的
2. 必须在唤醒对方时是用notifyAll方法进行唤醒,因为在多个消费者和生产者中notify有可
只是唤醒了生产者方的线程导致线程全部都挂起。notifyAll则同时把对方也唤醒了。
package xyxysjxy.thread; public class ProducerConsumerDemo { public static void main(String args[]) { Resource r = new Resource(); Producer p1 = new Producer(r); Thread t1 = new Thread(p1); //Producer p2 = new Producer(r); Thread t3 = new Thread(p1); Consumer c1 = new Consumer (r); Thread t2 = new Thread(c1); //Consumer c2 = new Consumer(r); Thread t4 = new Thread(c1); t1.start(); t2.start(); t3.start(); t4.start(); } } class Resource { private int count = 1; private String name; private boolean flag = false; public synchronized void set(String name) { while (true ) {//if不能达到判断多线程效果 while (flag ) try { wait(); } catch (Exception e) { } this.name = name + "----" + count++; System. out.println("producer生产---" + this.name ); flag = true ; notifyAll();//notify不一定是唤醒对方的可能是自己这边的线程 } } public synchronized void out() { while (true ) { while (!flag ) try { wait(); } catch (Exception e) { } System. out.println("consumer消费-------" + name); flag = false ; notifyAll(); } } } class Producer implements Runnable { private Resource r; public Producer(Resource r) { this.r = r; } public void run() { r.set( "商品"); } } class Consumer implements Runnable { private Resource r; public Consumer(Resource r) { this.r = r; } public void run() { r.out(); } }
在JDK1.5中提供了多线程升级解决方案。
将同步synchronizied替换成了显示的lock操作。
将object中的wait,notify,notifyAll,替换了condition对象
该对象可以从lock所长获取,该示例中,实现了本方只唤醒对方线程。
class Resource2 { private int count = 1; private String name; private boolean flag = false; private Lock lock = new ReentrantLock(); private Condition producer_c = lock.newCondition(); private Condition consumer_c = lock.newCondition(); public void set(String name) { lock.lock(); while (true ) { while (flag ) try { producer_c.await(); this.name = name + "----" + count++; System. out.println("producer生产---" + this. name); flag = true ; } catch (Exception e) { } finally { consumer_c.signal(); } } } public void out() { lock.lock(); while (true ) { while (!flag ) try { wait(); System. out.println("consumer消费-------" + name); flag = false ; } catch (Exception e) { } producer_c.signal(); } } }
停止线程:该如何停止多线程stop()方法已经过时。所以只有一种方式:让run方法结束
但是开启多线程运行,运行代码通常是在循环结构中。
所以只有控制循环,就可以让run方法结束,也就能让线程结束。】
特殊情况:当线程处于冻结状态,就不会读到为循环所设置的标记,那么就不能控制线程。
当没有指定的方式让冻结的线程恢复到运行状态,这时需要对冻结进行清除。
强制线程恢复到运行状态中来,这样就可以操作标记让线程结束。
示例代码如下:
package xyxysjxy.thread; public class TestThread6 { public static void main(String[] args) throws InterruptedException { MyRunner6 m = new MyRunner6(); Thread t1 = new Thread(m); t1.start(); Thread. sleep(100); t1.interrupt(); //当调用该方法时,会抛出interruptedException } } class MyRunner6 implements Runnable { @Override public synchronized void run() { boolean flag = true; System. out.println(flag); while (flag) {//设置标志位,目的是让运行的线程能得到控制 try { wait(); } catch (InterruptedException e) { System. out.println(e); flag = false;//把标志位至为false就可以中断线程 } System. out.println(Thread.currentThread().getName() + "------"); } } }
setDaemon | 守护线程:后台线程 |
/*
将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用。
该方法首先调用该线程的 checkAccess 方法,且不带任何参数。
这可能抛出 SecurityException(在当前线程中)。
* */
join():
当A线程执行到了B线程的.join()时,A就会等待(释放执行权,
并不代表他把执行权赋予给B了而是释放执行权利而已),等待B线程都执行完了,A才会执行
join可以用来临时加入线程执行。
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。