Thread线程的讲解(单线程、多线程、多任务、比较、用法、同步、线程安全)

多线程:

多线程则指的是在单个程序中可以同时运行多个不同的线程执行不同的任务.线程是程序内的顺序控制流,只能使用分配给程序的资源和环境。多线程编程的目的,就是"最大限度地利用CPU资源",当某一线程的处理不需要占用CPU而只和I/O等资源打交道时,让需要占用CPU资源的其它线程有机会获得CPU资源。从根本上说,这就是多线程编程的最终目的。

一个进程可以包含一个或多个线程

一个程序实现多个代码同时交替运行就需要产生多个线程

CPU随机的抽出时间,让我们的程序一会做这件事情,一会做另外一件事情

单线程:

当程序启动运行时,就自动产生一个线程,主方法main就在这个主线程上运行

多任务:多任务处理被所有的现代操作系统所支持。然而,多任务处理有两种截然不同的类型:基于进程的和基于线程的;

1、基于进程的多任务处理是更熟悉的形式。进程(process)本质上是一个执行的程序。因此基于进程的多任务处理的特点是允许你的计算机同时运行两个或更多的程序。举例来说,基于进程的多任务处理使你在运用文本编辑器的时候可以同时运行Java编译器。在基于进程的多任务处理中,程序是调度程序所分派的最小代码单位。

2、基于线程(thread-based)的多任务处理环境中,线程是最小的执行单位。这意味着一个程序可以同时执行两个或者多个任务的功能。例如,一个文本编辑器可以在打印的同时格式化文本。

首先分析下为什么要使用多线程:如果是一个单线程的,那么一个线程在执行的过程中,如果此时他只是在等待数据,cpu并没有在处理,那么这个时候就浪费了cpu。总的来说单线程因为等待资源师阻塞(block,挂起执行),整个程序停止执行。多线程则是,一个线程挂起,cpu资源不会浪费。其他线程还能运行。这样使得整个程序不会停止运行。

线程和进程的区别:

1、多个进程的内部数据和状态都是完全独立的,而多线程是共享一块内存空间和一组系统资源,有可能互相影响;

2、线程本身的数据通常只有寄存器数据,以及一个程序执行时使用的堆栈,所以线程的切换比进程切换的负担要小;

3、多线程程序比多进程程序需要更少的管理费用。进程是重量级的任务,需要分配给它们独立的地址空间。进程间通信是昂贵和受限的。进程间的转换也是很需要花费的。另一方面,线程是轻量级的选手。它们共享相同的地址空间并且共同分享同一个进程。线程间通信是便宜的,线程间的转换也是低成本的。

4、多线程可帮助你编写出CPU最大利用率的高效程序,使得空闲时间保持最低。这对Java运行的交互式的网络互连环境是至关重要的,因为空闲时间是公共的。例如,网络的数据传输速率远低于计算机处理能力,而本地文件系统资源的读写速度也远低于CPU的处理能力。当然,用户输入也比计算机慢很多。在传统的单线程环境中,程序必须等待每一个这样的任务完成以后才能执行下一步—尽管CPU有很多空闲时间。多线程使你能够获得并充分利用这些空闲时间。

多线程的优势:

1、Java运行系统在很多方面依赖于线程,所有的类库设计都考虑到多线程。实际上,Java使用线程来使整个环境异步。这有利于通过防止CPU循环的浪费来减少无效部分。

2、为更好地理解多线程环境的优势,我们可以将它与它的对照物相比较。单线程系统的处理途径是使用一种叫作轮询的事件循环方法。在该模型中,单线程控制在一无限循环中运行,轮询一个事件序列来决定下一步做什么。一旦轮询装置返回信号表明已准备好读取网络文件,事件循环调度控制管理到适当的事件处理程序。直到事件处理程序返回,系统中没有其他事件发生。这就浪费了CPU时间。这导致了程序的一部分独占了系统,阻止了其他事件的执行。总的来说,单线程环境,当一个线程因为等待资源时阻塞(block,挂起执行),整个程序停止运行。

线程的实现:

在Java中通过run方法为线程指明要完成的任务,有两种技术来为线程提供run方法。1.继承Thread类并重写run方法。2.通过定义实现Runnable接口的类进而实现run方法。

总结:两种方法均需执行线程的start方法为线程分配必须的系统资源、调度线程运行并执行线程的run方法。在具体应用中,采用哪种方法来构造线程体要视情况而定。通常,当一个线程已继承了另一个类时,就应该用第二种方法来构造,即实现Runnable接口。线程的消亡不能通过调用一个stop()命令。而是让run()方法自然结束。

线程的生命周期:一个线程从创建到消亡的过程。线程的生命周期可分为四个状态:1.创建状态2.可运行状态3.不可运行状态

4. 消亡状态

1.创建状态•当用new操作符创建一个新的线程对象时,该线程处于创建状态。•处于创建状态的线程只是一个空的线程对象,系统不为它分配资源

2.可运行状态•执行线程的start()方法将为线程分配必须的系统资源,安排其运行,并调用线程体—run()方法,这样就使得该线程处于可运行( Runnable )状态。

•这一状态并不是运行中状态(Running ),因为线程也许实际上并未真正运行。

3.不可运行状态当发生下列事件时,处于运行状态的线程会转入到不可运行状态。•调用了sleep()方法;•线程调用wait方法等待特定条件的满足

•线程输入/输出阻塞

返回可运行状态:•处于睡眠状态的线程在指定的时间过去后•如果线程在等待某一条件,另一个对象必须通过notify()或notifyAll()方法通知等待线程条件的改变•如果线程是因为输入/输出阻塞,等待输入/输出完成

4. 消亡状态当线程的run方法执行结束后,该线程自然消亡。

线程的优先级

1. 线程的优先级及其设置设置优先级是为了在多线程环境中便于系统对线程的调度,优先级高的线程将优先执行。一个线程的优先级设置遵从以下原则:–线程创建时,子继承父的优先级–线程创建后,可通过调用setPriority()方法改变优先级。–线程的优先级是1-10之间的正整数。1 - MIN_PRIORITY,10 – MAX_PRIORITY

5- NORM_PRIORITY

2. 线程的调度策略线程调度器选择优先级最高的线程运行。但是,如果发生以下情况,就会终止线程的运行。•线程体中调用了yield()方法,让出了对CPU的占用权•线程体中调用了sleep()方法, 使线程进入睡眠状态•线程由于I/O操作而受阻塞•另一个更高优先级的线程出现。•在支持时间片的系统中,该线程的时间片用完。

多线程同步:

1、为什么要引入同步机制在多线程环境中,可能会有两个甚至更多的线程试图同时访问一个有限的资源。必须对这种潜在资源冲突进行预防。解决方法:在线程使用一个资源时为其加锁即可。访问资源的第一个线程为其加上锁以后,其他线程便不能再使用那个资源,除非被解锁。

2、怎样实现同步(第一种):对于访问某个关键共享资源的所有方法,都必须把它们设为synchronized例如:synchronized void f() { /* ... */ } synchronized void g() { /* ... */ }

如果想保护某些资源不被多个线程同时访问,可以强制通过synchronized方法访问那些资源。 调用synchronized方法时,对象就会被锁定。

说明:•当synchronized方法执行完或发生异常时,会自动释放锁。•被synchronized保护的数据应该是私有(private)的。

2、怎样实现同步(第二种):

线程间的相互作用:•wait and notify•The pools:– Wait pool– Lock pool

在java中上面提到有使用synchronized、代码块和wait、notify的方式:

我做了一个总结:

 没有static修饰有synchronized修饰: * 操作的是同一个对象example的情况下,只要访问的方法上面有synchronized,那么就会线程安全,同步执行 * 操作的是不同的对象(example、example2)的情况下,这个情况不管访问的是同一个方法,还是不同的方法,都不会同步执行。因为所在各自对象的上面。这个只会对不同线程访问同一对象起作用有static修饰有synchronized修饰:*操作的是同一对象example的情况下,只要访问的方法上面有synchronized、static上面修饰那么就是线程安全,同步执行

 * 如果操作的不是同一对象,只要访问的方法上面有synchronized、static上面修饰那么就是线程安全,同步执行,因为这个时候锁是加载了class对象上面。

那么我们列举在java中使用的线程安全和线程不安全的例子给解释下:

比如arrayList和Vector:arrayList是线程的安全的,Vector则是线程不安全的,因为你去查看Vector这个类。这个类中的好多方法都是加上了synchronized的,所以这样造成多个线程去处理这个vector的实例(对象)的时候,如果你作为一个线程add,他作为一个线程也add。add上面有synchronized修饰,你操作的时候这个对象就被锁起来了,而第二个线程去操作的时候就不能去add。那么这个就是线程安全。而arraylist则没有synchronized修饰。多个线程同时操作这个对象的时候,就会造成线程飞安全,比如两个线程同时add值。那么这个时候可能会造成最后只add了一个。arraylist中只有一个数,两个线程同时去删除,可能会造成都提示删除成功。还有就是多线程同时去访问一个方法的时候,在这个方法需要对一个static的变量加一万。这个时候就会造成3个线程每个线程加一万,结果变量小于三万,所以这个时候需要使用线程安全。

那么为什么vertor是线程安全的而我们还要用arraylist这个线程不安全的,首先第一点就是效率:arraylist的效率往往比vector的效率高很多。并且arraylist如果要线程安全可以使用外部实现线程安全。这个介意查看我的其他博客有介绍:

代码实例:

public class ThreadTest
{
	public static void main(String[] args)
	{
//1		Thread1 thread1 = new Thread1();
//		Thread1 thread2 = new Thread1();
//		thread1.start();
//		//必须用start()方法启动线程,这个方法在启动的时候就会自动分配资源给这个线程,然后调用run()
//		//方法
//		thread2.start();
		//这里虽然只有一个线程类,但是实例化两个对象,这样就变成了两个线程
		
	
		Thread1 thread1 = new Thread1("first thread");
		Thread2 thread2 = new Thread2("second thread");
		thread1.start();
		//必须用start()方法启动线程,这个方法在启动的时候就会自动分配资源给这个线程,然后调用run()
		//方法,因为这个计算机是4核的,可以同时处理4个线程,所以这两个线程可以同时运行,也可以单独运行
		//这个是预料不到的
		thread2.start();
//		thread1.run();
//		thread2.run();
		//这样就不会以线程的方式来启动,而是按照顺序来执行,这样就指定 了,先thread1的run然后是thread2的run
		
		
//3		Thread1 thread1 = new Thread1();
//		Thread2 thread2 = new Thread2();
//		thread1.start();
//		thread2.run();
	}
	

}
class Thread1 extends Thread
//继承了Thread类就是一个线程类,并且重写run()方法
{
	public Thread1(String name)
	//在调用构造方法的时候给线程赋上一个名字
	{
		super(name);
	}
	
	@Override
	public void run()
	{
		for(int i = 0; i < 100; i ++)
		{
			System.out.println("hello world:" + i);
		}
	}
}
class Thread2 extends Thread
	//继承了Thread类就是一个线程类
	{
		public Thread2(String name)
		{
			
		}
		@Override
		public void run()
		{
			for(int i = 0; i < 100; i ++)
			{
				System.out.println("welcome:" + i);
			}
		}
	}

使用匿名内部类:

public static void main(String[] args)
	{
		Thread t1 = new Thread(new Runnable() {
			//这个使用的就是匿名类,这个和观察模式还是有区别的,这个相当于是在构造方法中的那个参数去给定
			//一个接口或者类的对象,然后在重写西面的方法,则会自动调用这个run方法
			//Thread这个类实现了Runnable这个接口
			@Override
			public void run()
			{
				for(int i = 1; i < 100; i ++)
				{
					System.out.println("hello:" + i);
				}
			}
		});
		t1.start();
		Thread t2 = new Thread(new Runnable() {
			
			@Override
			public void run()
			{
				for(int i = 0; i <100; i ++)
				{
					System.out.println("welcome" + i);
				}
			}
		});
		t2.start();
	}

线程对于成员变量的访问:

public class ThreadTest4
{
	public static void main(String[] args)
	{
		Runnable runnable = new HelloThread();
		//会自动调用父类的构造方法
		Thread thread1 = new Thread(runnable);
		
		runnable = new HelloThread();
		Thread thread2 = new Thread(runnable);
		thread1.start();
		thread2.start();
	}

	

}
class HelloThread implements Runnable
{
//	int i = 0;//两个线程分别有一份
	static int i = 0;
	//两个线程使用的是一个i,这样两个线程是相互影响的
	@Override
	public void run()
	{
//		int i = 0;
		//两个线程使用人是不同的i,这个两个线程是互不影响的
		while(true)
		{
			System.out.println("number:" + i++);
			try
			{
				Thread.sleep((long)(Math.random() * 1000));
				//导致当前的线程会停止执行
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
			if(50 == i)
			{
				break;
			}
			
			
		}
	}
}

关于使用synchronized和static的应用实例:

public class ThreadTest5
{
	/*
	
	 * 
	 * 所以我吧这里所能出现的情况都列出来:
	 * 没有static修饰有synchronized修饰:
	 * 操作的是同一个对象example的情况下,只要访问的方法上面有synchronized,那么就会线程安全,同步执行
	 * 操作的是不同的对象(example、example2)的情况下,这个情况不管访问的是同一个方法,还是不同的方法,都不会同步执行。因为所在各自对象的上面。这个只会对不同线程访问同一对象起作用
	 * 有static修饰有synchronized修饰:
	 * 操作的是同一对象example的情况下,只要访问的方法上面有synchronized、static上面修饰那么就是线程安全,同步执行
	 * 如果操作的不是同一对象,只要访问的方法上面有synchronized、static上面修饰那么就是线程安全,同步执行,因为这个时候锁是加载了class对象上面。
	 */
	public static void main(String[] args)
	{
		Example example = new Example();
		Thread t1 = new TheThread(example);
		t1.start();
//		example = new Example();
		Thread t2 = new TheThread2(example);
		
		t2.start();
	}
}
class Example
{
	public synchronized  void example()
	//这个是给对象上锁,所以当一个执行的时候,整个对象就被锁住了
	{
		for(int i = 0; i < 20; i ++)
		{
			try
			{
				Thread.sleep(1000);
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
			System.out.println("example:" + i);
		}
		
	}
	public synchronized void example2()
	{
		for(int i = 0; i < 20; i ++)
		{
			try
			{
				Thread.sleep(1000);
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
			System.out.println("example2:" + i);
		}
		
	}
}
class TheThread extends Thread
{
	private Example example;
	public TheThread(Example example )
	{
		this.example = example;
	}
	@Override
	public void run()
	{
		this.example.example();
	}
}
class TheThread2 extends Thread
{
	private Example example;
	public TheThread2(Example example )
	{
		this.example = example;
	}
	@Override
	public void run()
	{
		this.example.example2();
	}
}

Synchronized代码块:

public class ThreadTest6
{
	public static void main(String[] args)
	{
		Example2 example2 = new Example2();
		TheThread3 theThread3 = new TheThread3(example2);
		
		TheThread4 theThread4 = new TheThread4(example2);
		
		theThread3.start();
		theThread4.start();
	}
}
class Example2
{
	private Object object = new Object();
	//这样就相当于锁上了一个相同的对象,这个就相当于是一个标识,所以定义成什么对象都可以
	public void example()
	{
		synchronized(object)
		//synchronized保护的数据是私有的
		{
			for(int i = 0; i < 20; i ++)
			{
				try
				{
					Thread.sleep(1000);
				}
				catch (InterruptedException e)
				{
					e.printStackTrace();
				}
				System.out.println("example:" + i);
			}
			
		}
		
		
	}
	
	public void example2()
	{
		synchronized(object)
		{
			for(int i = 0; i < 20; i ++)
			{
				try
				{
					Thread.sleep(1000);
				}
				catch (InterruptedException e)
				{
					e.printStackTrace();
				}
				System.out.println("example2:" + i);
			}	
		}
	}
}
class TheThread3 extends Thread
{
	private Example2 example;
	public TheThread3(Example2 example )
	{
		this.example = example;
	}
	@Override
	public void run()
	{
		this.example.example();
	}
}
class TheThread4 extends Thread
{
	private Example2 example;
	public TheThread4(Example2 example )
	{
		this.example = example;
	}
	@Override
	public void run()
	{
		this.example.example2();
	}
}

具体应用在银行取钱:

public class FetchMoney
{
	public static void main(String[] args)
	{
		Bank bank = new Bank();
		
		Thread t1 = new MoneyThread(bank);
		
		Thread t2 = new MoneyThread(bank);
		t1.start();
		t2.start();
	}

}
class Bank
{
	private int money = 1000;
	public  synchronized int getMoney(int number)
	//这样就不会同时又几个线程去访问这个资源
	{
		if(number < 0)
		{
			return -1;
		}
		else if(number > money)
		{
			return -2;
		}
		else if(money < 0)
		{
			return -3;
		}
		else
		{
			try
			{
				Thread.sleep(1000);
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
			money -= number;
			System.out.println("剩余多少钱:" + money);
			return number;
		}
	}
}
class MoneyThread extends Thread
{
	private Bank bank;
	public MoneyThread(Bank bank)
	{
		this.bank = bank;
	}
	@Override
	public void run()
	{
		System.out.println("您取了:" + bank.getMoney(800));
	}
	
}

使用wait和notify:

public class Sample
{
	private int number;
	public synchronized void increase()
	//执行加
	{
		while(0 != number)
		{
			try
			{
				wait();//此时的这个锁会释放,这个对象的锁就会释放(释放对象的锁),其他的线程就可以访问这个对象
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		}
		number ++;
		System.out.println(number);
		notify();
	}
	
	public synchronized void decrease()
	//执行减
	{
		while(0 == number)
		{
			try
			{
				wait();//释放锁
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		}
		number --;
		System.out.println(number);
		notify();//通知
		
	}
}
  notify()就是对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值