java多线程系列
一:程序、进程、线程的概念
程序:指令和数据的有序集合;就是一串串有步骤的代码。就好比没有打开的王者
进程:一段程序写好了,就要运行。进程就是执行程序的一次执行过程。通俗的说,就是“上号”
线程:一个进程中至少有一个线程;当你玩王者选英雄的同时,也可以聊天;你就可以当做同时运行了选英雄线程和聊天线程;当然,有很多线程在隐藏的运行。
那多线程有什么好处呢?
你可以把写出来的程序想作一杯饮料;而进程就是装饮料的容器;多线程就相当于吸管。就是说,线程越多,程序运行越快;用户的体验感就越好。即: 高效的体验
多个线程要在CPU上运行,而CPU就一个;问题是不是就来了:你先还是我先?这里就引入了串行、并发、并行
任务调度器:CPU处理器使用时间片轮转技术,可以让多个线程切换
串行:我们排好队,一个一个的来;前一个线程没完成,后一个线程必须等着
并发:前一个线程运行时说道:等会儿,我要等待十分钟才行。CPU就说:那让后边的线程先来,你自己记住运行到哪里了就行(程序计数器)
程序计数器:程序计数器记录的是该线程下一指令的地址;这里涉及了JVM原理。
并行:并发的一种理想状态 ;适用于多核CPU
二:创建线程的三种方式
1.继承Thread类
public class ThreadTest extends Thread {
@Override
public void run() {
// Thread.currentThread() 表示当前执行run()的线程 | getName() 表示 获取指定线程的名称
System.out.println("我是"+Thread.currentThread().getName());
}
public static void main(String[] args) {
new Thread(new ThreadTest(),"线程一号").start();
}
}
缺点:不利于Threadtest类的扩展;一般程序中不这么写
2.实现Runnable接口
- 凭什么能实现Runnable接口?
- Thread类是实现了Runable接口的
- Thread类有接收Runable实现类对象的构造器 public Thread(new Runnable())
- 直接实现Runnable接口
public class RunnableTest implements Runnable {
@Override
public void run() {
System.out.println("我是"+Thread.currentThread().getName());
}
public static void main(String[] args) {
new Thread(new RunnableTest(),"线程二号").start();
}
}
- 匿名实现Runnable接口
public class RunnableTest {
public static void main(String[] args) {
new Thread(new Runnable(){
public void run() {
System.out.println("我是"+Thread.currentThread().getName());
}
},"线程三号").start();
}
}
4.lamda表达式实现Runnable接口
public class RunnableTest {
public static void main(String[] args) {
new Thread(()->{
System.out.println(Thread.currentThread().getName());
},"线程三号").start();
}
}
3.实现Callable接口(不常用)
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class CallableTest implements Callable<String> {
@Override
public String call() throws Exception {
return Thread.currentThread().getName()+"is running!";
}
public static void main(String[] args) {
//创建一个任务线程对象callable
Callable<String> call = new CallableTest();
//将任务线程对象转换为未来任务对象
FutureTask<String> task = new FutureTask(call);
//将未来任务对象转换为线程
Thread newThread = new Thread(task);
newThread.start();
//如何获取Callable中的返回值??
try{
String myCallablevalue = task.get();
System.out.println(myCallablevalue);
}catch(Exception e){
e.printStackTrace(); }
}
}
-
步骤
1. 实现callable接口,重写call()
2. 创建一个任务线程对象 new Callable(){ }
3. 将任务线程对象转换为未来任务对象 new FutureTask(new Callable())
4. 通过Thread的构造方法 public Thread(new FUtureTask())
5. 获取Callable对象的返回值 -
注意: JDK1.8 不支持Callable这种创建线程的写法
三、线程实现底层:静态代理模式
思考一个问题:凭什么调用Thread线程的start(),就能执行run()?
有人就要说了,一定是在start()中有run()的调用呗。那看源码:
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
这明显不是想的那样吧,连run()的影子都没有。其实,这里运用了静态代理的模式。
什么是静态代理?
- 就好比,你是个少爷,你要吃饭;你压根不需要考虑吃饭前的做饭环节和吃饭后的洗碗环节;你就只考虑吃饭这一件事;其他的交给别人来做;这里的人就是代理的意思。话说回来,start()就只考虑启动线程;不管有没有run(),start()都要启动。可以说,start()与run()压根没关系。
public class StaticAgent {
public static void main(String[] args) {
You you = new You();
Agent agent = new Agent(you);
agent.eat();
}
}
interface Action{
void eat();
}
//这是你,你只考虑吃饭
/*
* 为什么要有Action接口??
* 仅仅是约束You的行为
* */
class You implements Action{
@Override
public void eat() {
System.out.println("我是少爷,我只考虑吃饭!");
}
}
//我是少爷的代理
class Agent implements Action{
private You you;
public Agent(You you) {
this.you = you;
}
@Override
public void eat() {
cook();
you.eat();
wash();
}
//做饭
void cook(){
System.out.println("我做饭 .....");
}
//洗碗
void wash(){
System.out.println("我洗碗.....");
}
}
四、线程的五大状态
五、控制线程的常用方法
1.使用标志位来停止线程
public class Interrupt implements Runnable{
@Override
public void run() {
System.out.println("-----");
//判断当前线程的标志位是否为 true
if (Thread.interrupted()){
return;
}
System.out.println("-----");
}
public static void main(String[] args) {
Thread thread = new Thread(new Interrupt());
//中断该线程 中断标志位 true
thread.interrupt();
thread.start();
}
}
2.线程休眠 Thread.sleep()
- 在run()中调用sleep() 会使线程休眠,但不会释放锁对象
- 在下列代码中,synchronized(this)在for循环之外;就算一号线程休眠,其他线程也无法进入synchronized(this)内;即:sleep()不会释放当前线程的锁对象;但在休眠时,会放弃CPU资源
public class SleepTest implements Runnable {
private Integer number = 100;
@Override
public void run() {
synchronized (this) {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "---number---->" + number--);
if (number <= 0) {
return;
}
}
}
}
public static void main(String[] args) {
SleepTest sleepTest = new SleepTest();
new Thread(sleepTest,"一号").start();
new Thread(sleepTest,"二号").start();
new Thread(sleepTest,"三号").start();
}
}
3.线程礼让 yield()
- 理解: 线程A正在占用CPU资源,看见后面就绪的线程兄弟没抢到CPU资源。很不好意思,就说我礼让一下吧。Thread.yield();然后,线程A又变为就绪状态了。跟其他线程处在同一起跑线上。线程A说道:这一次我要是还被CPU看上,我就不礼让了哟。
public class YieldTest {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
synchronized (YieldTest.class) {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"====>"+i);
if (i == 5){
Thread.yield();
System.out.println(Thread.currentThread().getName()+"====>我礼让一下");
}
}
}
}
},"线程A").start();
}
}
4.线程插队join()
理解: 在线程B执行代码的时候,突然遇到这么一句:线程A.join() ;线程B就明白了,线程A要让我滚一边去。CPU给线程A运行。那好,我滚,你运行吧;等你运行好了,我再运行。
public class JoinTest {
public static void main(String[] args) {
Thread A = new Thread(new Runnable() {
@Override
public void run() {
synchronized (JoinTest.class) {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "====> " + i);
if (i == 10){
try {
Thread.sleep(10000);
System.out.println(Thread.currentThread().getName() + "说:我先睡眠10秒" );
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
},"线程A");
A.start();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "====> " + i);
if(i == 5){
System.out.println(Thread.currentThread().getName() + "我接下来会运行 A.join() ;把CPU资源给线程A");
try {
A.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
5.线程的等待wait()与唤醒notify() | notifyAll()
方法 | 描述 |
---|---|
sleep() | 静态方法、不释放锁对象、可以睡眠定时 |
wait() | 释放锁对象、没其他线程唤醒就会一直等待、对象锁.wait() |
notify() | 对象锁.notify()、随机唤醒一个在该锁中被等待的线程(是谁,无所谓!能运行这个锁对象内的代码就行) |
notifyAll() | 对象锁.notifyAll()、唤醒全部在该锁中等待的线程 |
public class WaitAndSleep {
public static void main(String[] args) {
Thread A = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
WaitAndSleep.notifyClass();
System.out.println(Thread.currentThread().getName()+"==>唤醒");
}
}
});
Thread B = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
WaitAndSleep.waitClass();
System.out.println(Thread.currentThread().getName()+"==>等待");
}
}
});
A.start();
B.start();
}
// 同步方法 其对象锁为: WaitAndSleep.class 即 本类.class
public static synchronized void notifyClass() {
WaitAndSleep.class.notify();
}
public static void waitClass() {
synchronized (WaitAndSleep.class) {
try {
WaitAndSleep.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
注意:
- notity()、notifyAll()、wait()都必须在同一个锁对象synchronized(锁对象) 中,不然会报异常:IllegalMonitorState
六、Lock锁
- Lock是显示锁,手动开启关闭,synchronized(锁对象){} 是隐式锁,出了作用域会自动释放
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好;并且具有更好的扩展性(有很多子类)
- 性能:lock锁 > 同步代码块> 同步方法
import java.util.concurrent.locks.ReentrantLock;
public class LockTest implements Runnable {
//定义Lock锁
private final ReentrantLock lock = new ReentrantLock();
//定义票数
private static Integer ticket = 10;
//标志
private static boolean flg = true;
//定义buy购买方法
public void buy() throws InterruptedException {
if (ticket > 0){
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "买了一张票,还剩 " + ticket--);
}else {
flg = false;
}
}
@Override
public void run() {
while(flg){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
LockTest test = new LockTest();
new Thread(test,"1号柜台").start();
new Thread(test,"2号柜台").start();
new Thread(test,"3号柜台").start();
}
}