------- android培训、java培训、期待与您交流! ----------
java基础之多线程
1.多线程概述:
多线程就是在一个进程中有多个线程执行。进程是指正在执行的程序,进程有一个执行顺序,叫执行路径或控制单元。而线程就是进程中一个独立的执行路径。
单核CPU在一个时刻只能执行一个线程,CPU在多个线程间进行着快速切换,产生了同时执行的效果,所以多线程可以提高程序的执行效率。
2.线程的创建方式
方式一:继承Thread类
1)、自定义线程类继承Thread类
2)、复写Thread类的run方法,将自定义线程执行的代码存储到run方法中
3)、创建自定义线程类的对象
4)、调用start方法,启动线程,执行run方法
方式二:实现Runnable接口
1)、定义Runnable接口的实现子类
2)、复写接口的run方法,将线程执行的自定义代码存储到run方法中
3)、创建接口实现子类对象,作为实际参数传递给Thread类构造函数,创建Thread类对象,也就是创建了线程
4)、调用Thread类的start方法,启动线程,执行Runnable接口实现子类中的run方法
/* * 练习:创建两个线程,展示交替运行的效果 * 思路:自定义线程和主线程交替运行。让两个线程分别打印循环语句执行结果,看 * 是否是交替执行。 */ public class CreatThread { public static void main(String[] args) { //Thread01 thread = new Thread01(); Thread thread = new Thread(new SubRunnable()); thread.start(); for(int x = 0;x < 300;x++) System.out.println("main-----"+"主线程"); } } class Thread01 extends Thread{ public void run(){ for(int x = 0;x < 300;x++) System.out.println("thread-----"+"自定义线程"); } } class SubRunnable implements Runnable{ public void run(){ for(int x = 0;x < 300;x++) System.out.println("thread-----"+"自定义线程"); } }
两种方式的区别:
继承的方式是将线程运行的代码存储到Thread类的子类中的run方法中
实现的方式是将线程运行的代码存储到Runnable接口的实现子类中的run方法中
实现的方式打破了单继承的局限性
3.线程状态
创建线程,调用start方法,启动线程,线程有了执行资格,但不一定有执行权
临时状态(阻塞):线程有执行资格,但没执行权
冻结状态:线程执行sleep()或wait()时,失去执行资格和执行权,当指定时间到,或者被notify()唤醒,恢复执行资格,进入临时状态。
运行状态:有执行资格和执行权,即CPU正在执行线程
消亡状态:执行stop方法,或者run方法结束
4.线程安全
1)、线程安全问题的原因:多个线程共享的数据被多条语句执行,当一个线程执行了一部分语句,另一个线程就开始执行,会造成共享数据错误。
2)、解决方法:同步。也就是,当执行操作多线程共享数据的语句时,在一个线程执行这些语句时不允许其他线程执行,先这个个线程执行完再让另一个线程执行。
a.同步代码块
格式:
synchronized(对象){
需要同步的语句
}
其中的对象就是锁,拿到这个锁的线程才能执行其中的语句,没拿到锁,即使获取了cpu执行权也进不去。
b.同步函数
格式:
在函数前加上synchronized符号即可。
同步函数中的锁是哪个锁呢?同步函数中的锁是函数所属对象的引用,也就是this。
3)、同步的前提:
a.必须有两个或两个以上的线程(多个线程)
b.必须是多个线程使用同一个锁(同一个锁)
4)、同步的利弊
保证了线程安全,但是判断锁比较消耗资源
5)、如何寻找线程安全问题
a.明确多线程操作的代码
b.明确多线程共享的数据
c.明确多线程操作的代码中操作共享数据的语句
5.静态函数同步
静态函数随着类的加载进入内存,优先于对象存在,静态函数同步无法使用本类对象作为锁,但类加载进内存时是以字节码文件的方式,也就是类类型的对象:类名.class,静态函数同步用的锁就是它!
例如:单例设计模式--懒汉式
有两种同步方式:同步函数和同步代码块
同步函数的方式,当多个线程调用时,每次都需要判断锁
同步代码块和双重判断的方式,可减少判断锁次数,稍微提高些效率
6.同步中嵌套同步时可能会发生死锁
JDK1.5的新特性,对线程安全处理的新方式,可以指定等待和被唤醒的线程,可以解决死锁问题
7.线程间通信
/* * 线程间通信:就是多个线程操作同一个资源,但操作动作不同 * 练习:有一个资源,有存储和取出两个动作 * 思路:定义一个资源(资源唯一),自定义两个线程,分别对资源进行 * 存储和取出操作 */ public class ThreadCommunicate { public static void main(String[] args) { Resource r = new Resource(); // Thread in = new Thread(new InPut(r)); // Thread out = new Thread(new OutPut(r)); // in.start(); // out.start(); new Thread(new InPut(r)).start(); new Thread(new OutPut(r)).start(); } } // 定义一个资源,并复写构造函数 class Resource { private String name; private String sex; private boolean flag = false; public synchronized void set(String name, String sex) { if (flag) try { wait(); } catch (Exception e) { } this.name = name; this.sex = sex; flag = true; notify(); } public synchronized void get() { if (!flag) try { wait(); } catch (Exception e) { } System.out.println(name + "----->" + sex); flag = false; notify(); } } // 定义存线程 class InPut implements Runnable { int x = 0; private Resource r; InPut(Resource r) { this.r = r; } public void run() { // TODO Auto-generated method stub while (true) { if (x == 0) r.set("小明", "男"); else r.set("丽丽", "女女"); x = (x + 1) % 2; } } } class OutPut implements Runnable { private Resource r; OutPut(Resource r) { this.r = r; } public void run() { while (true) { r.get(); } } } // notifyAll(); /* * wait: notify(); notifyAll(); * * 都使用在同步中,因为要对持有监视器(锁)的线程操作。 所以要使用在同步中,因为只有同步才具有锁。 * * 为什么这些操作线程的方法要定义Object类中呢? 因为这些方法在操作同步中线程时,都必须要标识它们所操作线程只有的锁, * 只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。 不可以对不同锁中的线程进行唤醒。 * * 也就是说,等待和唤醒必须是同一个锁。 * * 而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。 */
生产者和消费者
/* * 需求:生产一个商品消费一个商品,两个生产两个消费 * 思路:定义一个资源(商品工厂),包含生产和消费产品的方法。因为对资源的操作的动作 * 有两种:生产和消费,所以定义两个线程类对应两中动作。 */ public class ProducerConsumer { public static void main(String[] args) { Resources r = new Resources(); Producer pro = new Producer(r); Consumer con = new Consumer(r); Thread t1 = new Thread(pro); Thread t2 = new Thread(pro); Thread t3 = new Thread(con); Thread t4 = new Thread(con); t1.start(); t2.start(); t3.start(); t4.start(); } } class Resources{ private int count = 1; private String name; private boolean flag = false; Lock lock = new ReentrantLock(); Condition condition_pro = lock.newCondition(); Condition condition_con = lock.newCondition(); public void set(String name)throws InterruptedException{ lock.lock(); try{ while(flag){ condition_pro.await(); } this.name = name+count++; System.out.println("生产--->"+this.name); flag = true; condition_con.signal(); } finally{ lock.unlock(); } } public void out()throws InterruptedException{ lock.lock(); try{ while(!flag) condition_con.await(); System.out.println("消费------>"+name); flag = false; condition_pro.signal(); } finally{ lock.unlock(); } } } class Producer implements Runnable{ private Resources r; public Producer(Resources r) { this.r = r; } @Override public void run() { while(true){ try { r.set("商品"); } catch (InterruptedException e) { // TODO Auto-generated catch block } } } } class Consumer implements Runnable{ private Resources r; public Consumer(Resources r) { this.r = r; } @Override public void run() { while(true){ try { r.out(); } catch (InterruptedException e) { // TODO Auto-generated catch block } } } }
7.停止线程:
原理就是让线程运行的代码结束,也就是让run方法结束。怎么结束run方法呢?一般run方法中会定义循环,所以只要定义循环结束标记,让循环结束即可。如果过程中处于了冻结状态,就不会读到循环标记,这时可以通过interrupt方法强制解除冻结状态,让线程恢复到具备执行资格的状态,可以读到循环结束标记结束循环。
8.守护线程:
可以理解为后台线程,调用setDaemon(true)将该线程设置为守护线程,该方法必须在启动线程前调用。当所有线程都是守护线程时,虚拟机退出。
9.加入线程:使用join方法可以临时加入一个线程。
当A线程执行到B线程的join方法时,A线程失去执行权和执行资格,处于冻结状态,B线程开始执行。只有B线程运行结束后,A线程才恢复执行资格,但不一定马上获得执行权。