Java多线程
_01Thread类及Runnable接口
线程执行具有随机性。
* 创建新执行线程的两种方法:
1. 将类声明为 Thread 的子类。该类应重写 Thread 类的 run()方法。创建对象,开启线程。run方法相当于其他线程的main方法。
2. 声明一个实现 Runnable 接口的类。实现 run()方法,然后创建Runnable 的子类对象,并传到某个线程的构造方法中,开启线程。
1、实现线程程序继承Thread
Thread是程序中的执行线程。JAVA虚拟机允许程序并发地运行多个执行线程。
例:继承Thread
/*
* 定义Thread子类,重写run()方法
*/
public class SubThread extends Thread{
public void run(){
for (int i = 0; i < 5; i++){
System.out.println("run..."+i);
}
}
@Test
/*
* 创建和启动一个线程
* 创建Thread子类对象
* 子类对象调用方法start():
* 让线程程序执行,JVM调用线程中的run
*/
public void testSubThread(){
SubThread st0 = new SubThread();
SubThread st1 = new SubThread();
st0.start();
st1.start();
for(int i = 0; i < 50; i++) {
System.out.println(i);
}
}
2、为什么要继承Thread类
1*、:我们为什么要继承Thread类,并调用其的start方法才能开启线程呢?
继承Thread类:因为Thread类用来描述线程,具备线程应该有功能。那为什么不直接创建Thread类的对象呢?
如下代码:
Thread t1 = new Thread();
t1.run();//这样做没有错,但是该start调用的是Thread类中的run方法
//而这个run方法没有做什么事情,更重要的是这个run方法中并没有定义我们需要让线程执行的代码。
2*、: 创建线程的目的是什么?
是为了建立程序单独的执行路径,让多部分代码实现同时执行。也就是说线程创建并执行需要给定线程要执行的任务。
对于之前所讲的主线程,它的任务定义在main函数中。自定义线程需要执行的任务都定义在run方法中。
3*、:多线程执行时,到底在内存中是如何运行的呢?
多线程执行时,在栈内存中,其实每一个执行线程都有一片自己所属的栈内存空间。进行方法的压栈和弹栈。
当执行线程的任务结束了,线程自动在栈内存中释放了。但是当所有的执行线程都结束了,那么进程就结束了。
3、获取线程名字Thread类方法
- 获取线程名字Thread类方法getName
public class NameThread extends Thread {
public void run(){
System.out.println(getName());
}
}
@Test
/*
* 每个线程,都有自己的名字
* 新建的线程的默认名字为"Thread-0","Thread-1",...
*/
public void testThreadName(){
NameThread nt = new NameThread();
nt.start();
}
- 获取线程名字Thread类方法currentThread
@Test
/*
* Thread中
* static Thread currentThread()返回正在执行的线程对象
*/
public void testCurrentThread(){
System.out.println(Thread.currentThread());
}
4、线程名字设置
- 通过调用父类Thread的构造器
public class NameThread extends Thread {
public NameThread(){
super("小猫");
}
public void run(){
System.out.println(getName());
}
}
- 通过调用setName()方法设置
public void testThreadName(){
NameThread nt = new NameThread();
nt.setName("��");
nt.start();
}
5、Thread类方法sleep
@Test
public void testSleepThread() throws Exception{
SleepThread st = new SleepThread();
st.start();
for (int i = 0; i < 5; i++) {
//睡眠500ms,500ms已到并且cpu切换到该线程继续向下执行
Thread.sleep(500);
System.out.println(i);
}
}
6、实现线程的另一种方式实现Runnable接口
/*
* 实现线程的另一方式,接口实现
* 实现接口Runnable,重写run方法
*/
public class SubRunnable implements Runnable{
public void run(){
for (int i = 0; i < 50; i++) {
System.out.println("run..."+i);
}
}
}
@Test
/*
* 实现接口方式的线程
* 创建Thread类对象,构造方法中,传递Runnable接口实现类
* 调用Thread类方法start()
*/
public void testRunnable(){
SubRunnable sr = new SubRunnable();
Thread t= new Thread(sr);
t.start();
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread()+"..."+i);
}
}
7、实现接口方式的好处
* 实现接口方式的好处:
第二种方式实现Runnable接口避免了单继承的局限性,所以较为常用。
实现Runnable接口的方式,更加的符合面向对象,线程分为两部分,一部分线程对象,一部分线程任务。
继承Thread类,线程对象和线程任务耦合在一起。
一旦创建Thread类的子类对象,既是线程对象,有又有线程任务。
实现runnable接口,将线程任务单独分离出来封装成对象,类型就是Runnable接口类型。Runnable接口对线程对象和线程任务进行解耦。
(降低紧密性或者依赖性,创建线程和执行任务不绑定)
8、匿名内部类实现线程程序
public class NiMing {
public static void main(String[] args) {
new Thread(){
public void run(){
System.out.println("继承方式");
}
}.start();
new Thread(new Runnable(){
public void run(){
System.out.println("实现接口方式");
}
}).start();
}
}
_02 线程池
1、线程池原理
1.在java中,如果每个请求到达就创建一个新线程,开销是相当大的。
2.在实际使用中,创建和销毁线程花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。
3.除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。
如果在一个jvm里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。
为了防止资源不足,需要采取一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象来进行服务。
线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使用应用程序响应更快。另外,通过适当的调整线程中的线程数目可以防止出现资源不足的情况。
2、JDK5实现线程池
/*
* JDK1.5新特性,实现线程池程序
* 使用工厂类 Executors中的静态方法创建线程对象,指定线程的个数
* static ExecutorService newFixedThreadPool(int 个数)
* 返回的是ExecutorService接口的实现类 (线程池对象)
* 接口实现类对象,调用方法submit (Ruunable r) 提交线程执行任务
*/
@Test
public void testThreadPool(){
//调用工厂类的静态方法,创建线程池对象
//返回线程池对象,是返回的接口
ExecutorService es = Executors.newFixedThreadPool(2);
//调用接口实现类对象es中的方法submit提交线程任务
//将Runnable接口实现类对象传递给submit
es.submit(new ThreadPoolRunnable());
es.submit(new ThreadPoolRunnable());
es.submit(new ThreadPoolRunnable());
}
public class ThreadPoolRunnable implements Runnable {
public void run(){
System.out.println(Thread.currentThread().getName()+"线程提交任务");
}
}
3、实现线程的Callable接口方式
/*
* 实现线程池的Callable接口方式
* 实现步骤:
* 工厂类 Executors静态方法newFixedThreadPool方法,创建线程池对象
* 返回线程池对象ExecutorService接口类型,调用submit提交进程任务
* submit(Callable c)
*/
@Test
public void testThreadPool1() throws InterruptedException, ExecutionException{
ExecutorService es = Executors.newFixedThreadPool(2);
//提交线程任务的方法submit方法返回 Future接口的实现类
Future<String> f = es.submit(new ThreadPoolCallable());
String s = f.get();
System.out.println(s);
}
public class ThreadPoolCallable implements Callable<String>{
public String call(){
return "Callable方式可以具有返回值";
}
}
4、线程实现异步计算案例
*:同步代码块的执行原理
同步代码块: 在代码块声明上 加上synchronized
synchronized (锁对象) {
可能会产生线程安全问题的代码
}
同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。
public class ThreadPoolCallable implements Callable<String>{
public String call(){
return "Callable方式可以具有返回值";
}
}
/*
* 使用多线程技术,求和
* 两个线程,一个计算1+100,另一个计算1+200的和
* 两个线程的异步计算
*/
@Test
public void testThreadPoolAdd() throws InterruptedException, ExecutionException{
ExecutorService es = Executors.newFixedThreadPool(2);
Future<Integer> f1 = es.submit(new GetSumCallable(100));
Future<Integer> f2 = es.submit(new GetSumCallable(200));
System.out.println(f1.get());
System.out.println(f2.get());
es.shutdown();
}
_03 线程安全
*A:线程操作共享数据的安全问题
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。
程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
1、同步代码块解决线程安全问题
public class TestTickets {
public static void main(String[] args) {
//创建Runnable接口实现类对象
Tickets t = new Tickets();
Thread t0 = new Thread(t);
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t0.start();
t1.start();
t2.start();
}
}
/*
* 通过线程休眠,出现安全问题
* 解决安全问题,Java程序,提供技术,同步技术
* 公式:
* synchronized(任意对象){
* 线程要操作的共享数据
* }
* 同步代码块
*/
public class Tickets implements Runnable{
private int ticket = 100;
private Object obj = new Object();
public void run(){
while(true){
//线程共享块,保证安全,加入同步代码块
synchronized (obj) {
if(ticket > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"出售的第"+ticket--);
}
}
}
}
}
2、同步的上厕所原理
a:不使用同步:线程在执行的过程中会被打扰
线程比喻成人
线程执行代码就是上一个厕所
第一个人正在上厕所,上到一半,被另外一个人拉出来
b:使用同步:
线程比喻成人
线程执行代码就是上一个厕所
锁比喻成厕所门
第一个人上厕所,会锁门
第二个人上厕所,看到门锁上了,等待第一个人上完再去上厕所
3、同步方法
* 采用同步方法形式,解决线程的安全问题
* 好处: 代码简洁
* 将线程共享数据,和同步,抽取到一个方法中
* 在方法的声明上,加入同步关键字
*
* 问题:
* 同步方法有锁吗,肯定有,同步方法中的对象锁,是本类对象引用 this
* 如果方法是静态的呢,同步有锁吗,绝对不是this
* 锁是本类自己.class 属性
* 静态方法,同步锁,是本类类名.class属性
例:
public class Tickets implements Runnable{
private int ticket = 100;
public void run() {
while (true) {
payTicket();
}
}
public synchronized void payTicket() {
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "出售的第" + ticket--);
}
}
}
_04JDK1.5新特性Lock接口
*A:JDK1.5新特性Lock接口
* Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
* Lock接口中的常用方法
void lock()
void unlock()
* Lock提供了一个更加面对对象的锁,在该锁中提供了更多的操作锁的功能。
* 我们使用Lock接口,以及其中的lock()方法和unlock()方法替代同步,
1、例:对电影院卖票案例中Ticket
public class Tickets implements Runnable{
//定义出售的资源
private int ticket = 100;
//在类的成员位置,创建Lock接口的实现类
private Lock lock = new ReentrantLock();
public void run(){
while(true){
//调用Lock接口方法lock获取锁
lock.lock();
//对票数贩毒案,大于0,可以出售,
if(ticket > 0){
try{
Thread.sleep(10);
System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
}catch(Exception e){
}finally{
//释放锁,调用Lock接口方法unlock
lock.unlock();
}
}
}
}
}
2、线程的死锁代码实现
public class LockA {
private LockA(){}
public static final LockA locka = new LockA();
}
public class LockB {
private LockB(){}
public static final LockB lockb = new LockB();
}
public class DeadLock implements Runnable{
private int i = 0;
public void run(){
while(true){
if(i % 2 == 0){
//先进入A同步,在进入B同步
synchronized (LockA.locka) {
System.out.println("if...locka");
synchronized (LockB.lockb) {
System.out.println("if...lockb");
}
}
}else{
//先进入B同步,再进入A同步
synchronized (LockB.lockb) {
System.out.println("else...lockb");
synchronized (LockA.locka) {
System.out.println("else...locka");
}
}
}
i++;
}
}
}
public class DeadLockDemo {
public static void main(String[] args) {
DeadLock dead = new DeadLock();
Thread t0 = new Thread(dead);
Thread t1 = new Thread(dead);
t0.start();
t1.start();
}
}
3、线程等待与唤醒案例
*A:线程等待与唤醒案例介绍
等待唤醒机制所涉及到的方法:
wait() :等待,将正在执行的线程释放其执行资格 和 执行权,并存储到线程池中。
notify():唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意的。
notifyAll(): 唤醒全部:可以将线程池中的所有wait() 线程都唤醒。
其实,所谓唤醒的意思就是让 线程池中的线程具备执行资格。必须注意的是,这些方法都是在 同步中才有效。同时这些方法在使用时必须标明所属锁,这样才可以明确出这些方法操作的到底是哪个锁上的线程。
例:
资源类编写:
/*
* 定义资源类,有2个成员变量
* name,sex
* 同时有2个线程,对资源中的变量操作
* 1个对name,age赋值
* 2个对name,age做变量的输出打印
*/
public class Resource {
public String name;
public String sex;
}
线程等待与唤醒案例输入进程:
public class Input implements Runnable{
private Resource r = new Resource();
public void run() {
int i = 0;
while (true) {
if (i % 2 == 0) {
r.name = "张三";
r.sex = "男";
} else {
r.name = "lisi";
r.sex = "nv";
}
i++;
}
}
}
线程等待与唤醒案例输出进程:
public class Output implements Runnable{
private Resource r = new Resource();
public void run() {
while(true){
System.out.println(r.name+"..."+r.sex);
}
}
}
线程等待与唤醒案例测试类:
public class ThreadDemo {
public static void main(String[] args) {
Resource r = new Resource();
Input in = new Input();
Output out = new Output();
Thread tin = new Thread(in);
Thread tout = new Thread(out);
tin.start();
tout.start();
}
}
出现的null值问题是由于r不是同一个。
线程等待与唤醒案例null值解决:
public class Input implements Runnable{
private Resource r;
public Input(Resource r){
this.r = r;
}
public void run() {
int i = 0;
while (true) {
if (i % 2 == 0) {
r.name = "张三";
r.sex = "男";
} else {
r.name = "lisi";
r.sex = "nv";
}
i++;
}
}
}
public class Output implements Runnable {
private Resource r;
public Output(Resource r){
this.r = r;
}
public void run() {
while(true){
System.out.println(r.name+"..."+r.sex);
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
Resource r = new Resource();
Input in = new Input(r);
Output out = new Output(r);
Thread tin = new Thread(in);
Thread tout = new Thread(out);
tin.start();
tout.start();
}
}
线程等待与唤醒案例数据安全解决
public class Resource {
public String name;
public String sex;
public boolean flag = false;
}
public class Input implements Runnable{
private Resource r;
public Input(Resource r){
this.r = r;
}
public void run() {
int i = 0;
while (true) {
synchronized (r) {
if(r.flag){
try {
r.wait();
} catch (InterruptedException e) {}
}
if (i % 2 == 0) {
r.name = "张三";
r.sex = "男";
} else {
r.name = "lisi";
r.sex = "nv";
}
//将对方线程唤醒,标记改为true
r.flag = true;
r.notify();
}
i++;
}
}
}
public class Output implements Runnable {
private Resource r;
public Output(Resource r){
this.r = r;
}
public void run() {
while(true){
synchronized (r) {
if(!r.flag){
try {
r.wait();
} catch (InterruptedException e) {}
}
System.out.println(r.name+"..."+r.sex);
r.flag = false;
r.notify();
}
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
Resource r = new Resource();
Input in = new Input(r);
Output out = new Output(r);
Thread tin = new Thread(in);
Thread tout = new Thread(out);
tin.start();
tout.start();
}
}