首页 > 代码库 > 多线程

多线程

1、线程的概念
? 多线程,就类似与操作系统中的多进程。简单的讲,就是可 以同时并发执行多个任务,处理多件事情。这与我们经常所 谓的边唱边跳,边说边做事一个道理。
? 线程是一个轻量级的进程,一个进程中可以分为多个线程。 比起进程,线程所耗费的系统资源更少,切换更加容易 

/*
 * 进程是操作系统中的一个任务,一个程序启动运行,就会创建
 * 一个(或多个)进程。
 * 线程是轻量级的进程。进程会有自己独立的内存空间与资源。一个进程
 * 下会存在一个(或多个)线程。线程为进程的执行单元。线程本身不含有
 * 独立的资源,而是共享进程中的资源。
 * 
 * 多线程的优势:
 * 1 使用多线程可以提高CPU的利用率。
 * 2 使用多线程可以提供更好的动态性与交互性。
 */
package day18;

public class HelloWorld {

	public static void main(String[] args) {
		// 无限循环,用来查看java程序的进程。
		// Java程序在运行时,就会存在一个进程。
		// 一个进程至少要包含一个线程,Java线程就是
		// 执行main方法的main线程(线程的名字叫做main)。
		while (true)
			;
	}
}

  2、Thread
? ThreadJava中的线程类,我们可以通过继承Thread类来实 现多线程操作。
? 继承Thread类,重写run方法, run方法中的代码即为我们需 要线程执行的任务,然后调用对象的start方法即可启动线程。
说明:
? 不是调用run方法,而是调用start方法,否则无法实现多线 程。
? 执行main方法就会创建一个线程。

/*
 * Thread类就表示线程。可以用来创建线程。
 * 通过继承Thread类实现多线程。
 */
package day18;

public class ThreadTest {
	public static void main(String[] args) {
		// 创建线程
		Mission1 m = new Mission1();
		Mission2 m2 = new Mission2();
		// 创建线程后,需要启动线程,线程才能够执行。
		// 调用start方法来启动一个线程,是线程处于就绪
		// 状态。线程处于就绪状态,不代表该线程会马上得到执行,
		// 具体何时执行,要取决于操作系统的调度。线程处于就绪
		// 状态,表示该线程有机会获得CPU的时间片,即有机会
		// 得到执行。
		m.start();
		m2.start();
	}
}

// run方法就是线程要执行的任务。
// 我们重写Thread类的run方法。
// 线程从run方法开始执行。
class Mission1 extends Thread {
	@Override
	public void run() {
		for (int i = 1; i <= 10; i++) {
			System.out.println(i);
			// 令当前线程休眠参数指定的时间(毫秒)
			// 处于休眠的线程会放弃掉当前的时间片,
			// 并且在整个休眠期间,不会获得CPU的时间片。
			// 当线程苏醒时,线程会处于就绪状态。(不是运行状态)。
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			// 获取线程的名字。
			System.out.println(getName());
		}
	}
}

class Mission2 extends Thread {
	@Override
	public void run() {
		for (int i = 11; i <= 20; i++) {
			System.out.println(i);
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

  3、Runnable接口
除了继承Thread类以外,还可以通过实现Runnable接口来实现 多线程。
? 首先,我们要创建一个类,该类实现Runnable接口, Runnable接口中存在一个抽象方法run,因此,我们需要实 现(重写) run方法。
? 然后创建该类的对象,将对象作为Thread的运行目标。 ( target)。 

/*
 * 通过实现Runnable接口实现多线程
 */
package day18;

public class RunnableTest {
	public static void main(String[] args) {
		Mission m = new Mission();
		// 使用实现Runnable接口的对象,作为
		// 线程的目标执行体。
		Thread t1 = new Thread(m);
		Thread t2 = new Thread(m);
		t1.start();
		t2.start();
	}
}

class Mission implements Runnable {
	@Override
	public void run() {
		for (int i = 1; i <= 10; i++) {
			System.out.println(i);
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		// 无法直接调用getName(),因为当前类没有继承Thread,
		// 不是Thread类的子类。
		// getName();
		// 获取当前的执行的线程。
		Thread current = Thread.currentThread();
		current.getName();
	}
}

  4、线程的生命周期
线程的生命周期可以分为以下环节:
? 新建-创建对象
? 就绪-调用start
? 运行-获得CPU资源
? 阻塞(挂起) -失去CPU资源
? 死亡-线程执行结束或抛出未捕获的异常

5、常用方法

public static Thread currentThread(); 获得当前执行的线程。
? public String getName() 获得线程名称。
? public final native boolean isAlive(); 判断线程是否存活
(在start方法调用后,并且线程没有死亡)。
? public void join() 等待直到这个线程死亡。
? public static void sleep(long millis) 使当前线程睡眠(暂时
停止执行) millis毫秒。如果当前程序存在其他等待的线程,
则其他线程会获得执行机会。
? public static void yield();当前运行的线程有意让出CPU资源,
由线程调度器重新选择线程调度。不过,这仅仅是一个提示
而已,线程调度器可能会忽略。
? public void setDaemon(boolean on);设置线程是否为后台
线程。
? public void setPriority(int newPriority)设置线程的优先级。
优先级为1-10。优先级高的线程仅意味着可能获得更多执行
的机会,不表示一定会一直执行,优先级低的线程仍然有机
会执行。

6、yield方法

package day18;

public class YieldTest {
	public static void main(String[] args) {
		Thread2 t = new Thread2();
		Thread3 t2 = new Thread3();
		t.start();
		t2.start();
	}
}

class Thread2 extends Thread {
	@Override
	public void run() {
		for (int i = 0; i <= 5; i++) {
			System.out.println(i);
		}
		// 当前线程有意让出CPU资源,让其他线程得到执行。
		// 但是,操作系统可能会忽略线程的这种请求。
		Thread.yield();
		for (int i = 6; i <= 9; i++) {
			System.out.println(i);
		}
	}
}

class Thread3 extends Thread {
	@Override
	public void run() {
		for (int i = 100; i < 110; i++) {
			System.out.println(i);
		}
	}
}

7、

package day18;

//执行main方法的线程名就叫main。
public class ThreadMethod {

	public static void main(String[] args) {
		// 获取当前执行的线程。
		Thread current = Thread.currentThread();
		// 获取线程的名字。
		// System.out.println(current.getName());
		// 判断当前线程是否处于存活状态(调用start方法之后,
		// 线程死亡之前),存活返回true,否则返回false。
		// System.out.println(current.isAlive());

		ThreadA ta = new ThreadA();
		ta.start();
		try {
			// 如果在A线程中调用B线程对象的join方法(无参),则A线程
			// 会一直等待B线程执行结束,A线程才会继续执行。
			ta.join();
			// 当前线程最多等待ta线程参数指定的时间。(毫秒)
			// ta.join(500);
			// 当前线程最多等待第一个参数的时间(毫秒)加上
			// 第二个参数的时间(纳秒)。(时间的和)
			// ta.join(500, 100);
			// 令当前线程休眠参数指定的时间。
			// Thread.sleep(500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("main方法输出内容");
		ThreadA ta2 = new ThreadA();
		// 设置当前线程是否为后台线程,true为后台线程,否则
		// 为前台线程。该方法需要在线程启动之前调用。
		ta2.setDaemon(true);
		// 设置线程的优先级。优先级高的线程不代表会一直执行,
		// 只是执行几率大于优先级低的线程。虽然Java提供了
		// 1-10这十个优先级,但是,操作系统未必会存在十个优先级
		// 有Java相对应。
		ta2.setPriority(Thread.NORM_PRIORITY);

	}
}

class ThreadA extends Thread {
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(i);
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

8、中断线程

package day18;

public class InterruptTest {
	public static void main(String[] args) {
		Thread4 t = new Thread4();
		t.start();
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		// 中断线程。
		t.interrupt();
	}
}

class Thread4 extends Thread {
	@Override
	public void run() {
		try {
			// 如果线程处于sleep,join等等待方法中,如果
			// 在等待期间,线程被其他线程所中断,则会产生
			// InterruptedException异常。如果没有处于
			// 以上等待方法中,则线程被其他线程所终端,不会
			// 产生InterruptedException异常。
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			System.out.println("产生了InterruptedException异常");
		}
	}
}

9、线程同步
当多线程并发运行时,多线程间很可能操作共享成员变量,此 时,就需要对共享成员变量的操作进行同步,避免出现多线程 的并发修改而引起的意外错误。 

当多线程并发修改公共变量时,我们就需要对公共变量的修改 部分进行同步,避免出现并发修改错误。 

线程同步可以使用:
? 同步块
? 同步方法

同步的代码在同一时刻,至多只会有一个线程执行。其使用线 程锁机制来保证。 

/*
 * 当多线程对共享变量并发进行修改时,就可能会出现
 * 并发修改的不一致性。
 * 对共享变量进行并发修改的区域,我们称之为共享区域。
 * 对于共享区域,我们必须要实现上锁(线程的互斥访问)。
 * 
 * 对于共享变量,要完全放在同步区域当中,否则就可能会出现
 * 问题。(存在修改共享变量)
 */
package day18;

public class BuyTicket {
	public static void main(String[] args) {
		Ticket ticket = new Ticket();
		Thread t1 = new Thread(ticket);
		Thread t2 = new Thread(ticket);
		Thread t3 = new Thread(ticket);
		// 设置线程的名字
		t1.setName("张三");
		t2.setName("李四");
		t3.setName("王五");
		t1.start();
		t2.start();
		t3.start();
	}
}

class Ticket implements Runnable {
	private int num = 100;
	// 任意对象都可以充当共享区域的锁。
	private Object lock = new Object();
	// 因为任意对象都可以充当锁的角色,因此,我们没有必要
	// 单独创建一个对象锁,使用当前对象this来充当锁就
	// 可以了。

	@Override
	public void run() {
		// while (num > 0) {
		// synchronized (lock) {
		// String name = Thread.currentThread().getName();
		// System.out.println(name + "抢到第" + num + "张票");
		// num--;
		// }
		// }
		// 虽然可以这样实现,但是这样做,完全退化成单线程。
		// synchronized (lock) {
		// while (num > 0) {
		// String name = Thread.currentThread().getName();
		// System.out.println(name + "抢到第" + num + "张票");
		// num--;
		// }
		// }
		while (true) {
			synchronized (this) {
				if (num > 0) {
					String name = Thread.currentThread().getName();
					System.out.println(name + "抢到第" + num + "张票");
					num--;
				} else {
					break;
				}
			}
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	/*
	 * 声明同步方法,使用synchronized修饰。同步方法的整个 方法体都是共享区域,都是互斥的进行访问的。
	 * 对于实例方法,使用当前对象this充当锁。 对于静态方法,使用当前类型的Class对象充当锁。
	 */
	public synchronized void f() {

	}

	public synchronized void g() {

	}

	public void k() {
		synchronized (this) {

		}
	}

	public void n() {
		synchronized (lock) {

		}
	}
}

  

package day18;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class BuyTicket2 {

	public static void main(String[] args) {

	}
}
/*
 * Lock与synchronized
 * Lock是JDK1.5新增的内容,更加贴近与面向对象化。
 * synchronized如果使用break,或者产生了一个未捕获的异常,
 * 同步区域的锁可以自动得到释放,但是Lock不能。为了确保Lock
 * 能够一定被解锁,将unlock方法写在finally语句块中。
 */

class Ticket2 implements Runnable {
	private int num = 100;
	private Lock lock = new ReentrantLock();

	@Override
	public void run() {
		while (true) {
			try {
				// 加锁
				lock.lock();
				if (num > 0) {
					// 输出一大堆
					num--;
				} else {
					break;
				}
			} finally {
				// 解锁,在finally中调用,确保锁能够
				// 被解开。
				lock.unlock();
			}
		}
	}
}

10、死锁
? 当两个或多个线程同时拥有自己的资源,而相互等待获得对 方资源,导致程序永远陷入僵持状态,这就是死锁。
? 当多线程并发访问共享数据时,使用同步操作可以避免多线 程并发修改带来的危害,但同时有可能会产生死锁。 

/*
 * 死锁
 * 死锁就是两个线程分别拥有自己的资源,而想要获得对方的资源,
 * 从而处于无限的僵持与等待之中。
 */
package day18;

public class DeadLock {
	public static void main(String[] args) {
		Fighter f1 = new Fighter("张三");
		Fighter f2 = new Fighter("李四");
		Thread t1 = new Thread(() -> {
			f1.hold(f2);
		});
		Thread t2 = new Thread(() -> {
			f2.hold(f1);
		});
		t1.start();
		t2.start();
	}
}

class Fighter {
	private String name;

	public Fighter(String name) {
		this.name = name;
	}

	public synchronized void hold(Fighter f) {
		System.out.println(name + "抓住了" + f.name);
		System.out.println(name + "等待" + f.name + "的放手");
		// 等待着对方先放开自己
		f.loose(this);
		// 然后自己再放开对方
		this.loose(f);
	}

	public synchronized void loose(Fighter f) {
		System.out.println(name + "放开了" + f.name);
	}
}

11、等待与唤醒
? 在多线程通信时,在某些特定条件下,我们需要线程做出一 定的“让步”,否则就很容易造成双方(或多方)进行僵持 状态,进而形成死锁。 

sleep方法虽然能使当前线程阻塞,但是sleep方法不会释放其  所占有的任何“锁”。而且,也不能保证线程苏醒后,条件就 一定会得到满足。 

我们可以使用以下方法实现线程的阻塞,并且令线程暂时释放 “锁”资源。以下方法都是在Object类中声明的(这意味着什
么?)。

? wait 令当前线程等待,直到另一个线程调用为该对象调用 notifynotifyAll方法。当前线程必要拥有该对象的锁。当调 用wait方法后,线程会释放掉其占有的锁,并处于等待队列中。 

? notify 唤醒等待该对象锁的一个线程,如果有多个线程处于 等待中,仅唤醒一个。具体哪一个,取决于底层的实现(这
又意味着什么?)。

? notifyAll唤醒等待该对象锁的所有线程。
说明: waitnotifynotifyAll方法调用时,当前线程一定要拥 有对象的锁。否则将会引发IllegalMonitorStateException异常。 

package day18;

import java.util.ArrayList;
import java.util.List;

public class Product {

	public static void main(String[] args) {
		Worker w = new Worker();
		Thread t1 = new Thread(() -> {
			for (int i = 0; i < 100; i++) {
				w.product();
			}
		});
		Thread t2 = new Thread(() -> {
			for (int i = 0; i < 100; i++) {
				w.consume();
			}
		});
		t1.start();
		t2.start();
	}
}

class Worker {
	private List<String> list = new ArrayList<>();

	public synchronized void product() {
		if (list.size() == 3) {
			System.out.println("仓库已满,生产阻塞。");
			try {
				// 释放掉当前占用的锁资源,使自身处于阻塞队里当中。
				// 调用该方法的线程必须要具有锁资源,否则就会产生异常
				// (IllegalMonitorStateException)。
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		} else {
			list.add("");
			// 通知(唤醒)之前处于阻塞队列的线程。
			notifyAll();
		}
	}

	public synchronized void consume() {
		if (list.size() == 0) {
			System.out.println("仓库已空,消费阻塞。");
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		} else {
			list.remove(0);
			notifyAll();
		}
	}
}

  

package day18;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Product2 {

	public static void main(String[] args) {
		Worker2 w = new Worker2();
		Thread t1 = new Thread(() -> {
			for (int i = 0; i < 100; i++) {
				w.product();
			}
		});
		Thread t2 = new Thread(() -> {
			for (int i = 0; i < 100; i++) {
				w.consume();
			}
		});
		t1.start();
		t2.start();
	}
}

/*
 * Condition与Lock联合使用。
 */
class Worker2 {
	private List<String> list = new ArrayList<>();
	private Lock lock = new ReentrantLock();
	private Condition condition = lock.newCondition();

	public void product() {
		try {
			lock.lock();
			if (list.size() == 3) {
				System.out.println("仓库已满,生产阻塞。");
				try {
					condition.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			} else {
				list.add("");
				// condition.signal();
				condition.signalAll();
			}
		} finally {
			lock.unlock();
		}
	}

	public void consume() {
		try {
			lock.lock();
			if (list.size() == 0) {
				System.out.println("仓库已空,消费阻塞。");
				try {
					condition.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			} else {
				list.remove(0);
				condition.signalAll();
			}
		} finally {
			lock.unlock();
		}
	}
}

  

 

多线程