java多线程


概述

线程是进程中的内容,而每一个应用程序里面至少有一个线程。

1.什么是线程?

线程:是进程中用于控制程序执行的控制单元(执行路径,执行情景)

 

2.什么是进程?

进程:是正在执行中的程序,每一个进程在执行时都有一个执行顺序,该顺序是一个执行路径,或叫做一个控制单元。

 

示例:

public static void main(String[] args){
	for(int x = 1; x<4000;x++)
		System.out.println(“Hello Word!”);}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

在我们启动JVM执行这个程序时,会有一个叫java.exe的进程。

因为进程中至少有一个线程,所以说明该进程中至少有一个线程在负责java程序的执行。

而这个线程运行的代码存放在main方法中,所以该线程也称为主线程。

3.多线程存在的意义

多线程的出现可以让我们程序中的部分能产生同时运行的效果。

例如:

其实如果更细节的说明,在JVM启动时是不止一个线程的,还有负责垃圾回收机制的线程。

如果JVM只有一个线程,没有垃圾回收线程,会有什么后果呢?

主线程在一直运行,在执行过程中堆内存中会存放很多很多垃圾,在主线程执行完后在做垃圾回收动作。可是,如果主线程在执行过程中垃圾太多堆内存放不下了,主线程就会先停下把垃圾先回收,解决之后在往下继续执行,而它在解决垃圾时我们的程序就会停止,这就很不合理。

如果JVM有多个线程,主线程在运行程序,还有一个线程在负责垃圾回收,这样程序就会更优化。

4.如何在程序中自定义线程?

Java给我们提供了对像线程这类事物的描述,该类是Thread类。

该类中定义了,创建线程对象的方法(构造函数),提供了要被线程执行的代码存储的位置(run()方法),还定义了开启线程运行的方法(start())

Java还给我们提供了一个规则,实现Runnable接口。如果自定义类不继承Thread,也可以实现Runnable接口。并将多线程要运行的代码存放在Runnablerun方法中。

创建线程

方式一:继承Thread类

   步骤:

           1.定义一个类,继承Thread

           2.复写Thread类中的run方法,方法中写需要在新线程中执行的代码

           3.创建Thread类的子类对象,调用线程的start方法

   代码实现:

           public class Demo extends Thread{
		public void run(){//复写Thread类中的run方法
			//这里用来存放我们要线程执行的代码
			System.out.println(“我的线程1”);
                  }
           }
	  public class DemoTest{
		public static void main(String[] args){
		Demo d = new Demo();//创建一个线程
		d.start();//开启线程并执行该线程的run方法
		//d.run();//仅仅是对象调用方法,而线程创建了并没有运行
                     //也可以用匿名的方式
                     //new Demo().start();
                  }
           }

1).为什么要覆盖run方法呢?

      我们为什么要开启线程,就是为了要让线程去执行我们定义的某些代码,让这些代码起      到同时运行的效果。

      Thread类用于描述线程,该类定义了一个功能,用于存储线程要运行的代码,该存储   功能就是run方法。也就是说Thread类中的run方法是用于存储线程要运行的代码。

      所以我们覆盖父类中run方法,在run方法中存放我们要执行的代码。

2).start方法的作用

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

     注意:如直接调用run()方法,则是主线程执行其中代码,不会开启新线程。

 

方式二:实现Runnable

   步骤:

           1. 定义类实现Runnable接口

           2.覆盖Runnable接口中的run方法,将多线程要运行的代码存入其中。

           3.通过Thread类建立线程对象,并将Runnable接口的子类对象作为参数传递给Thread的构造函数。

           4.调用Thread对象的start方法开启线程。

   代码实现:

	public class Demo implements Runnable{
		public void run(){
		//将线程要运行的代码存放在该run方法中
			System.out.println(“我的线程2”);
		}
	}
	public class DemoTest{
		public static void main(String[] args){
		Demo d = new Demo();//这里不是创建线程
		Thread t = new Thread(d);//这里是创建线程
		t.start();//启动线程
		//匿名的方式
		//new Thread(new Demo()).start();
		}
	}

  注意:实现Runnable时这个类还不是线程,创建线程的是Thread类对象或Thread子类对象。

 1).为什么要设计Runnable接口?

      假设我们有两个类AB,它们有一些共性代码,我们通过向上的不断抽取,出现了一个父类。可是A类中有一部分代码是需要被多线程所执行,因为java支持的是单继承,它已经继承了一个类了,所以不能在继承Thread类。java支持多实现所以就设计了Runnable接口。

 2).Runnable接口应该由哪些类来实现?

     Runnable接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个名称为run的无参数方法。

 3).为什么要将Runnable接口的子类对象传递给Thread的构造函数?

     因为线程要运行的代码都在Runnable子类的run方法中存储。所以要将该run方法所属的对象传递给Thread。让Thread线程去使用该对象调用其run方法。

 4).继承Thread类和实现Runnable接口建立线程有什么区别?

  1.它们线程代码存放位置不同

    继承Thread类:线程代码存放在Thread子类run方法中。

    实现Runnable接口:线程代码存放在接口的子类run方法中。

  2.局限性

    实现Runnable接口避免了单继承的局限性。而且实现Runnable接口这个对象中的数据共享(如果不用静态修饰共享数据是在堆内存中)

 

  注意:在定义线程时,建议使用实现Runnable接口方式。

线程运行状态


 

 

 

 

 

临时状态:有执行资格,没有执行权。

冻结状态:没有执行资格的情况下。

运行状态:有执行资格,有执行权。

sleep方法与wait方法的区别?

sleep():不会释放对象锁,有执行权,时间结束后,自动恢复线程。

wait():释放对象锁,进入等待此对象线程池中,只有针对此对象发出notify方法或notifyAll方法后此线程才进入临时状态准备获得执行权进入运行状态。

注意:其中的stop方法现在已经过时用不了,他被新方法interrupt方法所代替。冻结状态的线程都会在线程池中。

同步

我们在运行多线程代码时发现运行结果每一次都不同,说明多线程具备随机性,因为这是由CPU不断的快速切换造成的。这就有可能会产生问题。

问题产生的原因:

     1,多线程代码中有操作共享数据。

     2,多条语句操作该共享数据。

当具备这两点时:有一个线程对多条操作共享数据的代码只执行了一部分时,另一个线程就开始参与执行,这就会发生数据错误。

解决方法:当一个线程在执行多条操作共享数据代码时,其它线程即使获取了执行权,也不可以参与操作。

Java对这种解决方式提供了专业的代码——同步。

同步的原理:就是将部分操作功能数据的代码进行加锁(也叫监视器)

同步的前提:

     1.必须要有两个或两个以上的线程。

     2.必须是多个线程使用同一个锁。

同步的好处与弊端:

       好处:解决了线程的安全问题。

       弊端:较为消耗资源,同步嵌套后容易死锁。

什么是死锁?

代码示例:

class Test implements Runnable
{
		private boolean flag;//定义一个布尔型变量
		Test(boolean flag)
		{
			this.flag = flag;
		}
	
		public void run(){
			if(flag){
				//同步代码块的嵌套
				synchronized(DuiXiang.t1){//锁1
					System.out.println("if ----1");
			
					synchronized (DuiXiang.t2){//锁2
						System.out.println("if----2");
					}	
				}	
	
			}
			else{
				synchronized(DuiXiang.t2){//锁2
					System.out.println("else----2");
					synchronized (DuiXiang.t1){//锁1
						System.out.println("else----1");
					}	
				}
			}
		}
}

class DuiXiang{//为了方便测试,所以把对象单独提取出来
       static Object t1 = new Object();//定义一个静态的Object对象
       static Object t2 = new Object();
}

class TestDemo{
	public static void main(String[] args){
		Thread h1 = new Tread(new Tesst(true));//创建线程
		Thread h2 = new Tread(new Tesst(false));
		h1.start();//启动线程
		h2.start();
	}
}

 

注意:我们在开发时一定要注意避免死锁。

同步的表现形式:

     1.      同步代码块

	synchronized(对象){
		需要被同步的代码
	}

    2.       同步函数

           1.一般同步函数

	public synchronized void 方法名(){
		需要被同步的代码
	} 

           2).静态同步函数

	public synchronized static void 方法名(){//静态同步函数
		需要被同步的代码
	} 

同步代码快与同步函数有什么不同?

     它们的锁不同:

          同步代码块:锁是任意对象。

          同步函数:一般同步函数是this。静态同步函数是类名.class,是该类的字节码文件对象(涉及示例:单例设计模式的懒汉式)

如何找安全问题?

        1.明确哪些代码是多线程运行代码。

        2.明确共享数据。

        3.明确多线程运行代码中哪些语句是操作共享数据的。

注意:一定要明白哪里可以用同步哪里不可以用,也不可以把run方法都放到同步里,那样就相当成了单线程。

多线程的一些方法

       currentThread():获取当前正在执行的线程对象

       getName():获取线程名称

       Thread.currentThread().getName():线程名称

       设置线程名称:

              1.      setName();

              2.    构造函数

                       类名(String name){

                               super(name);//父类有这个构造函数,所以直接调用就行

                       }

设置线程名称的意义

我们想要知道当前到底是哪一个线程在运行,我们可以获取其名称并进行判断。 

wait():等待。//这里会发生异常

notify():唤醒一个。

notifyAll():唤醒线程池中的所有。

上面这三个都使用在同步中,因为要对持有监视器()的线程操作。所以要使用在同步中,因为只有同步才具有锁。

为什么这三个操作线程的方法要定义Object类中呢?

 因为这些方法在操作同步中线程时,都必须要标识它们所操作线程持有的锁。

只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒。

不可以对不同锁中的线程进行唤醒。

而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。

       ThreadGroup():线程组。一般情况下,是谁开启这个线程的,这个线程就属于哪个组。

       toString():返回该线程的字符串表示形式,包括线程名称、优先级和线程组。一般用的不多。

多线程示例

线程间通信:就是多个线程在操作同一个资源,但操作的动作不同,如:我们有一些数据,a在往里存,b在往出取。

等待唤醒机制示例:

class XianCheng {
	public static void main(String[] args) {
		Res r = new Res();
		new Thread(new Input(r)).start();//创建线程并启动
		new Thread(new Output(r)).start();
	}
}

class Res{
	private String name;//姓名
	private String sex;//性别
	private boolean fal;//默认是false
	public synchronized void set(String name,String sex){
		if(fal)//判断
			try {
				this.wait();//这里会出现异常,所以只能try或抛
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		this.name = name;
		this.sex = sex;
		fal = true;
		this.notify();//唤醒一个线程
	}
	
	public synchronized void out(){
		if(!fal)//这里是不等于ture,因为上面给付了true
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		System.out.println(name+"----------"+sex);
		fal = false;
		this.notify();
	}
}

//输入姓名 性别
class Input implements Runnable{
	private Res r;//建立Res的引用,确保拿到的是同一个对象
	Input(Res r){
		this.r = r;
	}
	public void run() {
		int x = 0;
		while(true){
		if(x==0)
			r.set("小丽", "女");
		else
			r.set("Jack", "man");
		x=(x+1)%2;
	}
	}
}

//输出姓名 性别
class Output implements Runnable{
	private Res r;
	Output(Res r){
		this.r = r;
	}
	public void run() {
		while(true)
			r.out();
	}
}


 

等待的线程放在哪里呢?

在线程运行时内存中会建立一个线程池,等待线程都存在这个线程池中,当我们notify时唤醒的都是线程池中的线程,通常唤醒第一个被等待的,因为它是按顺序往里存的。

 

停止线程与守护线程

如何停止线程

1.       定义循环结束标记,让run方法结束,因为线程运行代码一般都是循环,只要控制了循环就可以让run方法结束,也就是线程结束。

2.       使用interrupt方法,该方法是结束线程的冻结状态,使线程回到运行状态中来。

 

特殊情况:

当线程处于了冻结状态,就不会读取到标记,那么线程就不会结束。当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除。强制让线程恢复到运行状态,这样就可以操作标记让线程结束。

 

守护线程

setDaemon(boolean on):将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,java虚拟机退出。该方法必须在启动线程前调用。

守护线程相当于后台线程,前台线程如果结束,后台线程自动结束。

 

守护线程代码示例:

class StopThread implements Runnable
{
	private boolean flag =true;//定义布尔型变量,赋初值true
	public void run()
	{
		while(flag)
		{
			/*
			try{
				wait();//等待
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			*/
			System.out.println(Thread.currentThread().getName()+"....run");
		}
	}
	public void changeFlag()
	{
		flag = false;
	}
}

class  StopThreadDemo
{
	public static void main(String[] args) 
	{
		StopThread st = new StopThread();
		
		Thread t1 = new Thread(st);//创建线程
		Thread t2 = new Thread(st);

		t1.setDaemon(true);//定义守护线程
		//t2.setDaemon(true);
		t1.start();
		t2.start();

		int num = 0;
		while(true)
		{
			if(num++ == 60)
			{
				st.changeFlag();
				//t1.interrupt();//停止线程
				//t2.interrupt();
				break;
			}
			System.out.println(Thread.currentThread().getName()+"......."+num);
		}
		System.out.println("over");
	}
}


 

Join与yield方法示例:

class Demo implements Runnable
{
	public void run()
	{
		for(int x=0; x<70; x++)
		{
			//返回该线程的字符串表示形式,包括线程名称、优先级和线程组
			System.out.println(Thread.currentThread().toString()+"....."+x);
			Thread.yield();//调用yield()方法释放执行权
		}
	}
}


class  JoinDemo
{
	public static void main(String[] args) throws Exception
	{
		Demo d = new Demo();
		Thread t1 = new Thread(d);//创建线程
		Thread t2 = new Thread(d);
		//t1.join();//这里会有一个异常,可以try或抛
		t1.start();
		
		//t1.setPriority(Thread.MAX_PRIORITY);//定义线程优先级
		t2.start();
		for(int x=0; x<80; x++)
		{
			//System.out.println("main....."+x);
		}
		System.out.println("over");
	}
}


 

join方法:等待该线程终止。

特点:当A线程执行到了B线程的.join()方法时,A线程就会等待。等B线程都执行完,A线程才会执行。Join方法可以用来临时加入线程执行。

 

yield方法:暂停当前正在执行的线程对象,并执行其他线程。

特点:线程一读到Thread.yield();就会释放执行权,临时释放用到。可以稍微减缓一下线程的运行。

 

优先级

setPriority():更改线程的优先级。

优先级是110

默认优先级是:5

 

MAX_PRIORITY:线程可以具有的最高优先级。10

MIN_PRIORITY:线程可以具有的最低优先级。1

NORM_PRIORITY:分配给线程的默认优先级。5

注意:凡是数据是固定的就定义成常量,凡是数据是共享的就定义成静态。

 

生产者消费者示例:

class XianCheng1 {
	public static void main(String[] args) {
		Ress r = new Ress();
		/*
		new hread(new Sheng(r)).start();//创建线程并启动线程
		new Thread(new Xiao(r)).start();
		*/
Sheng s = new Sheng(r);
		Xiao x = new Xiao(r);

		Thread t1 = new Thread(s); //创建线程
		Thread t2 = new Thread(s); 
		Thread t3 = new Thread(x); 
		Thread t4 = new Thread(x); 

		t1.start();//启动线程<p align="left"><span style="color:black;"><span style="font-family:Times New Roman;">             		t2.start();</span></span></p><p align="left"><span style="color:black;"><span style="font-family:Times New Roman;">		t3.start();</span></span></p><p align="left"><span style="color:black;"><span style="font-family:Times New Roman;">		t4.start();</span></span></p>	}
}

class Ress{
	private String name;
	private int count = 1;
	private boolean fa = false;
	
	public synchronized void set(String name){
		while(fa)
			try {
				wait();//等待
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		this.name = name+"---"+count++;//让count自增
		System.out.println(Thread.currentThread().getName()+"---生产者--"+this.name);
		fa = true;
		notifyAll();//唤醒所有
	}
	
	public synchronized void out(){
		while(!fa)
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		System.out.println(Thread.currentThread().getName()+"----------消费者--"+this.name);
		fa = false;
		notifyAll();
	}
}

class Sheng implements Runnable{
	private Ress r;//定义对象引用
	Sheng(Ress r){
		this.r = r;
	}
	public void run() {
		while(true)
			r.set("==商品==");
	}
}

class Xiao implements Runnable{
	private Ress r;
	Xiao(Ress r){
		this.r = r;
	}
	public void run() {
		while(true)
			r.out();
	}
}

对于这个示例我们不可以定义if语句了只能定义while语句,为什么呢?

因为是多个消费者和多个生产者,需要让被唤醒的线程再一次判断标记。

为什么定义notifyAll()

因为需要唤醒对方线程。只用notify,容易出现只唤醒本方线程的情况。导致程序中的所有线程都等待。

if语句只适用在只有一个生成者和消费者的时候。

 

这个问题怎么解决呢?

JDK1.5中提供了多线程升级解决方案

将同步synchronized替换成显示了Lock操作。

Object中的wait,notify,notifyAll,替换成了Condition对象。

该对象可以将Lock锁进行获取。

该示例中,实现了本方只唤醒对方的操作。

新特性示例:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//一定要导包,否则有错误
class XianCheng2 {
	public static void main(String[] args) {
		Ress1 r = new Ress1();

		Sheng1 s = new Sheng1(r);
		Xiao1 x = new Xiao1(r);

		Thread t1 = new Thread(s); //创建线程
		Thread t2 = new Thread(s); 
		Thread t3 = new Thread(x); 
		Thread t4 = new Thread(x); 

		t1.start();//启动线程
		t2.start();
		t3.start();
		t4.start();

	}
}

class Ress1 {
	private String name;
	private int count = 1;
	private boolean fa = false;

	private Lock lock = new ReentrantLock();//创建Lock的实现类对象。
	private Condition condition_sheng = lock.newCondition();//新Condition实例。
	private Condition condition_xiao = lock.newCondition();

	public void set(String name) throws InterruptedException {
		lock.lock();//获取锁
			try {
				while (fa)
					condition_sheng.await();//生产者等待
				this.name = name + "---" + count++;
				System.out.println(Thread.currentThread().getName()+ "---生产者--" + this.name);
				fa = true;
				condition_xiao.signal();//唤醒消费者
				} finally {//一定执行语句
					lock.unlock();//释放锁
				}
	}

	public void out() throws InterruptedException {
		lock.lock();
		
			try {
				while (!fa)
					condition_xiao.await();
				System.out.println(Thread.currentThread().getName()+ "----------消费者--" + this.name);
				fa = false;
				condition_sheng.signal();
			} finally {
				lock.unlock();
			}
	}
}

class Sheng1 implements Runnable {
	private Ress1 r;
	Sheng1(Ress1 r) {
		this.r = r;
	}
	public void run() {
		while (true)
			try {
				r.set("==商品==");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
	}
}

class Xiao1 implements Runnable {
	private Ress1 r;
	Xiao1(Ress1 r) {
		this.r = r;
	}

	public void run() {
		while (true)
			try {
				r.out();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
	}
}

Lock:它是一个接口,实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操

作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的Condition对象。

ReentrantLock:是Lock的实现类。

 

Lock:替代了synchronized方法和语句的使用。

       lock ():获取锁。

       unlock():释放锁。

       newCondition():返回绑定到此Lock实例的新Condition实例。

Condition:替代了Object监视器方法的使用(wait notify notifyAll),它也是一个接口。

       await();等待,会抛异常。

       signal();唤醒一个等待线程。

       signalAll();唤醒所有等待线程。

注意:要把lock.unlock();放到finally里,释放锁的资源。

新特性好处:一个锁上可以有多个相关的Condition

总结:

       run方法是将自定义代码存储的地方,不可以直接调用。

       我们在开发时建议使用创建线程的实现Runnable接口方法,因为Java里面只允许单一继承,但允许实现多个接口。实现Runnable接口方法更加灵活。

      多线程的安全问题与多线程的随机性有关,解决方法就是同步。

      线程在我们不自定义起名时有默认的名称,Thread-编号,该编号从0开始。

      finally里一般是用来释放资源的,join就是在要主线程的CPU执行权。Join有加入的意思。

      等待和唤醒必须是同一个锁。



 


 


 

 

 

 

 

 


 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值