黑马程序员-----java中你必须掌握的线程技术一DAY15总结

---------------------- 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学习型技术博客、期待与您交流! ----------------------


黑马程序员-----java中你必须掌握的线程技术一DAY15总结,布布扣,bubuko.com

黑马程序员-----java中你必须掌握的线程技术一DAY15总结

上一篇:Python 中的@修饰符作用


下一篇:开发日志:计算Java内一段代码运行所用的时间