JavaSe——线程

A.线程的概述和多线程的意义

l  多线程概述

•    进程:

•    正在运行的程序,是系统进行资源分配和调用的独立单位。

•    每一个进程都有它自己的内存空间和系统资源。

•    线程:

•    是进程中的单个顺序控制流,是一条执行路径

•    一个进程如果只有一条执行路径,则称为单线程程序。

•    一个进程如果有多条执行路径,则称为多线程程序。


1:要想了解多线程,必须先了解线程,而要想了解线程,必须先了解进程,因为线程是依赖于进程而存在。


2:什么是进程?
通过任务管理器我们就看到了进程的存在。
而通过观察,我们发现只有运行的程序才会出现进程。
进程:就是正在运行的程序。
进程是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。

3:多进程有什么意义呢?
单进程的计算机只能做一件事情,而我们现在的计算机都可以做多件事情。
举例:一边玩游戏(游戏进程),一边听音乐(音乐进程)。
也就是说现在的计算机都是支持多进程的,可以在一个时间段内执行多个任务。
并且呢,可以提高CPU的使用率。

问题:
一边玩游戏,一边听音乐是同时进行的吗?
不是。因为单CPU在某一个时间点上只能做一件事情。
而我们在玩游戏,或者听音乐的时候,是CPU在做着程序间的高效切换让我们觉得是同时进行的。

4:什么是线程呢?
在同一个进程内又可以执行多个任务,而这每一个任务我就可以看出是一个线程。
线程:是程序的执行单元,执行路径。是程序使用CPU的最基本单位。
单线程:如果程序只有一条执行路径。
多线程:如果程序有多条执行路径。

5:多线程有什么意义呢?
多线程的存在,不是提高程序的执行速度。其实是为了提高应用程序的使用率。
程序的执行其实都是在抢CPU的资源,CPU的执行权。
多个进程是在抢这个资源,而其中的某一个进程如果执行路径比较多,就会有更高的几率抢到CPU的执行权。
我们是不敢保证哪一个线程能够在哪个时刻抢到,所以线程的执行有随机性。

6.并行和并发

并行:逻辑上同时发生,指在某一个时间内同时运行多个程序。

并发:物理上同时发生,指在某一个时间点同时运行多个程序。


B.Java程序运行原理

l  Java程序运行原理

•    java 命令会启动java 虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个“主线程” ,然后主线程去调用某个类的 main 方法。所以main方法运行在主线程中。在此之前的所有程序都是单线程的。

•    思考:

•    jvm虚拟机的启动是单线程的还是多线程的?


多线程。原因是垃圾回收线程也要先启动,否则很容易出现内存溢出。

现在的垃圾回收线程加上前面的主线程,最低启动了两个线程,所以,jvm的启动其实是多线程的。


C.如何实现多线程及多线程方式1的思路


方式1:

step1.写一个MyThread类继承Thread类,

step2.重写MyThread里面的run方法

step3.创建MyThread对象

step4.启动线程


问题:为什么要重写run方法呢?

因为不是类中所有的代码都需要被线程执行的。

而为了区分哪些代码被执行,java提供了Thread类中的run()用来包含那些被线程执行的代码。


package com.core.thread;

public class MyThread extends Thread {
	
	public MyThread() {
		super();
	}
	public MyThread(String name) {
		super(name);
	}

	@Override
	public void run() {
		//耗时操作
		for (int i = 0; i < 10; i++) {
			//获取线程的名称
			System.out.println(getName()+"::"+i);
		}
	}
}


package com.core.thread;

public class Test {
	public static void main(String[] args) {
		MyThread t1 = new MyThread();

		MyThread t2 = new MyThread();
		
		// 设置线程名称
		t2.setName("线程t2");
		Thread t3 = new MyThread("线程t3");
		
		// 获得main函数的线程名称?
		// 返回当前正在执行的线程对象
		Thread currentThread = Thread.currentThread();
		System.out.println("currentThread::" + currentThread.getName());

	}
}


D.线程调度及获取和设置线程优先级

package com.core.thread;

public class Test {
	public static void main(String[] args) {
		MyThread t1 = new MyThread();

		MyThread t2 = new MyThread();
		
		// 设置线程名称
		t2.setName("线程t2");
		Thread t3 = new MyThread("线程t3");
		
		// 获得main函数的线程名称?
		// 返回当前正在执行的线程对象
		Thread currentThread = Thread.currentThread();
		System.out.println("currentThread::" + currentThread.getName());

		// • public final int getPriority()
		// • public final void setPriority(int newPriority)
		System.out.println(t1.getPriority());
		System.out.println(t2.getPriority());
		System.out.println(t3.getPriority());
		t1.setPriority(10);
		t2.setPriority(1);
		t3.setPriority(1);
		
		t1.start();
		t2.start();
		t3.start();
	}
}

E.休眠线程

•    线程休眠

•    public static void sleep(long millis)

package com.core.thread.demo1;

import java.util.Date;

public class SleepThread extends Thread {
	
	public SleepThread() {
		super();
	}
	public SleepThread(String name) {
		super(name);
	}

	@Override
	public void run() {
		//耗时操作
		for (int i = 0; i < 100; i++) {
			//获取线程的名称
			System.out.println(getName()+"--"+i+","+new Date());
			try {
				sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

测试类:
package com.core.thread.demo1;

public class Test {
	public static void main(String[] args) {
		SleepThread st1 = new SleepThread();
		SleepThread st2 = new SleepThread("线程2");
		st1.setName("线程1");

		st1.start();
		st2.start();
	}
}


F.加入线程

•    线程加入

•    public final void join()

等待线程结束
package com.core.thread.demo1;

import java.util.Date;

public class JoinThread extends Thread {
	
	public JoinThread() {
		super();
	}
	public JoinThread(String name) {
		super(name);
	}

	@Override
	public void run() {
		//耗时操作
		for (int i = 0; i < 100; i++) {
			//获取线程的名称
			System.out.println(getName()+"--"+i+","+new Date());
		}
	}
}

测试类:
package com.core.thread.demo1;

public class Test {
	// 线程加入
	// public final void join()

	public static void main(String[] args) {
		JoinThread jt1 = new JoinThread("爸爸");
		JoinThread jt2 = new JoinThread("大儿子");
		JoinThread jt3 = new JoinThread("小儿子");
		jt1.start();
		try {
			jt1.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		jt2.start();
		jt3.start();
	}
}


G.礼让线程

•    线程礼让

•    public static void yield()


package com.core.thread.demo3;

import java.util.Date;

public class YieldThread extends Thread {

	public YieldThread() {
		super();
	}

	public YieldThread(String name) {
		super(name);
	}

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

测试类:
package com.core.thread.demo3;

public class Test {
	//线程礼让,暂停当前正在执行的线程对象,并执行其他线程
	//让多个线程的执行更协调,但是不能保证一人一次
	public static void main(String[] args) {
		YieldThread st1 = new YieldThread();
		YieldThread st2 = new YieldThread("线程2");
		st1.setName("线程1");

		st1.start();
		st2.start();
	}
}


H.守护线程

public final void setDaemon(boolean on)
将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用。 
package com.core.thread.demo4;

import java.util.Date;

public class DaemonThread extends Thread {

	public DaemonThread() {
		super();
	}

	public DaemonThread(String name) {
		super(name);
	}

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

测试类:
package com.core.thread.demo4;

public class Test {
	// public final void setDaemon(boolean on)
	// 将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
	// 该方法必须在启动线程前调用

	public static void main(String[] args) {
		DaemonThread dt1 = new DaemonThread();
		DaemonThread dt2 = new DaemonThread("关羽");
		dt1.setName("张飞");

		// 设置dt1和dt2为守护线程,必须放在启动线程之前
		dt1.setDaemon(true);
		dt2.setDaemon(true);

		dt1.start();
		dt2.start();
		Thread.currentThread().setName("刘备");
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName() + "," + i);
		}
	}
}


I .中断线程


package com.core.thread.demo5;

import java.util.Date;

public class StopThread extends Thread {

	public StopThread() {
		super();
	}

	public StopThread(String name) {
		super(name);
	}

	@Override
	public void run() {
		long currentTimeMillis = System.currentTimeMillis();
		Date date = new Date(currentTimeMillis);
		System.out.println("开始时间:" + date);

		try {
			sleep(5000);
		} catch (InterruptedException e) {
			// e.printStackTrace();
			System.out.println("线程被终止");
		}

		currentTimeMillis = System.currentTimeMillis();
		date = new Date(currentTimeMillis);
		System.out.println("结束时间:" + date);
	}
}

测试类:
package com.core.thread.demo5;

public class Test {

	public static void main(String[] args) {
		StopThread st1 = new StopThread("线程1");
		st1.start();
		
		try {
			Thread.currentThread().sleep(3000);
//			st1.stop();
			st1.interrupt();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

注意在终止线程的时候,stop和interrupt的区别。

J.线程生命周期图解




K.多线程方式2的思路及代码实现


l  实现Runnable接口

•    如何获取线程名称

•    如何给线程设置名称

l  实现接口方式的好处

•    可以避免由于Java单继承带来的局限性。

•    适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想。

package com.core.thread.demo6;

public class MyRunnable implements Runnable {

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

}

测试类:

package com.core.thread.demo6;

public class Test {
	// 多线程的方式2
	// a.写一个MyRunnable类继承Runnable接口
	// b.重写run()方法
	// c.创建MyRunnable对象
	// d.创建Thread对象,并把c步骤创建的MyRunnable对象作为构造参数传递
	public static void main(String[] args) {
		MyRunnable runnable = new MyRunnable();
		Thread t1 = new Thread(runnable, "线程1");
		Thread t2 = new Thread(runnable, "线程2");
		t1.start();
		t2.start();
	}
}

L.多线程两种方式的图解比较及区别



M.线程安全


如,某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。

用多线程来实现,两种方法。

第一种,继承Thread类,重写run方法。

第二种,实现Runnable接口。


先用第一种方法:

package com.core.thread.demo7;

public class SellTicket extends Thread {
	public static int ticketNum = 100;
	public SellTicket(String name){
		super(name);
	}
	@Override
	public void run() {
//		while(ticketNum>0){
//			
//		}
		while(true){
			if(ticketNum >0){
				System.out.println(Thread.currentThread().getName()+"正在出售"+ticketNum+"张票");
				ticketNum--;
			}
		}
	}
}

测试类:

package com.core.thread.demo7;

//某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票
public class Test {
	public static void main(String[] args) {
		SellTicket st1 = new SellTicket("窗口1");
		SellTicket st2 = new SellTicket("窗口2");
		st1.start();
		st2.start();
	}
}

通过打印可以看出,这个是存在线程安全的!暂时先放一下,稍后解决。

接下来,使用实现Runnable接口的方式。

package com.core.thread.demo8;

public class SellTicket implements Runnable {
private static int ticketNum = 100;
	@Override
	public void run() {
		while(true){
			if(ticketNum >0){
				try {
					Thread.currentThread().sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+"正在出售"+ticketNum+"张票");
				ticketNum--;
			}
		}
	}

}

package com.core.thread.demo8;

//某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票
//实现Runnable接口
public class Test {
	public static void main(String[] args) {
		SellTicket st = new SellTicket();
		Thread t1 = new Thread(st, "窗口1");
		Thread t2 = new Thread(st, "窗口2");
		t1.start();
		t2.start();
	}

}

注意:为了演示方便,我在售票的时候,睡眠了一段时间。从打印的数据来看,还是存在线程安全的问题。


N.出现了同票和负数票的原因分析

l  问题

•    相同的票出现多次

•    CPU的一次操作必须是原子性的

•    还出现了负数的票

•    随机性和延迟导致的

l  注意

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



O.线程安全问题的产生原因分析

l  首先想为什么出现问题?(也是我们判断是否有问题的标准)

•    是否是多线程环境

•    是否有共享数据

•    是否有多条语句操作共享数据

l  如何解决多线程安全问题呢?

•    基本思想:让程序没有安全问题的环境。

•    怎么实现呢?

•    把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。


P.使用同步代码块解决线程安全问题

package com.core.thread.demo8;

public class SellTicket implements Runnable {
	private static int ticketNum = 100;
	//创建锁对象
	private Object obj = new Object();

	@Override
	public void run() {
		while (true) {
			synchronized (obj) {
				if (ticketNum > 0) {
					try {
						Thread.currentThread().sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "正在出售"
							+ ticketNum + "张票");
					ticketNum--;
				}
			}
		}
	}

}


package com.core.thread.demo8;

//某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票
//实现Runnable接口
public class Test {
	public static void main(String[] args) {
		SellTicket st = new SellTicket();
		Thread t1 = new Thread(st, "窗口1");
		Thread t2 = new Thread(st, "窗口2");
		Thread t3 = new Thread(st, "窗口3");
		t1.start();
		t2.start();
		t3.start();
	}

}

Q.同步代码块的特点和优缺点

l  同步的前提

•    多个线程

•    多个线程使用的是同一个锁对象

l  同步的好处

•    同步的出现解决了多线程的安全问题。

l  同步的弊端

•    当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。

R.同步方法

package com.core.thread.demo10;

public class SellTicket implements Runnable {
private static int ticketNum = 100;
	@Override
	public void run() {
		while(true){
			sellTicket();
		}
	}
	private synchronized void sellTicket() {
		if(ticketNum >0){
			try {
				Thread.currentThread().sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"正在出售"+ticketNum+"张票");
			ticketNum--;
		}
	}

}

S.关于使用同步方法锁的问题

回顾一下,关于我们使用的锁!


package com.core.thread.demo11;

public class SellTicket implements Runnable {
	private static int ticketNum = 100;
	private Object obj = new Object();
	private Test t = new Test();

	@Override
	public void run() {
		while (true) {
			// 使用Object类为锁
/*			synchronized (obj) {
				if (ticketNum > 0) {
					try {
						Thread.currentThread().sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()
							+ "正在出售" + ticketNum + "张票");
					ticketNum--;
				}
			}

			// 使用任意类为锁
			synchronized (t) {
				if (ticketNum > 0) {
					try {
						Thread.currentThread().sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()
							+ "正在出售" + ticketNum + "张票");
					ticketNum--;
				}
			}
*/
			// 使用字节码类为锁
			synchronized (SellTicket.this) {
				if (ticketNum > 0) {
					try {
						Thread.currentThread().sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()
							+ "正在出售" + ticketNum + "张票");
					ticketNum--;
				}
			}
		}
	}

	private synchronized void sellTicket() {
		if (ticketNum > 0) {
			try {
				Thread.currentThread().sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "正在出售"
					+ ticketNum + "张票");
			ticketNum--;
		}
	}

}

再回过来,看同步方法到底是使用哪一个锁呢?

package com.core.thread.demo12;

public class SellTicket implements Runnable {
	private static int ticketNum = 100;
	private Object obj = new Object();
	private Test t = new Test();
	private int x = 1;

	@Override
	public void run() {
		while (true) {
			if (x % 2 == 1) {
				synchronized (this) {
					// 这里注意了,如果是SellTicke.class 设为锁,则会出现售0票的现象。
					// 但是如果要是this,或者SellTicket.this 设为锁,则售票正常[可以看下面的例子,sellTicket为静态方法]
					if (ticketNum > 0) {
						try {
							Thread.currentThread().sleep(100);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						System.out.println(Thread.currentThread().getName()
								+ "正在出售" + ticketNum + "张票");
						ticketNum--;
					}
				}
			} else {
				sellTicket();
			}
			x++;
		}
	}

	private synchronized void sellTicket() {
		if (ticketNum > 0) {
			try {
				Thread.currentThread().sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "正在出售"
					+ ticketNum + "张票");
			ticketNum--;
		}
	}

}

静态方法发锁:

package com.core.thread.demo12;

//某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票
//实现Runnable接口
public class Test {
//	小结:
//	A:同步代码块上的锁对象是谁?
//		任意对象。
//	B:同步方法是锁是谁?
//		this
//	C:静态方法的锁对象是谁?
//		类的字节码文件
	
	public static void main(String[] args) {
		SellTicket st = new SellTicket();
		Thread t1 = new Thread(st, "窗口1");
		Thread t2 = new Thread(st, "窗口2");
		Thread t3 = new Thread(st, "窗口3");
		t1.start();
		t2.start();
		t3.start();
	}

}


小结:

A:同步代码块上的锁对象是谁?
任意对象。
B:同步方法是锁是谁?
this
C:静态方法的锁对象是谁?
类的字节码文件对象

T.回顾以前的线程安全的类

package com.core.thread.demo13;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Hashtable;
import java.util.List;
import java.util.Vector;

public class Test {

	public static void main(String[] args) {
		// 回顾以前讲过的线程安全的类
		StringBuffer sb = new StringBuffer();
		Vector<Object> vector = new Vector<>();
		Hashtable<Object, Object> hashtable = new Hashtable<>();

		ArrayList<Object> list = new ArrayList<>();// 线程不安全。
		// Collections类中给我提供一个方法
		// synchronizedList
		// public static <T> List<T> synchronizedList(List<T> list)
		// 返回指定列表支持的同步(线程安全的)列表。为了保证按顺序访问,必须通过返回的列表完成所有对底层实现列表的访问。
		List<Object> list2 = Collections.synchronizedList(list);//线程安全。
	}

}








  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值