博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
多线程
阅读量:5237 次
发布时间:2019-06-14

本文共 11754 字,大约阅读时间需要 39 分钟。

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
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
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(); } }}

  

 

转载于:https://www.cnblogs.com/liuwei6/p/6582828.html

你可能感兴趣的文章
Jquery简单动画的实现记录
查看>>
栈的实现原理
查看>>
Day 35 协程
查看>>
如何为EXCEL单元格颜色排序
查看>>
Hat's Words
查看>>
Oracle字符函数
查看>>
git介绍和常用指令
查看>>
在SQL Server中将数据导出为XML和Json
查看>>
Etcd源码解析(转)
查看>>
如何在Exe和BPL插件中实现公共变量共享及窗口溶入技术Demo源码
查看>>
Luogu[SDOI2008]Sue的小球
查看>>
linux安装-mysql5.7.24
查看>>
linux网络配置、环境变量以及JDK安装(CentOS 6.5)
查看>>
Leetcode Validate Binary Search Tree
查看>>
利用套打和分栏巧妙来做商品价签
查看>>
hdu 1102 Constructing Roads(kruskal || prim)
查看>>
JS中一些特殊的方法
查看>>
vb.net版机房收费——助你学会七层架构(二)反射+抽象工厂
查看>>
【转载】SpringBoot yml 配置
查看>>
tcp client.cs
查看>>