---------------------- android培训、java培训、期待与您交流!
----------------------
Java中的线程技术小结
1 进程和线程
进程是指一个内存中运行的应用程序,每个进程都有自己独立的一个内存空间,
一个进程中可以有多个线程。比如windows中,一个运行程序就是一个进程。
Java程序的进程里有几个线程:
1 主线程
2 垃圾回收线程(后台线程)
线程是指进程中的一个执行任务,一个进程中可以运行多个线程,多个线程可以
共享数据。
多进程:是指操作系统中同时运行的多个程序,比如浏览器,磁盘管理器同时
多线程:在同一个继承中同时运行的多个任务,比如迅雷下载
一个进程至少有一个线程,为了提高效率,可以在一个进程中开启多个控制单元
并发运行:可以同时完成运行,但是通过程序运行的结果发现虽然同时运行但是结果都不一致。
因为多线程存在一个特性:随机性
造成的原因,CPU在瞬间不断切换去处理各个线程而导致的
可以理解为多个线程在抢CPU资源
总结:
多线程下载:可以吧线程理解为一个下载通道,一个线程就是一个文件的
下载通道,多个线程也就是同时开启好几个下载通道,当服务器提供下载服务室,
使用下载者是共享带宽的,那么在优先级相同的情况下,总服务器会对总下载进程进行
平均分配。那么如果你的下载程序进程多得到的资源也就多,相对的下载速度也就更快了。
多线程是为了同步完成多项任务,不是为了提供运行效率,通过提高资源使用
效率来提高系统的效率。
线程是在同一个时间需要完成多项任务的时候实现的
线程和进程的比较
线程具有传统进程的特征,也叫做轻型进程或进程元
一般把传统的进程成为重型进程,它相当于只有一个线程任务。
在引入了线程的操作系统中,通常一个进程都有若干个线程,至少需要一个线程
进程和线程的区别:
1 进程有独立的进程空间,进程中的数据存放空间(堆和栈空间)是独立存在的
2 线程的对空间是共享的,栈空间是独立的,线程消耗的资源也比进程小, 相互之间可以相互影响
java中创建线程方式:
1 继承Thread类
子类覆写父类中的run方法,将线程运行的代码存放到run中
建立子类对象的同时线程也被创建
通常调用start方法开启线程
2 实现Runnable接口
子类覆盖接口中的run方法
通过thread类创建线程,并将实现了Runnable接口的子类对象作为参数传递给
Thread类的构造函数
Thread类对象调用start方法开启线程
可使用匿名内部类来写
实例:
package com.itheima; /**当前是一个关于进程的演示程序,主要实现的是启动一个进程3个线程*/ class MyThread extends Thread{//实现一个MyThread类继承Thread进程类 并继承其方法 private String name;//定义一个name私有字符 public MyThread(String name) {//实现一个MyThread的方法 super();//调用父类对象 this.name = name;//把当前name的值赋给对象中的name } public void run(){//实现一个run函数 输出方法打印出name值 System.out.println(name+"启动!"); } } //以上为第一个线程实现完毕 //一下是采用接口的方式实现线程 class YourThread implements Runnable{ private String name; public YourThread(String name) { this.name = name; } @Override public void run() { for (int i = 0; i < 3; i++) { System.out.println(Thread.currentThread().getName()+"第"+i+"次启动!"); } } } public class ThreadTest { public static void main(String[] args) { for (int i = 0; i < 100; i++) { if(i == 50){ new MyThread("刘昭").start();//Thread类对象调用start方法开启线程。 new Thread(new YourThread(""),"章泽天").start(); } } } }
小结
Thread类中的run()和strat()方法区别:
1 run()方法在本线程内调用该Runnable对象run()方法,可以重复多次调用
2 start()方法 启动一个线程,调用该Runnable对象的run()方法,不能多次启动一个线程
实现方式,因为避免了单继承的局限性,所以创建线程建议使用第二种方式
*/ //线程卖票的例子 /*创建线程 子类继承父类Thread 第一种方式 单继承 1 子类覆写父类中的run方法 将线程运行的代码存放在run方法中 2 建立子类对象的同时线程也被创建 3 通过star方式开启线程*/ class SellTicket extends Thread{ private String name; private int num = 50; public SellTicket(String name) { super(); this.name = name; } public void run(){ for (int i = 1; i <= num; i++) { System.out.println(name+"卖出了第"+i+"张票!"); } } } /*实现Runnable接口 第二种方式 创建Runnable接口 可以避免单继承的局限 1 子类覆盖接口中的run方法 2 通过Thread类创建线程,并将实现了Runnable接口的子类对象作为参数传递给 Thread类的构造函数 3 Thread类对象调用start方法开启线程 4 可以使用匿名内部类来写*/ class MySell implements Runnable{ private int num = 50; @Override public void run() { for (int i = 1; i <= num; i++) { System.out.println(Thread.currentThread().getName()+"卖出了第"+i+"张票!"); } } } //主函数入口 public class ThreadTest { public static void main(String[] args) throws Exception { new SellTicket("A").start();//通过调用star来开启线程 new SellTicket("B").start(); new SellTicket("C").start(); new Thread(new MySell(),"D").start(); new Thread(new MySell(),"E").start(); new Thread(new MySell(),"F").start(); for (int i = 10; i > 0; i--) { System.out.println(i); Thread.sleep(1000); } } }
两种进程创建方式的比较:
1 A extends Thread:
简单
不能再继承其他类了(java单继承)
同份资源不能共享
2 A extends Runnable()推荐
优点:多个线程共享一个目标资源,适合多线程处理同一份资源
该类还可以继承其他类,也可以实现其他接口
实现方式,因为避免了单继承的局限性,所以创建线程建议使用第二种方式
为什么要覆盖run方法呢?
Thread类用于描述线程。该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就是run方法.
也就是说Thread类中的run方法,用于存储线程要运行的代码。
线程的生命周期
Thread类内部有个public的枚举,Tread.State,里边将线程的状态分为:
NEW——新建状态,至今尚未启动的线程处于这种状态
RUNNABLE——运行状态,正在java虚拟机中执行的线程处于这种状态
BLOCKED——阻塞状态,受阻赛并等待某个监视器锁的线程处于这种状态。
WAITING——冻结状态,无限期地等待另一个线程来执行某一特定操作的线程处于这种状态
TIME_WATING——等待状态,等待领一个进程来执行取决于指定等待时间的操作
的线程处于这种状态
TERMINATED——已经退出的线程处于这种状态
如何停止线程?
只有一种方法,就是用run方法结束。开启多线程运行,代码通常是循环结构。
只要控制住循环,就可以让run方法结束,也就是线程结束。
控制线程
join方法:调用join方法的线程对象强制运行,该线程强制运行期间,其他线程
无法运行,必须等到该线程结束后其他线程才可以运行。
有人也把这种方式称为联合线程
join方法的重载方式:
join(long millis):
join(long millis,int nanos):
通常很少使用第三个方法:
程序无需精确到一纳秒;
计算机硬件和操作系统也无法精确到一纳秒
实例
package com.itheima; /*join实例 这里使用的是线程的第二种方式 Runnable接口的方式实现线程 首先定义一个类 implements Runnable接口 然后覆写run方法 实现进程的调用*/ class MyThreadDemo implements Runnable{ @Override public void run() { for (int i = 0; i < 50; i++) { System.out.println(Thread.currentThread().getName()+"正在运行!"+i); if(i == 25){ try { new Thread(new MyThreadDemo(),"刘昭").join(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } public class joinTest{ public static void main(String[] args) { new Thread(new MyThreadDemo(),"刘昭").start(); new Thread(new MyThreadDemo(),"章泽天").start(); } }
Daemon 后台线程
后台线程:处于后台运行,任务是为其他线程提供服务,也成为守护线程,或精灵线程
JVM的垃圾回收继承只是典型的后台线程
特点:若所有的前台线程都死亡,后台线程自动死亡
设置后台线程:Thread对象setDaemon(ture);
setDeamon(ture);必须在start()调用前。否则出现IllegalThreadStateException异常
前台线程创建的线程默认是前台线程;
判断时候是后台线程,使用Thread对象的isDaemon()方法
并且当且仅当创建线程是后台线程时,新线程才是后台线程
sleep 线程休眠
让执行的线程暂停一段时间,进入组赛状态。实例中可以参看迅雷下载中的进程暂停
sleep(long millis) throws InterruptedException:毫秒
sleep(long mills,int nanos)
throws InterruptedException:毫秒,纳秒
调用sleep()后,在指定时间段之类,该线程不会获得执行的机会
控制线程的优先级
每个线程都有优先级,优先级的高低之和线程获得执行机会的次数多少有关
并非线程优先级越高的就一定先执行,那个线程的先运行取决于CPU的调度
默认情况下main线程具有普通的优先级,而它创建的线程也具有普通优先级
Thread对象的setPriority(int x)和 getPriority()来设值和获得优先级
MAX_PRIORITY: 值是10
MIN_PRIORITY:值是1
NORM_PRIORITY:值是5(主方法默认优先级)
yieId 线程礼让
暂停当前正在执行的线程对象,并执行其他线程;
Thread的静态方法,可以使当前线程暂停,但是不会阻塞该线程,而是进入就绪
状态。所以完全有可能:某个线程调用了yieId()之后,线程调度器又把他调度出来重新执行
注意查看api
多线程的安全问题
导致安全问题出现的原因分析:
1 多个线程访问出现延迟
2 线程的随机性
线程安全问题在理想情况下不会出现,但一旦出现对软件的影响非常大的
我们可以通过Thread.sleep(long time)方法来简单模拟延迟情况
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,
还没有执行完,另一个线程参与进来执行。导致共享数据的错误。
解决办法:
对多条操作共享数据的语句,只能先让一个线程都执行完。在执行过程中,其他线程不可以参与执行
//在前面的卖票例子上,在每卖票的前面加上模拟延时的语句! class SellDemo implements Runnable{ private int num = 50; @Override public void run() { for (int i = 0; i < 200; i++) { if(num > 0){ try { //因为它不可以直接调用getName()方法,所以必须要获取当前线程。 Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"卖出第"+num--+"张票!"); } } } } public class joinTest{ public static void main(String[] args) { SellDemo s = new SellDemo(); new Thread(s,"A").start(); new Thread(s,"B").start(); new Thread(s,"C").start(); } }
输出:这样的话,会出现买了第0,甚至-1张票的情况!
多线程安全问题的解决方法
三种方法
1 同步代码块:
synchronized(obj){
//obj表示同步监视器,是同一个同步对象
TODO something
}
同步方法
格式:
在方法上加上synchronized修饰符即可。(一般不直接在run方法上加!)
synchronized 返回值类型 方法名(参数列表){
//doingsomething
}
同步方法的同步监听器其实就是 this
静态方法的同步
同步方法
同步代码块
static 不能喝this连用
静态方法的默认同步锁是当前方法所在类的.class对象
同步锁 功能更强大
jdk1.5后的另一种同步机制:
通过显示定义同步锁对象来实现同步,这种机制,同步锁应该使用Lock对象充当。
在实现线程安全控制中,通常使用ReentrantLock(可重入锁)。使用该对象可以
显示加锁和解锁
具有与使用synchronized方法和语句所访问的隐式监控器锁相同的一些基本行为和语句
但功能更强大
public class X {
privatefinal ReentrantLock lock = new ReentrantLock();
//定义需要保证线程安全的方法
publicvoid m(){
//加锁
lock.lock();
try{
//...method body
}finally{
//在finally释放锁
lock.unlock();
}
}
}
线程的通信
有一个数据存储空间,划分为两部分,一部分用于存储人的姓名,另一部分用于存储
人的性别。我们的引用包含两个线程,一个线程不停的向数据存储空间添加数据
(生产者),另一个线程从数据空间取出数据(消费者)
因为线程的不确定性。存在以下两种情况:
若生产者线程刚向存储空间添加了人的姓名还没有添加人的性别,
CPU 就切换到消费者线程,消费者线程把名字和上一个人的性别联系到一起
生产者放了若干数据,消费者才开始取数据,或者是消费者取完一个数据,还没等到生产者
放入新的数据,又重复的取已取过的数据
生产者和消费者
wait() 让当前的进程放弃监视器进入等待,直到其他线程调用同一个监视器
并调用notify()或notifyAll()为止
notify()唤醒在同一对象监听中调用wait方法的第一个线程
notifyAll()唤醒在同一对象监听器中调用wait方法的所有线程
这三个方法之恩个让同步监听器调用
在同步方法中:谁调用
在同步代码块中:谁调用
wait() notify() notifyAll()这三个方法属于Object不属于Thread,这三个方法由同步
监视器对象来调用
两种情况:
1:synchronizd 修饰的方法,因为该类的默认实例(this)就是同步监视器,
所以可以再同步方法中调用这三个方法
2:synchronizd修饰的同步代码块,同步监视器是括号里的对象,所以必须使用该
对象调用这三个方法;
可要是我们使用的是Lock对象来保证同步的,系统中不存在隐式的同步监视器对象,
那么就不能使用这三个方法了,那该咋办呢?
此时,Lock代替了同步方法或同步代码块,Condition代替了同步监视器的功能
Condition对象通过Lock对象的newCondition()方法创建
里面方法包括:
await():等价于同步监听器wait()方法
signal():等价于同步监听器的notify()方法
signalAll():等价于同步监听器的notifyAll方法
例子:设置属性 容易出现的问题是: 名字和性别不对应! 线程通信,很好! package july7; class Person{ private String name; private String sex; private Boolean isimpty = Boolean.TRUE;//内存区为空! public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public void set(String name,String sex){ synchronized (this) { while(!isimpty.equals(Boolean.TRUE)){//不为空的话等待消费者消费! try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } this.name = name;//为空的话生产者创造! this.sex = sex; isimpty = Boolean.FALSE;//创造结束后修改属性! this.notifyAll(); } } public void get(){ synchronized (this) { while(!isimpty.equals(Boolean.FALSE)){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("姓名"+getName()+ ", "+"性别"+getSex()); isimpty = Boolean.TRUE; this.notifyAll(); } } } class Producer implements Runnable{ private Person p; public Producer(Person p) { super(); this.p = p; } @Override public void run() { for (int i = 0; i < 100; i++) { if( i % 2 == 0){ p.set("刘昭", "男"); }else{ p.set("章泽天", "女"); } } } } class Consumer implements Runnable{ private Person p; public Consumer(Person p) { super(); this.p = p; } @Override public void run() { for (int i = 0; i < 100; i++) { p.get(); } } } public class Demo9 { public static void main(String[] args) { Person p = new Person(); new Thread(new Producer(p)).start(); new Thread(new Consumer(p)).start(); } }
----------------------- android培训、java培训、java学习型技术博客、期待与您交流! ----------------------