文档章节

java --多线程

求是科技
 求是科技
发布于 2016/09/08 22:09
字数 3512
阅读 104
收藏 2

总结:
1.继承方式创建线程,步骤
1.1创建一个类继承Thread
1.2该类实现run方法
1.3在需要开辟线程的地方,创建该类的实例,一个实例表示一个线程
1.4调用Thread类的start()方法,启动线程,则它会执行run()方法

2.实现方式创建线程,步骤
2.1创建一个类实现Runnable接口
2.2该类实现run方法
2.3在需要开辟线程的地方,创建该类的实例,将该类的实例作为形参传入Thread类的构造器中,创建Thread类的实例,该实例即为线程。 2.4调用Thread类的start()方法,启动线程,则它会执行run()方法

3.Thread类常用方法
3.1 start():启动线程,执行当前线程的run方法
3.2 run():执行子线程的逻辑业务
3.3 currentThread():获取当前线程
3.4 getName()与setName():设置/获取线程名称
3.5 yield():释放当前线程的cpu执行权,但是它会参与下一个片刻的cpu争抢
3.6 join():在A线程中,B线程执行join()方法,等着B线程执行结束,A线程再执行
3.7 isAlive():判断当前线程是否在还活着
3.8 sleep():使线程沉睡多少秒

4.线程的生命周期
4.1新建:该线程处于创建状态
4.2就绪:处于创建状态的线程执行start()方法后,等待CPU时间片
4.3运行:就绪状态的线程抢到了CPU执行权,执行run()方法
4.4阻塞:运行中的线程被挂起,即让出CPU执行权
4.5死亡:线程完成了它的工作,或者线程被强制性中止

5.线程同步
实际上是加锁
1.同步代码块
添加监视器在 操作共享数据的代码块上
2.同步方法
添加synchronized在 操作共享数据的代码块的方法上

6.线程通信
线程通信有三个方法
wait():挂起当前线程,释放CPU执行权
notify():唤醒排队中优先级最高的那个线程
notifyAll():唤醒所有排队的线程

测试:两个线程交替打印输出1-100,步骤
1.实现多线程功能,两个线程可能会打印出同一个数据
2.实现线程同步,一个线程输出一个数据,但没有实现交替
3.实现交替功能,在同步方法中
3.1唤醒线程
3.2该线程执行 操作共享数据方法
3.3挂起当前线程,即让另一个线程进来
3.4重复1、2、3

###1.基本概念
1.程序:为了完成某一特定功能基于某种语言编写的一组指令的集合,即一段静态的代码。
2.进程:程序的一次运行过程,即正在运行的程序。
3.线程:程序中的一个分支,一个进程可以同时运行多个线程,简称多线程。
###2.创建线程方式
####方式1:继承Thread类

/**
 * 方式1:继承方式创建线程
 * 步骤:
 * 1.创建一个类继承Thread
 * 2.重写Thread类的run()方法,该方法内实现子线程需要完成的功能
 * 3.在需要开辟该线程的地方创建该线程的实例对象
 * 4.调用Thread类的start()方法,启动子线程,则会调用run()方法
 */
public class SubThread extends Thread{
	public void run() {
		for (int i = 0; i <= 100; i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
	}
}

测试代码:

	public static void main(String[] args) {
		SubThread st = new SubThread();
		//给子线程设置名字
		st.setName("子线程");
		//执行start()方法时,才是真正的开辟了子线程
		st.start();
		
		//给主线程设置名字
		Thread.currentThread().setName("主线程");
		//主线程实现功能
		for (int i = 0; i <= 100; i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
	}

输出结果

子线程:50
子线程:51
子线程:52
主线程:48
子线程:53
主线程:49
子线程:54
主线程:50
......

备注:由结果可知,子线程执行了。
####方式2:实现Runnable接口方式

package com.test.thread;

/**
 * 方式2:实现方式创建进程
 * 步骤:
 * 1.创建一个类,实现Runnable接口  
 * 2.重写Thread类的run()方法,该方法内实现子线程需要完成的功能
 * 3.在需要开辟该线程的地方创建该线程的实例对象
 * 4.将此对象作为形参传递给Thread类的构造器中,创建Thread类的实例对象,此对象即为一个线程,要想创建多个线程,即需要实例化多个对象
 * 5.调用Thread类的start()方法,启动子线程,则会调用run()方法
 */
public class SubThread2 implements Runnable{

	@Override
	public void run() {
		for (int i = 0; i <= 100; i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
	}

}

测试代码

	public static void main(String[] args) {
		
		SubThread2 st2 = new SubThread2();
		
		//线程1
		Thread thread1 = new Thread(st2);
		//线程2
		Thread thread2 = new Thread(st2);
		
		thread1.start();
		thread2.start();
	}

###3.Thread常用方法
1.start():启动线程,并执行该线程中run()方法
2.run():子线程药执行的逻辑业务 3.currentThread():静态方法,返回当前Thread
4.getName():获取当前线程名称
5.setName():给当前线程设置名称
6.yield():调用此方法的线程释放当前CPU的执行权

		//主线程实现功能
		for (int i = 0; i <= 100; i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
			if (i>10) {
				//释放当前CPU执行权,但是自己的功能还未执行完,下一个片刻接着争抢cpu执行权
				Thread.yield();
			}
		}

7.join():在A线程中,B线程执行join()方法,则A线程暂停,直至B线程执行结束,A线程再执行。

		//主线程实现功能
		for (int i = 0; i <= 100; i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
			if (i>10) {
				try {
					//当前线程暂停,子线程优先执行完;子线程执行完后,主线程接着执行完
					st.join();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}

8.isAlive:判断该线程是否还存活

		//主线程实现功能
		for (int i = 0; i <= 100; i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
			if (i>10) {
				try {
					//当前线程暂停,子线程优先执行完;子线程执行完后,主线程接着执行完
					st.join();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			//很明显st线程已经死掉了
			System.out.println(st.isAlive());
		}

9.sleep(n):静态方法,调用该方法的线程睡眠n毫秒。

public class SubThread extends Thread{
	public void run() {
		for (int i = 0; i <= 100; i++) {
			try {
				//该线程睡眠1秒钟
				SubThread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
	}
}

###4.线程的调度
####1.调度策略
1.1 时间片轮转:即给进程中的每个线程相同的时间,轮流使用cpu
1.2 优先级:即优先级高的先使用cpu

	public static void main(String[] args) {
		SubThread st = new SubThread();
		//给子线程设置名字
		st.setName("子线程");
		//设置子线程优先级
		st.setPriority(Thread.MAX_PRIORITY);
		//执行start()方法时,才是真正的开辟了子线程
		st.start();
		
		//给主线程设置名字
		Thread.currentThread().setName("主线程");
		//设置主线程优先级
		Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
		//主线程实现功能
		for (int i = 0; i <= 100; i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
	}

###5.实现方式多线程示例
在实际工作中,线程实现方式比继承方式用的多。如下示例是多窗口售票

package com.test.thread;

class Window implements Runnable{

	//总共的票数
	//基于该对象的多个线程共享该资源
	int ticket = 100;
	
	@Override
	public void run() {
		while (true) {
			if (ticket > 0) {
				System.out.println(Thread.currentThread().getName()+"售票,票号为:"+ticket--);
			}else
			{
				break;
			}
		}
		
	}
	
}


public class Main {
	
	public static void main(String[] args) {
		
		Window window = new Window();
		
		//线程1
		Thread thread1 = new Thread(window);
		//线程2
		Thread thread2 = new Thread(window);
		//线程3
		Thread thread3 = new Thread(window);
		
		//开启线程
		thread1.start();
		thread2.start();
		thread3.start();
		
	}

}

###6.线程生命周期(5种状态)
####1.新建
Thread类或其子类的对象被声明创建时,则新生的线程处于创建状态。
####2.就绪
处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它处于就绪状态。
####3.运行
当就绪的线程被调度并获取处理器资源时,便进入运行状态,run()方法里面定义了该线程要实现的功能。
####4.阻塞
线程被人为挂起,或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
####5.死亡
线程完成了它的全部工作,或线程提前被强制性中止。 ###7.线程的同步机制
####1.线程安全出现的原因?
多个线程同时操作共享数据,其中一个线程在操作共享数据的过程中,未执行完毕的情况下,其它的线程也参与进来,导致共享数据存在了安全问题。
####2.如何解决线程安全问题?
线程的同步机制
#####方法1:同步代码块

synchronized(同步监视器)
{
    //需要被同步的代码块,即操作共享数据的代码
}
1.共享数据:多个线程可以同时操作同一个数据  
2.同步监视器:俗称"锁",任何一个对象都可以充当。所有的线程都公用一个监视器。

实现代码

class Window implements Runnable{

	//总共的票数
	//基于该对象的多个线程共享该资源
	int ticket = 100;
	
	//同步监视器
	//任何一个对象都可以充当监视器
	Object oj = new Object();
	
	@Override
	public void run() {
		while (true) {
			//同步
			//oj也可以写成this,表示本类的实例对象充当监视器
			synchronized (oj) {
				if (ticket > 0) {
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()+"售票,票号为:"+ticket--);
				}else
				{
					break;
				}
			}
		}
	}
}

实现原理:
监视器(oj)在同一时刻只放一个线程进入if(){}方法,该线程执行完任务退出后,再放其他线程(这里是所有的线程公平竞争,也包括刚刚执行过的线程)进入。因此,该监视器就像一把锁,一旦有线程进入if()方法,便锁住,不让其他线程进入;当该线程执行完退出后,监视器打开锁,再放一个线程进来。
#####方法2:同步方法
将操作共享数据的方法声明为synchronized。即此方法为同步方法,它能够保证一个线程在执行此方法时,其它线程在外等待直至该线程结束。同步方法的锁即为this。
代码如下

class Window implements Runnable{

	//总共的票数
	//基于该对象的多个线程共享该资源
	int ticket = 100;
	
	//同步监视器
	//任何一个对象都可以充当监视器
	Object oj = new Object();
	
	@Override
	public void run() {
		while (true) {
			show();
		}
	}
	
	//将共享的方法声明为同步方法
	public synchronized void show() {
		
		if (ticket > 0) {
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"售票,票号为:"+ticket--);
		}
		
	}
}

###8.懒汉式单例模式线程安全

//懒汉式单例模式线程安全
class Singleton{
	private Singleton(){
		
	}
	private static Singleton instance = null;
	public static Singleton getInstance(){
		
		if (instance == null) {
			synchronized (Singleton.class) {
				if (instance == null) {
					instance = new Singleton();
				}
			}
		}
		return instance;
	}	
}

测试代码

	public static void main(String[] args) {
		
		Singleton singleton1 = Singleton.getInstance();
		Singleton singleton2 = Singleton.getInstance();
		System.out.println(singleton1 == singleton2);
		
	}

###9.线程同步练习
需求:银行有一个账户,有两个储户,同时向该账户存3000块钱(每次存1000,分3次存完),每次存完钱都需要打印出该账户的余额。
需求分析如下

/**
 * 银行有一个账户
 * 有两个储户,同时向该账户存3000块钱(每次存1000,分3次存完),每次存完钱都需要打印出该账户的余额
 * 预想结果:
 * 1.两个储户交替存钱
 * 2.账户的钱每次都加1000
 * 分析:
 * 1.创建一个账户类
 * 2.创建一个储户类(两个储户可以看作两个线程)
 * 
 */

Account类

class Account{
	//账户余额
	private double balance;
	//存钱方法
	public synchronized void storeMoney(double money){
		balance = balance+money;
		try {
			Thread.currentThread().sleep(100);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()+",账户余额:"+balance);
	}
}

Customer类

class Customer implements Runnable{

	Account Account = new Account();
	
	@Override
	public void run() {
			for (int i = 0; i < 3; i++) {
				Account.storeMoney(1000);
		}
	}
}

测试代码

public class Main {
	
	public static void main(String[] args) {
		
		Customer customer = new Customer();
		Thread thread1 = new Thread(customer);
		thread1.setName("储户1");
		Thread thread2 = new Thread(customer);
		thread2.setName("储户2");
		thread1.start();
		thread2.start();
	}

}

###10.线程死锁
死锁:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方优先释放自己需要的同步资源,这样就形成了线程的死锁。死锁示例:

public class Main {
	
	static StringBuffer sb1 = new StringBuffer();
	static StringBuffer sb2 = new StringBuffer();
	
	public static void main(String[] args) {
		
		//线程1
		new Thread(){
			@Override
			public void run() {
				synchronized (sb1) {
					try {
						Thread.currentThread().sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					sb1.append("A");
					synchronized (sb2) {
						sb2.append("B");
						System.out.println(sb1);
						System.out.println(sb2);
					}
				}
			}
		}.start();
		
		//线程2
		new Thread(){
			@Override
			public void run() {
				synchronized (sb2) {
					try {
						Thread.currentThread().sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					sb2.append("C");
					synchronized (sb1) {
						sb1.append("D");
						System.out.println(sb1);
						System.out.println(sb2);
					}
				}
			}
		}.start();
		
	}

}

如上代码就会出现死锁现象,例如
线程1抢到资源sb1,然后休眠;在休眠期间,线程2抢到了资源sb2。此时线程1需要资源sb2,但是资源sb2被线程2抢占着;同时线程2需要资源sb1,但是资源sb1被线程1抢占着。线程1等着线程2释放资源sb2,线程2等着线程1释放资源sb1,线程1与线程2一直这样僵持着,导致程序无法运行下去,这就构成了死锁。
###11.线程通信
线程通信中用到的三个方法:
wait():使当前线程挂起,并释放CPU(同步资源),使其它线程可以访问并修改同步资源。
notify():唤醒正在排队等待同步资源的线程(优先级最高的那一个),使其访问/修改同步资源。
notifyall():唤醒正在排队等待同步资源的所有线程。
如上三个方法是在java.lang.Object中提供的,且只能用在synchronized方法(或 synchronized代码块中)。

/**
 * 使用两个线程交替打印输出1-100
 *
 */
class PrintNum implements Runnable {

	int num = 1;

	@Override
	public void run() { 
		while (true) {
			this.show();
		}
	}
	
	public synchronized void show() {
		
		//唤醒
		notify();
		
		if (num <= 100) {
			try {
				Thread.currentThread().sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			System.out.println(Thread.currentThread().getName() + ":" + num);
			num = num + 1;
		}
		
		try {
			//让出资源
			wait();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

public class Main {
	
	public static void main(String[] args) {
		
		PrintNum printNum = new PrintNum();
		Thread thread1 = new Thread(printNum);
		Thread thread2 = new Thread(printNum);
		
		thread1.setName("线程1");
		thread2.setName("线程2");
		thread1.start();
		thread2.start();
	}
}

© 著作权归作者所有

共有 人打赏支持
求是科技

求是科技

粉丝 96
博文 448
码字总数 231187
作品 0
成都
后端工程师
私信 提问
15个顶级Java多线程面试题及回答

Java 线程面试问题 在任何Java面试当中多线程和并发方面的问题都是必不可少的一部分。如果你想获得任何股票投资银行的前台资讯职位,那么你应该准备很多关于多线程的问题。在投资银行业务中多...

LCZ777
2014/05/27
0
0
浅谈Java中的ThreadLocal的多线程应用问题

什么是ThreadLocal?首先要说明的一点是ThreadLocal并不是一个Thread,而是Thread的局部变量。在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了...

小欣妹妹
2017/10/23
0
0
java面试必备之ThreadLocal

按照传统的经验,如果某个对象是非线程安全的,在多线程环境下对象的访问需要采用synchronized进行同步。但是模板类并未采用线程同步机制,因为线程同步会降低系统的并发性能,此外代码同步解...

编程老司机
2018/05/16
0
0
java中高级大公司多线程面试题

1)在Java中Lock接口比synchronized块的优势是什么?你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它? lock接口在多线程和并发编...

java成功之路
2018/10/30
0
0
一份关于 Java、Kotlin 与 Android 的学习笔记

JavaKotlinAndroidLearn 这是一份关于 Java 、Kotlin 、Android 的学习笔记,既包含对基础知识点的介绍,也包含对一些重要知识点的源码解析,笔记的大纲如下所示: Java 重拾Java(0)-基础知...

叶应是叶
2018/08/08
0
0

没有更多内容

加载失败,请刷新页面

加载更多

dockerfile 镜像构建(1)

通用dockerfile 利用已经编译好的.jar 来构建镜像。要构建的目录如下: [root@iZuf61quxhnlk9m2tkx16cZ demo_jar]# docker build -t demo:1 . 运行镜像: [root@iZuf61quxhnlk9m2tkx16cZ de...

Canaan_
14分钟前
0
0
Redis radix tree源码解析

Redis实现了不定长压缩前缀的radix tree,用在集群模式下存储slot对应的的所有key信息。本文将详述在Redis中如何实现radix tree。 核心数据结构 raxNode是radix tree的核心数据结构,其结构体...

阿里云云栖社区
16分钟前
3
0
vue import 传入变量

在做动态添加component的时候,传入变量就会报错,出现以下错误信息: vue-router.esm.js?fe87:1921 Error: Cannot find module '@/components/index'. at eval (eval at ./src/components ......

朝如青丝暮成雪
18分钟前
0
0
Flutter开发 Dio拦截器实现token验证过期的功能

前言: 之前分享过在Android中使用Retrofit实现token失效刷新的处理方案,现在Flutter项目也有“token验证过期”的需求,所以接下来我简单总结一下在Flutter项目中如何实现自动刷新token并重...

EmilyWu
19分钟前
5
0
final Map可以修改内容,final 常量不能修改

1.final Map 可以put元素,但是不可以重新赋值 如: final Map map = new HashMap(); map = new HashMap();//不可以 因为栈中变量map引用地址不能修改 2.final str = “aa”; str = "bb";/......

qimh
23分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部