黑马程序员_源自梦想 线程

                                                    ------- android培训java培训、期待与您交流! ---------

进程和线程

进程:是一个正在执行中的程序。
每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。
进程的特点:每一个进程都有自己的独立的一块内存空间、一组资源系统。其内部数据和状态都是完全独立的。
怎么看待进程?进程的优点是提高CPU运行效率,在同一时间内执行多个程序,即并发执行。但是从严格上讲,也不是绝对的同一时刻执行多个程序,只不过CPU在执行时通过时间片等调度算法不同进程高速切换。

线程:就是进程中的一个独立的控制单元。(一个线程是进程的一个顺序执行流。)
线程在控制着进程的执行。
线程的特点:同类的多个线程共享一块内存空间和一组系统资源。

一个进程中至少有一个线程。

如何在自定义的代码中,自定义一个线程呢?
        创建线程的 第一种方式:继承Thread类。
步骤:
1.定义类继承Thread。
2.复写Thread类中的run方法。
目的:将自定义代码存储在run方法。让线程运行。

3.调用线程的start方法,

  该方法两个作用:启动线程,调用run方法。

class Demo1 extends Thread
{
	public void run()
	{
		System.out.println(Thread.currentThread().getName());
	}
}

public class ThreadDemo 
{
	public static void main(String[] args) 
	{

		Demo1 d = new Demo1();//创建好一个线程。
		d.start();//开启线程并执行该线程的run方法。
		System.out.println("*************************");
		d.run();//仅仅是对象调用方法。而线程创建了,并没有运行。
	}
}
//--------结果------------
*************************
main
Thread-0
//main说明d.run()仅仅是对象调用方法。而线程创建了,并没有运行。

创建线程的 第二种方式:实现Runnable
1.定义类实现Runnable接口
2.覆盖Runnable接口中的run方法。
将线程要运行的代码存放在该run方法中。
3.通过Thread类建立线程对象。
4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
为什么要将Runnable接口的子类对象传递给Thread的构造函数。
因为,自定义的run方法所属的对象是Runnable接口的子类对象。
所以要让线程去指定指定对象的run方法。就必须明确该run方法所属对象。
5.调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。

class Ext implements Runnable{
	public void run() {
		System.out.println("cwk");
	}
}
public class RunnableDemo {
	public static void main(String[] args) {
		Ext ext = new Ext();
		Thread t = new Thread(ext);
		t.start();
		System.out.println("*********************");
		new Thread(new Ext()).start();
	}
}
实现方式和继承方式有什么区别呢?
实现方式好处:避免了单继承的局限性。
在定义线程时,建立使用实现方式。
两种方式区别:
继承Thread:线程代码存放Thread子类run方法中。
实现Runnable,线程代码存在接口的子类的run方法。
发现运行结果每一次都不同。
因为多个线程都获取cpu的执行权。cpu执行到谁,谁就运行。明确一点,在某一个时刻,只能有一个程序在运行。(多核除外)
cpu在做着快速的切换,以达到看上去是同时运行的效果。我们可以形象把多线程的运行行为在互相抢夺cpu的执行权。
这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长,cpu说的算。

线程五状态:创建、运行、阻塞、挂起、死亡(java编程思想中4状态 创建、运行、阻塞、死亡)

1.创建状态(new Thread())
2.运行状态(start())
3.阻塞状态(pend)

(1)通过调用sleep()方法使线程进入休眠状态,线程在指定时间内不会运行。
        (2)通过调用join()方法使线程挂起,使自己等待另一个线程的结果,直到另一个线程执行完毕为止。
4.挂起状态(suspend)
 挂起实际上是让线程进入“非可执行”状态下,在这个状态下CPU不会分给线程时间片,进入这个状态可以用来暂停一个线程的运行;在线程挂起后,可以通过重新唤醒线程来使之恢复运行。
     (1)通过调用wait()方法使线程挂起,直到线程得到了notify()和notifyAll()消息,线程才会进入“可执行”状态。
     (2)使用suspend挂起线程后,可以通过resume方法唤醒线程。(不使用)
虽然suspend和resume可以很方便地使线程挂起和唤醒,但由于使用这两个方法可能会造成死锁,因此,这两个方法被标识为 deprecated(抗议)标记,这表明在以后的jdk版本中这两个方法可能被删除,所以尽量不要使用这两个方法来操作线程。
      调用sleep()、yield()、suspend()的时候并没有被释放锁
      调用wait()的时候释放当前对象的锁

      wait()方法表示,放弃当前对资源的占有权,一直等到有线程通知,才会运行后面的代码。
      notify()方法表示,当前的线程已经放弃对资源的占有,通知等待的线程来获得对资源的占有权,但是只有一个线程能够从wait状态中恢复,然后继续运行wait()后面的语句。
      notifyAll()方法表示,当前的线程已经放弃对资源的占有,通知所有的等待线程从wait()方法后的语句开始运行。 

阻塞与挂起的区别:(以前没这个概念,网上引用)
理解一:挂起是一种主动行为,因此恢复也应该要主动完成,而阻塞则是一种被动行为,是在等待事件或资源时任务的表现,你不知道他什么时候被阻塞(pend),也就不能确切 的知道他什么时候恢复阻塞。而且挂起队列在操作系统里可以看成一个,而阻塞队列则是不同的事件或资源(如信号量)就有自己的队列。
理解二:阻塞(pend)就是任务释放CPU,其他任务可以运行,一般在等待某种资源或信号量的时候出现。挂起(suspend)不释放CPU,如果任务优先级高就永远轮不到其他任务运行,一般挂起用于程序调试中的条件中断,当出现某个条件的情况下挂起,然后进行单步调试。
理解三:pend是task主动去等一个事件,或消息.suspend是直接悬挂task,以后这个task和你没任何关系,任何task间的通信或者同步都和这个suspended task没任何关系了,除非你resume task;
理解四:任务调度是操作系统来实现的,任务调度时,直接忽略挂起状态的任务,但是会顾及处于pend下的任务,当pend下的任务等待的资源就绪后,就可以转为ready了。ready只需要等待CPU时间,当然,任务调度也占用开销,但是不大,可以忽略。可以这样理解,只要是挂起状态,操作系统就不在管理这个任务了。
理解五:挂起是主动的,一般需要用挂起函数进行操作,若没有resume的动作,则此任务一直不会ready。而阻塞是因为资源被其他任务抢占而处于休眠态。两者的表现方式都是从就绪态里“清掉”,即对应标志位清零,只不过实现方式不一样。

5.死亡状态
多线程问题
     问题的原因:

当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,
另一个线程参与进来执行。导致共享数据的错误。
     解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。
多线程的同步依靠的是对象锁机制,synchronized关键字的背后就是利用了封锁来实现对共享资源的互斥访问。

//用synchronized进行加锁
class Ticket implements Runnable
{
	private  int tick = 200;

	public synchronized void show()//this
	{
			try{Thread.sleep(30);}catch(Exception e){}
			System.out.println(Thread.currentThread().getName()+"....show.... : "+ tick--);
	}
	public void run() {
		for(int i = 0; i < 100; i++)
			show();
	}
}
public class  ThisLockDemo
{
	public static void main(String[] args) 
	{
		Ticket t = new Ticket();

		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		t1.start();
		t2.start();
	}
}

//同一对象用this进行加锁
class Ticket implements Runnable
{
	private  int tick = 200;
	 //private Object obj  = new Object();
	
	public  void show()
	{
			try{Thread.sleep(30);}catch(Exception e){}
			System.out.println(Thread.currentThread().getName()+"....show.... : "+ tick--);
	}
	public void run() {
		
		//synchronized (obj) {
		
		synchronized (this) {
			for(int i = 0; i < 100; i++)
				show();
		}
	}
}


//静态加锁的是类本身 Ticket.class
class Ticket implements Runnable
{
	private static  int tick = 200;
	 //private Object obj  = new Object();
	
	public static synchronized  void show()//静态加锁的是类本身Ticket.class
	{
		try{Thread.sleep(30);}catch(Exception e){}
		System.out.println(Thread.currentThread().getName()+"....show.... : "+ tick--);
	}
	public void run() {
		//synchronized(Ticket.class){}
	        for(int i = 0; i < 100; i++)
		     show();
		
	}
}

//死锁(一个先给钱,才能取货;另一个你先给货,才能拿钱;相互僵持着)
package com.cwk.inner;

class Test implements Runnable
{
	private int i ;
	public Test(int i){
		this.i = i;
	}
	public void run() {
		if(i == 1){
				while(true){
						synchronized(DB.one){
							System.out.println(Thread.currentThread().getName() + "-->one");
							synchronized (DB.two) {
								System.out.println(Thread.currentThread().getName() + "-->two");
							}
						}
				}
		}else{
			while(true){
				synchronized(DB.two){
					System.out.println(Thread.currentThread().getName() + "-->two");
					synchronized (DB.one) {
						System.out.println(Thread.currentThread().getName() + "-->one");
					}
				}
			}
		}
		
	}
}
class DB{
	public static Object one = new Object();
	public static Object two = new Object();
}
class  DeadLockTest
{
	public static void main(String[] args) 
	{
		Thread t1 = new Thread(new Test(1));
		Thread t2 = new Thread(new Test(0));
		t1.start();
		t2.start();
	}
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
黑马程序员线程练习题主要包括两个问题。第一个问题是如何控制四个线程在打印log之前能够同时开始等待1秒钟。一种解决思路是在线程的run方法中调用parseLog方法,并使用Thread.sleep方法让线程等待1秒钟。另一种解决思路是使用线程池,将线程数量固定为4个,并将每个调用parseLog方法的语句封装为一个Runnable对象,然后提交到线程池中。这样可以实现一秒钟打印4行日志,4秒钟打印16条日志的需求。 第二个问题是如何修改代码,使得几个线程调用TestDo.doSome(key, value)方法时,如果传递进去的key相等(equals比较为true),则这几个线程应互斥排队输出结果。一种解决方法是使用synchronized关键字来实现线程的互斥排队输出。通过给TestDo.doSome方法添加synchronized关键字,可以确保同一时间只有一个线程能够执行该方法,从而实现线程的互斥输出。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [黑马程序员——多线程10:多线程相关练习](https://blog.csdn.net/axr1985lazy/article/details/48186039)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值