黑马程序员----多线程



------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流! -------

多线程:

进程 是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以有多个线程。比如在Windows系统中,一个运行的xx.exe就是一个进程。

Java程序的进程里有几个线程:主线程垃圾回收线程(后台线程)

线程是指进程中的一个执行任务(控制单元),一个进程中可以运行多个线程,多个线程可共享数据。

多进程:操作系统中同时运行的多个程序;

多线程:在同一个进程中同时运行的多个任务;

一个进程至少有一个线程,为了提高效率,可以在一个进程中开启多个控制单元。  

并发运行。如:多线程下载软件。  

可以完成同时运行,但是通过程序运行的结果发现,虽然同时运行,但是每一次结果都不一致。  

因为多线程存在一个特性:随机性。  

造成的原因:CPU在瞬间不断切换去处理各个线程而导致的。  

可以理解成多个线程在抢cpu资源。  

 


多线程下载:此时线程可以理解为下载的通道,一个线程就是一个文件的下载通道,多线程也就是同时开起好几个下载通道.当服务器提供下载服务时,使用下载者是共享带宽的,在优先级相同的情况下,总服务器会对总下载线程进行平均分配。不难理解,如果你线程多的话,那下载的越快。现流行的下载软件都支持多线程。

 

多线程是为了同步完成多项任务,不是为了提供运行效率,通过提高资源使用效率来提高系统的效率.

线程是在同一时间需要完成多项任务的时候实现的.

 

 

线程与进程的比较

 

线程具有许多传统进程所具有的特征,故又称为轻型进程(LightWeight Process)或进程元;而把传统的进程称为重型进程(HeavyWeight Process),它相当于只有一个线程的任务。在引入了线程的操作系统中,通常一个进程都有若干个线程,至少需要一个线程。

 

进程与线程的区别:

1.进程有独立的进程空间,进程中的数据存放空间(堆空间和栈空间)是独立的。

2.线程的堆空间是共享的,栈空间是独立的,线程消耗的资源也比进程小,相互之间可以影响的。


1继承Thread

子类覆写父类中的run方法,将线程运行的代码存放在run中。

建立子类对象的同时线程也被创建。

通过调用start方法开启线程。

 

2实现Runnable接口

子类覆盖接口中的run方法。

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

Thread类对象调用start方法开启线程。

可使用匿名内部类来写

 

Eg

package july7;

//线程的两种方法

<span style="font-size:18px;">class MyThread extends Thread{
<span style="white-space:pre">	</span>private String name;
 
<span style="white-space:pre">		</span>public MyThread(String name) {
<span style="white-space:pre">		</span>super();
<span style="white-space:pre">		</span>this.name = name;
<span style="white-space:pre">	</span>}
<span style="white-space:pre">	</span>public void run(){
<span style="white-space:pre">		</span>System.out.println(name+"启动!");
<span style="white-space:pre">	</span>}
}
 
class YourThread implements Runnable{
<span style="white-space:pre">	</span>private String name;
<span style="white-space:pre">	</span>public YourThread(String name) {
<span style="white-space:pre">		</span>this.name = name;
<span style="white-space:pre">	</span>}
<span style="white-space:pre">	</span>public void run() {
<span style="white-space:pre">		</span>for (int i = 0; i < 3; i++) {
<span style="white-space:pre">		</span>System.out.println(Thread.currentThread().getName()+"第"+i+"次启动!");
<span style="white-space:pre">	</span>}
}

 
public class Demo1 {
<span style="white-space:pre">	</span>public static void main(String[] args) {
<span style="white-space:pre">		</span>for (int i = 0; i < 100; i++) {
<span style="white-space:pre">			</span>if(i == 50){
<span style="white-space:pre">				</span>new MyThread("刘昭").start();
<span style="white-space:pre">				</span>new Thread(new YourThread(""),"章泽天").start();
<span style="white-space:pre">			</span>}
<span style="white-space:pre">		</span>}
<span style="white-space:pre">	</span>}
}</span>

Thread类中run()start()方法的区别如下:
run()方法:在本线程内调用该Runnable对象的run()方法,可以重复多次调用;
start()方法:启动一个线程,调用该Runnable对象的run()方法,不能多次启动一个
线程;

种进程创建方式比较

A extends Thread

简单

不能再继承其他类了(Java单继承)

同份资源不共享

A implements Runnable:(推荐)

多个线程共享一个目标资源,适合多线程处理同一份资源。

该类还可以继承其他类,也可以实现其他接口。

 

我的总结:

实现方式,因为避免了单继承的局限性,所以创建线程建议使用第二种方式。

Eg

 

package july7;

//线程卖票的例子

 

<span style="font-size:18px;">class SellTicket extends Thread{
<span style="white-space:pre">	</span>private String name;
<span style="white-space:pre">	</span>private int num = 50;
<span style="white-space:pre">	</span>public SellTicket(String name) {
<span style="white-space:pre">		</span>super();
<span style="white-space:pre">		</span>this.name = name;
<span style="white-space:pre">	</span>}
<span style="white-space:pre">	</span>public void run(){
<span style="white-space:pre">		</span>for (int i = 1; i <= num; i++) {
<span style="white-space:pre">			</span>System.out.println(name+"卖出了第"+i+"张票!");
<span style="white-space:pre">		</span>}
<span style="white-space:pre">	</span>}
}
 
class MySell implements Runnable{
<span style="white-space:pre">	</span>private int num = 50;
<span style="white-space:pre">	</span>public void run() {
<span style="white-space:pre">		</span>for (int i = 1; i <= num; i++) {
<span style="white-space:pre">			</span>System.out.println(Thread.currentThread().getName()+"卖出了第"+i+"张票!");
<span style="white-space:pre">		</span>}
<span style="white-space:pre">	</span>}
}
 
public class Demo2 {
<span style="white-space:pre">	</span>public static void main(String[] args) throws Exception {
<span style="white-space:pre">		</span>new SellTicket("A").start();
<span style="white-space:pre">		</span>new SellTicket("B").start();
<span style="white-space:pre">		</span>new SellTicket("C").start();
<span style="white-space:pre">		</span>new Thread(new MySell(),"D").start();
<span style="white-space:pre">		</span>new Thread(new MySell(),"E").start();
<span style="white-space:pre">		</span>new Thread(new MySell(),"F").start();
<span style="white-space:pre">		</span>for (int i = 10; i > 0; i--) {
<span style="white-space:pre">			</span>System.out.println(i);
<span style="white-space:pre">			</span>Thread.sleep(1000);
<span style="white-space:pre">		</span>}
<span style="white-space:pre">	</span>}
}</span>


 

我的总结:

为什么要覆盖run方法呢?

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

也就是说Thread类中的run方法,用于存储线程要运行的代码。

线程的生命周期

Thread类内部有个public的枚举Thread.State,里边将线程的状态分为:
     
NEW-------新建状态,至今尚未启动的线程处于这种状态。
     
RUNNABLE-------运行状态,正在 Java 虚拟机中执行的线程处于这种状态。
     
BLOCKED-------阻塞状态,受阻塞并等待某个监视器锁的线程处于这种状态。
     
WAITING-------冻结状态,无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。
     
TIMED_WAITING-------等待状态,等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。
     
TERMINATED-------已退出的线程处于这种状态。

如何停止线程?

只有一种,run方法结束。开启多线程运行,运行代码通常是循环结构。只要控制住循环,就可以让run方法结束,也就是线程结束。

控制线程

 join方法:调用join方法的线程对象强制运行,该线程强制运行期间,其他线程无法运行,必须等到该线程结束后其他线程才可以行。

有人也把这种方式成为联合线程

 

join方法的重载方法:

join(long millis):

join(long millis,int nanos):

通常很少使用第三个方法:

程序无须精确到一纳秒;

计算机硬件和操作系统也无法精确到一纳秒;

 

Eg

package july7;

 

<span style="font-size:18px;">class MyThreadDemo implements Runnable{
<span style="white-space:pre">	</span>public void run() {
<span style="white-space:pre">		</span>for (int i = 0; i < 50; i++) {
<span style="white-space:pre">		</span>System.out.println(Thread.currentThread().getName()+"正在运行!"+i);
<span style="white-space:pre">			</span>if(i == 25){
<span style="white-space:pre">				</span>try {
<span style="white-space:pre">					</span>new Thread(new MyThreadDemo(),"刘昭").join();
<span style="white-space:pre">				</span>} catch (InterruptedException e) {
<span style="white-space:pre">					</span>e.printStackTrace();
<span style="white-space:pre">				</span>}
<span style="white-space:pre">			</span>}
<span style="white-space:pre">		</span>}
<span style="white-space:pre">	</span>}
}
 
public class DemoRe10 {
<span style="white-space:pre">	</span>public static void main(String[] args) {
<span style="white-space:pre">		</span>new Thread(new MyThreadDemo(),"刘昭").start();
<span style="white-space:pre">	</span>new Thread(new MyThreadDemo(),"章泽天").start();
<span style="white-space:pre">	</span>}
}</span>
Daemon

后台线程:处于后台运行,任务是为其他线程提供服务。也称为“守护线程”或“精灵线程”。JVM的垃圾回收就是典型的后台线程。

特点:若所有的前台线程都死亡,后台线程自动死亡。

设置后台线程:Thread对象setDaemon(true);

setDaemon(true)必须在start()调用前。否则出现IllegalThreadStateException异常;

前台线程创建的线程默认是前台线程;

判断是否是后台线程:使用Thread对象的isDaemon()方法;

并且当且仅当创建线程是后台线程时,新线程才是后台线程。 

sleep

线程休眠:

让执行的线程暂停一段时间,进入阻塞状态。

sleep(long milllis) throws InterruptedException:毫秒

sleep(long millis,int nanos) 

throws  InterruptedException:毫秒,纳秒

调用sleep()后,在指定时间段之内,该线程不会获得执行的机会。

控制线程之优先级

每个线程都有优先级,优先级的高低只和线程获得执行机会的次数多少有关。

并非线程优先级越高的就一定先执行,哪个线程的先运行取决于CPU的调度;

默认情况下main线程具有普通的优先级,而它创建的线程也具有普通优先级。

Thread对象的setPriority(int x)getPriority()来设置和获得优先级。

MAX_PRIORITY : 值是10

MIN_PRIORITY : 值是1

NORM_PRIORITY : 值是5(主方法默认优先级)

yield

线程礼让:

暂停当前正在执行的线程对象,并执行其他线程;

Thread的静态方法,可以是当前线程暂停,但是不会阻塞该线程,而是进入就绪状态。所以完全有可能:某个线程调用了yield()之后,线程调度器又把他调度出来重新执行。

 

我的总结:用到时查询api

线程安全问题

导致安全问题的出现的原因:

多个线程访问出现延迟。

线程随机性。

注:线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。

我们可以通过Thread.sleep(long time)方法来简单模拟延迟情况。

当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。

解决办法:

对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。

Eg:在前面的卖票例子上,在每卖票的前面加上模拟延时的语句!

package july7;

 

<span style="font-size:18px;">class SellDemo implements Runnable{
<span style="white-space:pre">	</span>private int num = 50;
<span style="white-space:pre">	</span>public void run() {
<span style="white-space:pre">		</span>for (int i = 0; i < 200; i++) {
<span style="white-space:pre">			</span>if(num > 0){
<span style="white-space:pre">				</span>try {
<span style="white-space:pre">				</span>//因为它不可以直接调用getName()方法,所以必须要获取当前线程。
<span style="white-space:pre">					</span>Thread.sleep(10);
<span style="white-space:pre">				</span>} catch (InterruptedException e) {
<span style="white-space:pre">					</span>e.printStackTrace();
<span style="white-space:pre">				</span>}
<span style="white-space:pre">			</span>System.out.println(Thread.currentThread().getName()+"卖出第"+num--+"张票!");
<span style="white-space:pre">			</span>}
<span style="white-space:pre">		</span>}
<span style="white-space:pre">	</span>}
}
 
public class Demo3 {
<span style="white-space:pre">	</span>public static void main(String[] args) {
<span style="white-space:pre">		</span>SellDemo s = new SellDemo();
<span style="white-space:pre">		</span>new Thread(s,"A").start();
<span style="white-space:pre">		</span>new Thread(s,"B").start();
<span style="white-space:pre">		</span>new Thread(s,"C").start();
<span style="white-space:pre">	</span>}
}</span>


 

输出:这样的话,会出现买了第0,甚至-1张票的情况!

线程安全问题的解决方法

 三种方法:

同步代码块:

synchronized(obj)

{

//obj表示同步监视器,是同一个同步对象

/**.....

TODO SOMETHING

*/

}

 

同步方法

格式:

在方法上加上synchronized修饰符即可。(一般不直接在run方法上加!

 

synchronized 返回值类型 方法名(参数列表)

{

/**.....

TODO SOMETHING

*/

}

同步方法的同步监听器其实的是 this

 

静态方法的同步

 

同步方法

同步代码块

static不能和 this连用

 

静态方法的默认同步锁是当前方法所在类的.class对象

 

同步锁

jkd1.5后的另一种同步机制:

通过显示定义同步锁对象来实现同步,这种机制,同步锁应该使用Lock对象充当。

在实现线程安全控制中,通常使用ReentrantLock(可重入锁)。使用该对象可以显示地加锁和解锁。

具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。

<span style="font-size:18px;">public class X {
<span style="white-space:pre">	</span>private final ReentrantLock lock = new ReentrantLock();
<span style="white-space:pre">		</span>//定义需要保证线程安全的方法
<span style="white-space:pre">	</span>public void  m(){
<span style="white-space:pre">	</span>	//加锁
<span style="white-space:pre">	</span>	lock.lock();
	<span style="white-space:pre">	</span>try{
	<span style="white-space:pre">	</span>	//... method body
<span style="white-space:pre">	</span>	}finally{
	<span style="white-space:pre">	</span>	//在finally释放锁
<span style="white-space:pre">	</span>	lock.unlock();
<span style="white-space:pre">	</span>	}
<span style="white-space:pre">	</span>}
}
 
修改后的例子:
//同步代码块
package july7;
 
class SellDemo implements Runnable{
<span style="white-space:pre">	</span>private int num = 50;
<span style="white-space:pre">	</span>public void run() {
<span style="white-space:pre">		</span>for (int i = 0; i < 200; i++) {
<span style="white-space:pre">			</span>synchronized (this) {
<span style="white-space:pre">				</span>if(num > 0){
<span style="white-space:pre">					</span>try {
<span style="white-space:pre">					</span>//因为它不可以直接调用getName()方法,所以必须要获取当前线程。
<span style="white-space:pre">					</span>Thread.sleep(10);
<span style="white-space:pre">					</span>} catch (InterruptedException e) {
<span style="white-space:pre">						</span>e.printStackTrace();
<span style="white-space:pre">					</span>}
<span style="white-space:pre">					</span>System.out.println(Thread.currentThread().getName()+"卖出第"+num--+"张票!");
<span style="white-space:pre">				</span>}<span style="white-space:pre">	</span>
<span style="white-space:pre">			
<span style="white-space:pre">		
<span style="white-space:pre">	</span>}
}
 
public class Demo3 {
<span style="white-space:pre">	</span>public static void main(String[] args) {
<span style="white-space:pre">		</span>SellDemo s = new SellDemo();
<span style="white-space:pre">		</span>new Thread(s,"A").start();
<span style="white-space:pre">		</span>new Thread(s,"B").start();
<span style="white-space:pre">		</span>new Thread(s,"C").start();
<span style="white-space:pre">	</span>}
}</span>


//同步方法

package july7;

 

//同步方法

 

<span style="font-size:18px;">class FinalDemo1 implements Runnable {
<span style="white-space:pre">	</span>private int num = 50;
 
<span style="white-space:pre">	</span>public void run() {
<span style="white-space:pre">		</span>for (int i = 0; i < 100; i++) {
<span style="white-space:pre">		</span>gen();
<span style="white-space:pre">	</span>}
}
 
public synchronized void gen() {
<span style="white-space:pre">	</span>for (int i = 0; i < 100; i++) {
<span style="white-space:pre">		</span>if (num > 0) {
<span style="white-space:pre">			</span>try {
<span style="white-space:pre">				</span>Thread.sleep(10);
<span style="white-space:pre">			</span>} catch (InterruptedException e) {
<span style="white-space:pre">				</span>e.printStackTrace();
<span style="white-space:pre">			</span>}
<span style="white-space:pre">			</span>System.out.println(Thread.currentThread().getName() + "卖出了第"+ num-- + "张票!");
<span style="white-space:pre">		</span>}
<span style="white-space:pre">	</span>}
}
}
 
public class Demo6 {
<span style="white-space:pre">	</span>public static void main(String[] args) {
<span style="white-space:pre">	</span>FinalDemo1 f = new FinalDemo1();
 
<span style="white-space:pre">	</span>new Thread(f, "A").start();
<span style="white-space:pre">	</span>new Thread(f, "B").start();
<span style="white-space:pre">	</span>new Thread(f, "C").start();
 
<span style="white-space:pre">	</span>}
}
 
 
//线程同步锁
package july7;
 
import java.util.concurrent.locks.ReentrantLock;
 
//同步锁
 
class FinalDemo2 implements Runnable {
private int num = 50;
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
for (int i = 0; i < 100; i++) {
gen();
}
}
 
public void gen() {
lock.lock();
try{
//for (int i = 0; i < 100; i++) {
if (num > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出了第"
+ num-- + "张票!");
}
//}
}finally{
lock.unlock();
}
}
}
 
public class Demo7 {
public static void main(String[] args) {
FinalDemo2 f = new FinalDemo2();
 
new Thread(f, "A").start();
new Thread(f, "B").start();
new Thread(f, "C").start();
 
}
}</span>

线程通信

 

有一个数据存储空间,划分为两部分,一部分用于存储人的姓名,另一部分用于存储人的性别;

我们的应用包含两个线程,一个线程不停向数据存储空间添加数据(生产者),另一个线程从数据空间取出数据(消费者);

因为线程的不确定性,存在于以下两种情况:

若生产者线程刚向存储空间添加了人的姓名还没添加人的性别,CPU就切换到了消费者线程,消费者线程把姓名和上一个人的性别联系到一起;

生产者放了若干数据,消费者才开始取数据,或者是消费者取完一个数据,还没等到生产者放入新的数据,又重复的取出已取过的数据;

 

生产者和消费者

 

wait():让当前线程放弃监视器进入等待,直到其他线程调用同一个监视器并调用notify()notifyAll()为止。

notify():唤醒在同一对象监听器中调用wait方法的第一个线程。

notifyAll():唤醒在同一对象监听器中调用wait方法的所有线程。

 

这三个方法只能让同步监听器调用:

在同步方法中: 谁调用

在同步代码块中: 谁调用

 

wait()notify()notifyAll(),这三个方法属于Object 不属于 Thread,这三个方法必须由同步监视对象来调用,两种情况:

1.synchronized修饰的方法,因为该类的默认实例(this)就是同步监视器,所以可以在同步方法中调用这三个方法;

2.synchronized修饰的同步代码块,同步监视器是括号里的对象,所以必须使用该对象调用这三个方法;

可要是我们使用的是Lock对象来保证同步的,系统中不存在隐式的同步监视器对象,那么就不能使用者三个方法了,那该咋办呢?

此时,Lock代替了同步方法或同步代码块,Condition代替了同步监视器的功能;

Condition对象通过Lock对象的newCondition()方法创建;

里面方法包括:

await():  等价于同步监听器的wait()方法;

signal(): 等价于同步监听器的notify()方法;

signalAll(): 等价于同步监听器的notifyAll()方法;


线程组:  它是线程的集合(包含对象线程对象)
 
 线程组的使用:
  1: 获取线程所在的组:
    getThreadGroup();
  2:  设置线程 所在的组:
    通过Thread类的构造方法来完成

 线程池:(线程的容器,可以有多个线程对象)
 线程池的使用:
  1: 创建线程池对象
   a: 创建一个具备缓冲功能的线程池
    public static ExecutorService newCachedThreadPool()
   b: 创建一个可重用固定线程数的线程池
    public static ExecutorService newFixedThreadPool(int nThreads)
   c: 创建具备一个线程对象的线程池
    public static ExecutorService newSingleThreadExecutor()
  
  2: 创建Runnable [ run() ] 接口的类对象, 或者创建一个Callable[ call() ]接口类的对象
  
  3: 提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future
   Future<?> submit(Runnable task)
  
     提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future
   <T> Future<T> submit(Callable<T> task)
   
   理解为: 获取线程池中的线程对象, 让获取到的线程对象,执行Runnable中的run()方法
      获取线程池中的线程对象, 让获取到的线程对象,执行Callable中的call()方法
   
  4: 关闭线程池
   void shutdown()
    
 匿名内部类的方式实现多线程程序:
   
  格式1:
   new Thread(){
    run(){.....};
   }.start();
   
  格式2:
   new Thread(
       new Runnable(){
     run(){.....}
    }
   ).start();

 定时器:

  Timer: 定时器类
   作用: 创建一个定时器对象, 在指定的时间 执行指定任务
   
   public void schedule(TimerTask task,  Date time)
   public void schedule(TimerTask task,  long delay)
   public void schedule(TimerTask task,  long delay, long period)
   
  TimerTask: 定时器任务类
   作用: 通过Timer对象, 来调用当前对象中的方法run()


黑马程序员多线程练习题主要包括两个问题。第一个问题是如何控制四个线程在打印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、付费专栏及课程。

余额充值