Java--线程(Thread)
来源:互联网 发布:nginx error log 关闭 编辑:程序博客网 时间:2024/05/21 18:49
一、线程简介
- 作业|程序|进程|线程
(1). 作业:一般存放在外存中;
(2). 程序(Program):静态概念;
(3). 进程(Process):动态概念,是程序一次动态执行的过程,占用特定的地址空间。每个进程都是独立的,由进程控制块(PCB)、程序块和数据块组成。可能存在的问题是,内存的浪费,cpu的负担。
(4). 线程(Thread):进程中一个”单一的连续控制流程/执行路径”。线程又被称为轻量级的进程。 - “进程与线程”的区别与关系
(1). 进程是并发执行的程序在执行过程中分配和管理资源的基本单位,是竞争计算机系统资源的基本单位。线程是进程的一部分,一个没有线程的进程可以被看做是单线程,是CPU调度的基本单位。
(2). 进程拥有一个完整的虚拟地址空间,不依赖于线程而独立存在;反之,线程是进程的一部分,没有自己的地址空间,与进程内的其他线程一起共享分配给该进程的所有资源(相同的变量和对象),它们从同一堆中分配对象,来进行通信、数据交换和同步操作等。
(3). 包含关系:作业是从外存中调度进内存的,而进程是在创建成功时起就在内存中了。一个作业可由多个进程组成,且必须至少由一个进程组成。一个进程可以有一个或多个线程组成。
(4). 由于线程间的通信是在同一地址空间上进行的,所以不需要额外的通信机制,这就使得通信更加简便而且信息传递速度也更快了。
二、线程的创建方式
1 . 继承Thread
详细分析见代码注释
/** * 模拟龟兔赛跑 * 1. 创建多线程,重写run()方法,run()方法里的即线程体 * 2. 使用线程:创建子类对象,调用对象.start()方法启动线程 * @author Fanff * */public class Rabbit extends Thread{ @Override public void run() { for(int i = 0; i < 100; i++){ System.out.println("兔子跑了" + i + "步"); } }}class Tortoise extends Thread{ @Override public void run() { for(int i = 0; i < 100; i++){ System.out.println("乌龟跑了" + i + "步"); } }}public class RabbitApp { public static void main(String[] args) { // 创建线程对象 Rabbit rab = new Rabbit(); Tortoise tor = new Tortoise(); // 线程启动 rab.start();// 将线程调入到CPU的线程组中进入就绪状态,等待CPU安排 tor.start(); for (int i = 0; i <= 1000; i++){ System.out.println("Main线程跑了"+i+"步"); } /* 以上有5个线程:Main、后台的gc、异常、Rabit、Tortoise * 每个线程会交替执行,而不是非得按代码从上到下的执行 */ /*如果是如下直接调用run方法,就是对象调用普通的方法 * 此时就只用3个线程:Main、后台gc、异常 * run方法属于Main线程中,会按照代码从上到下的执行 */ // 直接调用run方法 //rab.run(); //tor.run(); }}
运行的部分结果:
2 .实现Runnable接口
本质来说是通过静态代理设计模式来实现的。
- 实现静态代理的三个条件:
1)真实角色;
2)代理角色:需要持有真实角色的引用;
3)二者得实现相同的接口 - 创建步骤
1). 实现Runnable类创建真实角色;
2). 利用Thread构造器创建代理角色,Thread+真实角色的引用;
3). 代理角色.start();启动线程
/** * 使用Runnable创建线程 * 1. Thread是代理角色,实现了Runnable接口 * 2. 创建一个实现Runnable接口的真实角色 * 3. 代理角色持有真实角色的引用 * @author Fanff * */public class Programmer implements Runnable{ @Override public void run() { for (int i = 1; i <= 1000; i++){ System.out.println("边听歌边睡觉..."); } }}public class ProgrammerApp { public static void main(String[] args) { // 1. 创建真实角色 Programmer p = new Programmer(); // 2. 创建代理角色,并持有真实角色的引用 Thread t = new Thread(p); // 3. 代理角色启动线程 t.start(); }}
两种方式的对比:
A. 继承Thread方法实现多线程的缺点:Java是单继承的的,如果我们的类已经从一个类继承了,则无法再继承Thread,或者说如果继承了Thread类就无法再去继承其他的类,显得很单一。
B. Runnable创建线程的优点:避免单继承的局限;便于共享资源
C. 在通过Thread实现多线程的时候,Thread类已经是实现了Runnable接口,它属于代理角色
/** * 简单模拟抢票 * 说明实现Runnable接口便于资源共享 * @author Fanff * */public class Web12306 implements Runnable{ int num = 50;// 总票数 @Override public void run() { while (true){ if (num <= 0)break; System.out.println(Thread.currentThread().getName() + "抢到了第" + (num--) + "张票"); } } public static void main(String[] args) { // 创建真实角色 Web12306 web = new Web12306(); // 创建多个代理角色,共享资源 Thread t1 = new Thread(web, "t1"); Thread t2 = new Thread(web, "t2"); Thread t3 = new Thread(web, "t3"); // 代理启动 t1.start(); t2.start(); t3.start(); }}
3 使用Callable
(1). 优点:可以解决run()方法中不能返回值和不能抛出异常的缺点;
(2). 缺点:创建太繁琐。因此这里也就不附代码了。
三、线程的状态
1. 线程的状态:新生状态(new创建对象后)->就绪状态(调用start()方法,或者挂起)->运行状态(CPU调用)->就绪状态(如果在cpu时间片段内执行完了,这次可能就绪状态不会出现)->阻塞状态(sleep)|死亡状态。
注意:【就绪<->运行】就绪状态下,线程已经具备了运行的条件,只差分配到CPU,处于线程的就绪队列,一旦获得CPU便进入运行状态并自动调用自己的run方法;运行状态下的线程如果在给定的时间片段内没有执行完,就会被系统强行拿走CPU,回到就绪状态;
【运行->阻塞】运行状态下的线程,在等待IO设备或者sleep等情况下,主动让出CPU并暂时停止自己的运行时,会进入阻塞状态;
【阻塞->就绪】导致阻塞的事件已经结束,如睡眠事件到了,则线程就会进入到就绪队列。
【死亡|结束】原因有两个,一个是正常运行的线程完成了它全部的工作,另一个是线程被强制性地终止了,如执行了stop()或destroy()方法(不推荐)。前者会产生异常,后者是强制被终止的,因此不会释放锁。
2 .相关状态方法
(1). join():合并线程,写在哪个线程里就暂停哪个线程
public class JoinTest extends Thread{ @Override public void run() { for (int i = 1; i <= 30; i++){ System.out.println("新线程在运行!"); } } public static void main(String[] args) throws InterruptedException { JoinTest t = new JoinTest(); t.start();// 就绪状态 for (int i = 0; i < 100; i++){ if (i == 50){ // 放在主线程中,因此阻塞主线程,等待创建的t线程运行完了再运行主线程 t.join();// 如果没有join则主线程和新线程根据CPU的调度交替执行 } System.out.println("主线程在运行"); } }}
(2). yiled():暂停本线程(并不代表要等待下一线程完成了再执行本线程,而是暂时先执行另外的线程),是Thread里的static方法,写在那个线程里就暂停哪个线程
public class YeildTest extends Thread{ @Override public void run() { for (int i = 1; i <= 30; i++){ System.out.println("新线程在运行!"); } } public static void main(String[] args) throws InterruptedException { YeildTest t = new YeildTest(); t.start();// 就绪状态 for (int i = 0; i < 1000; i++){ if (i == 50){ // 放在主线程中,因此阻塞**本**线程,即主线程,等待创建的线程运行完了再运行主线程 Thread.yield();// 如果没有yield则主线程和新线程根据CPU的调度交替执行 } System.out.println("主线程在运行"); } }}
// sleep模拟倒计时public class SleepTest1 { public static void main(String[] args) throws InterruptedException { Date starttime = new Date(); Date endtime = new Date(System.currentTimeMillis() + 10 * 1000); SimpleDateFormat sdf = new SimpleDateFormat("ss"); while (endtime.after(starttime)){ Thread.sleep(1000); endtime = new Date(endtime.getTime() - 1000); System.out.println(sdf.format(endtime)); } }}
/** * sleep模拟网络延时 * @author Fanff * */public class SleepTest2 { public static void main(String[] args) { // 创建真实角色 Web12306 web = new Web12306(); // 创建多个代理角色,共享资源 Thread t1 = new Thread(web, "t1"); Thread t2 = new Thread(web, "t2"); Thread t3 = new Thread(web, "t3"); // 代理启动 t1.start(); t2.start(); t3.start(); }}class Web12306 implements Runnable{ int num = 50;// 总票数 @Override public void run() { while (true){ if (num <= 0)break; try { Thread.sleep(1000);// 模拟网络延时,即前1秒看到了票,等到取的时候却没了 System.out.println(Thread.currentThread().getName() + "抢到了第" + (num--) + "张票"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }}
在模拟网络延时情况下抢票会出现-1票等问题,解决办法见下面的线程锁。
(3). 死亡状态(停止线程)的方法
A. 自然终止:线程体自然执行完毕;
B. 外部干涉:标志法,步骤如下
a). 在线程类中定义线程体使用的标志
b). 线程体使用该标志
c). 对外提供改变该标志的方法
d). 外部根据条件来调用该方法改变标志
/** * 线程的终止: * 由于提供的stop方法不安全,不推荐使用 * 因此利用外部干涉来终止线程 * @author Fanff * */public class Stop1 implements Runnable{ // 在线程类中定义线程体使用的标志 private boolean flag = true; @Override public void run() { while (flag){ System.out.println("new的线程在运行"); } System.out.println("new的线程被外部终止了"); } // 对外提供改变该标志的方法 public void setStop(boolean oflag){ this.flag = oflag; } public static void main(String[] args) { Stop1 s = new Stop1(); new Thread(s).start(); for (int i = 0; i < 1000; i++){ System.out.println("主线程在运行"); if (i == 50){ // 对外提供方法改变标志以改变线程状态 s.setStop(false); } } }}
3 .线程的基本方法与属性
1). 基本方法
a). isAlive()线程是否还未终止;
b). getPriority()获得线程的优先级数值;
c). setPriority()设置线程的优先级;
d). setName()给线程一个名字;
e). getName()获得线程的名字;
f). currentThread():是一个静态方法,获得当前正在运行的线程对象也就是取得自己本身
2). 优先级:只是指概率,不代表绝对的先后
a). MAX_PRIORITY 10
b). MIN_PRIORITY 1
c). NORM_PRIORITY 5(默认)
四、同步与死锁
- 同步:并发的多个线程访问同一份资源,确保资源安全(线程安全)的一种手段。
(1). 同步块:注意锁定的范围(范围过大会造成资源浪费,范围过小会造成锁定无效),一般锁定的是对象。
格式:
synchronized(引用类型变量|this|类.class){ }
(2). 同步方法:在方法的返回值前加入synchronized
/** * 模拟网络延时,造成线程不安全 * @author Administrator * */public class TestSynchronized implements Runnable{ private int num = 10; private boolean flag = true; @Override public void run() { while (flag){ //test1(); //test2(); //test3(); //test4(); //test5(); test6(); } } // 线程不安全 public void test1(){ if (num <= 0){ flag = false; return ; } try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "抢到了第" + num-- + "张票"); } // 线程安全:同步方法,给方法上锁 public synchronized void test2(){ if (num <= 0){ flag = false; return ; } try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "抢到了第" + num-- + "张票"); } //线程安全: 同步块,锁定对象 public void test3(){ synchronized(this){ if (num <= 0){ flag = false; return ; } try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "抢到了第" + num-- + "张票"); } } //同步块:锁定引用类型 public void test4(){ synchronized((Integer) num){ if (num <= 0){ flag = false; return ; } try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "抢到了第" + num-- + "张票"); } } // 同步块:锁定this对象,但是锁定的范围不正确,线程不安全 public void test5(){ if (num <= 0){ flag = false; return ; } synchronized(this){ try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "抢到了第" + num-- + "张票"); } } // 同步块:锁定this对象,但是锁定的范围不正确,线程不安全 public void test6(){ synchronized(this){ if (num <= 0){ flag = false; return ; } } try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "抢到了第" + num-- + "张票"); } public static void main(String[] args) { // 共用一份资源: 一个真实角色,多个代理 TestSynchronized my = new TestSynchronized(); Thread proxy1 = new Thread(my, "甲"); Thread proxy2 = new Thread(my, "乙"); Thread proxy3 = new Thread(my, "丙"); proxy1.start(); proxy2.start(); proxy3.start(); // 自然终止 }}
3 .死锁:形成条件参见操作系统,这里不做说明。解决死锁的方法,生产者消费者模式。实现生产者消费者模式的方法有管道法和信号灯法。这里只实现信号灯法。信号灯法里三个方法:
- wait():释放已持有的锁,进入wait队伍。(sleep()是不会释放锁的,而且wait()是Object的方法,而sleep()是Thread的方法);
- notify():唤醒wait队列中的第一个线程,并将该线程移入互斥锁申请队列;
- notifyAll():唤醒wait队列中的所有的线程,并将这些线程移入互斥锁申请队列
/** * 一个场景,共同的资源 * 生产值消费者模式--信号灯法 * 生产者生产影片,消费者观看影片 * 生产者和消费者不能同时进行 --->上锁 * @author Fanff * */public class Movie { private String pic; // 信号灯:flag = true表示生产者生产,消费者等待,反之... // 生产完成后,通知消费;消费完成后,通知生产 private boolean flag = true; // 制作影片 public synchronized void play(String pic){ if (!flag){// 生产者等待 try { this.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } try { // 生产者无需等待时,开始生产,以500ms表示生产的过程 Thread.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("生产了:" + pic); // 生产完毕,通知消费者消费,同时设置信号灯 this.pic = pic; this.notify(); this.flag = false;// 生产者停止生产 } // 播放影片 public synchronized void watch(){ if (flag){// 消费者等待 try { this.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } try { // 开始消费 Thread.sleep(300); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("消费了:" + pic); // 消费完毕 // 通知生产 this.notify(); // 设置信号灯 this.flag = true; }}/** * 相当于生产者 * @author Fanff * */public class Player implements Runnable{ private Movie m; public Player(Movie m) { super(); this.m = m; } @Override public void run() { for (int i = 0; i < 20; i++){ if (0 == i % 2){ m.play("奥特曼"); }else{ m.play("火影忍者"); } } }}public class Watcher implements Runnable{ private Movie m; public Watcher(Movie m) { super(); this.m = m; } @Override public void run() { for (int i = 0; i < 20; i++){ m.watch(); } }}public class App { public static void main(String[] args) { // 共同的资源 Movie m = new Movie(); Player p = new Player(m); Watcher w = new Watcher(m); new Thread(p).start(); new Thread(w).start(); }}
注意:1). 如果线程对一个指定的对象x发出一个wait()调用,该线程会暂停执行,直到另一个线程对同一个指定的对象x发出一个notify ()调用。2). wait()、notify()、notifyAll()三种方法只能出现在synchronized作用的范围内,即只能在同步中出现,没有同步这三个方法时无用的。
五、任务调度类任务调度:TimerTask任务类
Timer();
void schedule(TimerTask task,long delay);// 只执行一次
void schedule(TimerTask task,Date time);// 在time的时候执行
void schedule(TimerTask task, long delay, long period); // delay是距离调用run()方法的时间,即第一次调用的时间;period是第一次调用 // 之后,从第二次开始每隔多长的时间调用一次 run() 方法
public class TestTimer { public static void main(String[] args) { Timer timer = new Timer(); timer.schedule(new TimerTask(){ @Override public void run() { System.out.println("So easy"); } }, 500, 1000); }}
- [Java] Thread 线程
- JAVA 线程Thread
- java线程thread详解
- java 线程 thread
- Java 线程Thread
- Java线程 Thread
- Java 线程 Thread Runnable
- Java-Thread (线程)
- Java--线程(Thread)
- JAVA中线程Thread
- Java Thread(线程)
- java 线程 thread runnable
- Java线程Thread类
- Java Thread线程
- java Thread线程实例
- Java线程Thread
- Java之线程Thread
- java-Thread(线程)
- oneproxy增加多个用户配置
- 《java设计模式——之模板方法模式》______大军
- Android 怎么退出整个应用程序?
- Retrofit2 + RxJava 中出现的问题
- python 库
- Java--线程(Thread)
- 基础之dp,sp跟px关系
- git 入门 链接
- android从零单排之批量删除手机通讯录
- poj 1251 最小生成树基础
- JDBC实现用于操作数据库Mysql的工具类JDBCTools和DAO--完整可用版
- x264代码剖析(五):encode()函数之x264_encoder_open()函数
- android singleton 泛型模式的单例
- sql杂记