目 录
进程、线程介绍
<https://blog.csdn.net/smile_Running/article/details/86745128#%E8%BF%9B%E7%A8%8B%E3%80%81%E7%BA%BF%E7%A8%8B%E4%BB%8B%E7%BB%8D>
程序、进程、线程概念
<https://blog.csdn.net/smile_Running/article/details/86745128#%E7%A8%8B%E5%BA%8F%E3%80%81%E8%BF%9B%E7%A8%8B%E3%80%81%E7%BA%BF%E7%A8%8B%E6%A6%82%E5%BF%B5>
何时应用多线程?
<https://blog.csdn.net/smile_Running/article/details/86745128#%E4%BD%95%E6%97%B6%E5%BA%94%E7%94%A8%E5%A4%9A%E7%BA%BF%E7%A8%8B%EF%BC%9F>
实现方式
<https://blog.csdn.net/smile_Running/article/details/86745128#%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F>
一、继承Thread类
<https://blog.csdn.net/smile_Running/article/details/86745128#%E4%B8%80%E3%80%81%E7%BB%A7%E6%89%BFThread%E7%B1%BB>
二、实现Runnable接口
<https://blog.csdn.net/smile_Running/article/details/86745128#%E4%BA%8C%E3%80%81%E5%AE%9E%E7%8E%B0Runnable%E6%8E%A5%E5%8F%A3>
线程重要内容
<https://blog.csdn.net/smile_Running/article/details/86745128#%E7%BA%BF%E7%A8%8B%E9%87%8D%E8%A6%81%E5%86%85%E5%AE%B9>
一、常用方法
<https://blog.csdn.net/smile_Running/article/details/86745128#%E4%B8%80%E3%80%81%E5%B8%B8%E7%94%A8%E6%96%B9%E6%B3%95>
二、线程调度
<https://blog.csdn.net/smile_Running/article/details/86745128#%E4%BA%8C%E3%80%81%E7%BA%BF%E7%A8%8B%E8%B0%83%E5%BA%A6>
1、设置线程优先级
<https://blog.csdn.net/smile_Running/article/details/86745128#1%E3%80%81%E8%AE%BE%E7%BD%AE%E7%BA%BF%E7%A8%8B%E4%BC%98%E5%85%88%E7%BA%A7>
2、yieid()、join()、sleep()
<https://blog.csdn.net/smile_Running/article/details/86745128#2%E3%80%81yieid()%E3%80%81jion()%E3%80%81sleep()>
三、线程生命周期
<https://blog.csdn.net/smile_Running/article/details/86745128#%E4%B8%89%E3%80%81%E7%BA%BF%E7%A8%8B%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F>
四、线程同步(synchronized)
<https://blog.csdn.net/smile_Running/article/details/86745128#%E5%9B%9B%E3%80%81%E7%BA%BF%E7%A8%8B%E5%90%8C%E6%AD%A5%EF%BC%88synchronized%EF%BC%89>
1、同步代码块
<https://blog.csdn.net/smile_Running/article/details/86745128#1%E3%80%81%E5%90%8C%E6%AD%A5%E4%BB%A3%E7%A0%81%E5%9D%97>
2、同步方法
<https://blog.csdn.net/smile_Running/article/details/86745128#2%E3%80%81%E5%90%8C%E6%AD%A5%E6%96%B9%E6%B3%95>
五、死锁
<https://blog.csdn.net/smile_Running/article/details/86745128#%E4%BA%94%E3%80%81%E6%AD%BB%E9%94%81>
六、线程通信
<https://blog.csdn.net/smile_Running/article/details/86745128#%E5%85%AD%E3%80%81%E7%BA%BF%E7%A8%8B%E9%80%9A%E4%BF%A1>
©本文由博主原创,未经允许,不得转载相关博文内容
*
进程、线程介绍
多线程编程是我们图形化操作系统的基本要求,比如之前的DOS操作系统,它以命令行的形式来获取用户行为,这种方式比较单一,程序在同一时间内也不会去做其他工作。再比如现在的Windows操作系统、Linux系统也罢,只要是提供丰富的图形化界面的操作系统,程序就不会局限于单一的工作。
而多线程编程正式为了解决这个问题,如在同一个进程内,比如QQ,我可以一边聊天,一边去下载群里的文件,同时也可以一边上传文件。这就用到了多线程的技术,让程序不局限于单一的工作,利用多余的CPU资源去同时工作,提升用户的体验,这也是图形化系统提升用户体验的最佳实践。
而进程却和线程有所不同,比如我可以一边写博客(浏览器)、一边听歌(网易云)、一边聊天(QQ、微信)。这里用到了多个不同的程序
,每个程序都互相独立的工作,在没有进程通信时,大多情况下都不会影响对方工作。我们可以打开任务管理器,可以看到操作系统下的大量进程在同时工作,这就是多进程的概念。
*
程序、进程、线程概念
* 程序(program),是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
* 进程(process),是程序的一次执行过程,或是正在运行的一个程序。动态过程:有它自身的产生、存在和消亡的过程。
* 线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。 若一个程序可同一时间执行多个线程,就是支持多线程的
*
何时应用多线程?
* 程序需要同时执行两个或多个任务。
* 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
* 程序需要一些后台运行的程序时。
*
实现方式
一、继承Thread类
步骤:
* 定义子类继承Thread类
* 子类中重写Thread类中的run方法
* 创建Thread子类对象,即创建了线程对象
* 调用线程对象start方法:启动线程,调用run方法
代码:
public class TestThread { public static void main(String[] args) { MyThread1
th1 = new MyThread1(); th1.start(); /** * 主线程 */ for (int i = 10; i >= 0; i--)
{ System.out.println("Main-" + i); } } } class MyThread1 extends Thread {
public MyThread1() { } @Override public void run() { for (int i = 10; i >= 0;
i--) { System.out.println("MyThread1-" + i); } } }
二、实现Runnable接口
步骤:
* 定义子类,实现Runnable接口
* 子类中重写Runnable接口中的run方法
* 通过Thread类含参构造器创建线程对象
* 将Runnable接口的子类对象作为实际参数传递给 Thread 类的构造方法中
* 调用Thread类的start方法:开启线程,调用 Runnable子类接口的run方法
代码:
public class TestThread { public static void main(String[] args) { MyThread2
th2 = new MyThread2(); Thread thread = new Thread(th2); thread.start(); /** *
主线程 */ for (int i = 10; i >= 0; i--) { System.out.println("Main-" + i); } } }
class MyThread2 implements Runnable { public void run() { for (int i = 10; i >=
0; i--) { System.out.println("MyThread2-" + i); } } }
实现 Runnable 接口的优点:
* Java 是单继承的,用实现接口的方式可以避免单继承的局限问题
* 只需 new 一个实现 Runnable 接口的实例,保证了可以共享同一份资源
*
线程重要内容
一、常用方法
* void start(); 启动线程,并执行对象的run()方法
* run(); 线程在被调度时执行的操作
* String getName(); 返回线程的名称
* void setName(String name); 设置该线程名称
* static currentThread(); 返回当前线程
修改如上代码:
public class TestThread { public static void main(String[] args) { /** * 继承
Thread 的方式 */ MyThread1 th1 = new MyThread1(); th1.setName("==th1==");
th1.start(); /** * 实现 Runnable 接口的方式 */ MyThread2 th2 = new MyThread2(); Thread
thread = new Thread(th2); thread.setName("==th2=="); thread.start(); /** * 主线程
*/ for (int i = 10; i >= 0; i--) {
System.out.println(Thread.currentThread().getName() + ":" + i); } } } class
MyThread1 extends Thread { public MyThread1() { } @Override public void run() {
for (int i = 10; i >= 0; i--) {
System.out.println(Thread.currentThread().getName() + ":" + i); } } } class
MyThread2 implements Runnable { public void run() { for (int i = 10; i >= 0;
i--) { System.out.println(Thread.currentThread().getName() + ":" + i); } } }
二、线程调度
Java对线程的调度方法:
* 对于同优先级线程,组成一个队列,以先进先出的方式抢占CPU资源
* 对于高优先级的线程,赋予优先的抢占式资源(但是也不是绝对的能够抢到)
1、设置线程优先级
线程的优先级分为三个等级,分别为 MAX_PRIORITY(10); MIN _PRIORITY (1); NORM_PRIORITY
(5);通过:
* getPriority() :返回线程优先值,默认为5
* setPriority(int newPriority) :改变线程的优先级,线程创建时继承父线程的优先级
2、yieid()、join()、sleep()
* yield():线程让步,暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程。若队列中没有同优先级的线程,忽略此方法。
* join() :当某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到 join() 方法加入的 join
线程执行完为止,低优先级的线程也可以获得执行 。
* sleep(long millis)(毫秒) : 令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。
* isAlive():判断线程是否还活着
例子:实现有三个人同时去银行取款机取钱,这三个同时操作。设银行共有10000元,第一人取1500,第二人取2000,第三人取3000,结算银行剩余多少钱?
这个问题比较简单,但是存在一个bug,线程抢夺cpu资源的问题。如果第一个人在取的时候,恰巧cpu资源权被第二个人抢了,那就造成问题。
public class TestThread { public static void main(String[] args) { Bank bank =
new Bank(); bank.getMoney(1500); Thread person1 = new Thread(bank);
person1.setName("==person1=="); person1.start(); try { person1.join(); } catch
(InterruptedException e) { e.printStackTrace(); } bank.getMoney(2000); Thread
person2 = new Thread(bank); person2.setName("==person2=="); person2.start();
try { person2.join(); } catch (InterruptedException e) { e.printStackTrace(); }
bank.getMoney(3000); Thread person3 = new Thread(bank);
person3.setName("==person3=="); person3.start(); try { person3.join(); } catch
(InterruptedException e) { e.printStackTrace(); } } } class Bank implements
Runnable { int totalMoney = 10000; int money;// 要取出的钱 public void getMoney(int
money) { this.money = money; } public void run() { String name =
Thread.currentThread().getName(); System.out.println(name + "取出:" + money); int
remainMoney = totalMoney - money; totalMoney = remainMoney;
System.out.println("银行剩余:" + remainMoney); } }
* 正常执行结果等于3500:
如果注释上面代码中线程的 join() 方法
,意味着三个人对cpu的获取权一样大,比如第一个人取到一半,执行权被第二个人给抢了,这就会导致金钱出现异常。
* 注释全部 join() 后执行结果:
三、线程生命周期
四、线程同步(synchronized)
线程同步指的是同一个线程来操作同一份资源,那不同步就有线程安全问题了。在线程并发时,如果不同的多个线程同时操作同一封资源的话,那将会造成数据紊乱。
举个实际中的例子,
我在乘坐高铁时想上厕所,这时厕所显示绿色,发现厕所没人用,我就进去了,却不小心门没有关紧。这时又来了一位想上厕所的人,由于门没关好,厕所上面的灯是绿色的,所以这位后面来的人就开门进来了,这就导致厕所紊乱了。
用这个例子反证线程的执行过程,简直一模一样。这个厕所,就如线程处理的同一份资源。多个人就对应多个线程,在同时处理一份资源时,问题就来了。
互斥锁(synchronized)
,这是一个关键字。作用在同一份资源上时,就是相当于厕所上面的指示器的作用,给这个资源加上一把锁,你其他线程不许进来,等我处理结束后再说。
看一个例子:
一家电影院有三个售票窗口,这部电影共有30个座位(30张票)。如果三个窗口同时卖票,则该如何操作?
public class TestThread { public static void main(String[] args) { Cinema
cinema = new Cinema(); Thread window1 = new Thread(cinema);
window1.setName("==窗口1=="); window1.start(); Thread window2 = new
Thread(cinema); window2.setName("==窗口2=="); window2.start(); Thread window3 =
new Thread(cinema); window3.setName("==窗口3=="); window3.start(); } } class
Cinema implements Runnable { int ticket = 30; public void run() { String name =
Thread.currentThread().getName(); while (true) { if (ticket > 0) { try {
Thread.currentThread().sleep(50); } catch (InterruptedException e) {
e.printStackTrace(); } System.out.println(name + "售出" + ticket-- + "号座位"); } }
} }
从打印结果可以看出,不仅出现了重复座位,而且还出现了 0 号座位,存在极大的bug。出现bug的原因:
多个线程参与同一个数据的操作,如上代码中,多个线程同时卖30张票,却没有给操作同一个资源加锁,就会出现这种bug。
1、同步代码块
修改 run(),添加 synchronized(Object obj) 关键字。这里一般传入 this ,this 即 Cinema 类的对象。
class Cinema implements Runnable { int ticket = 30; public void run() { while
(true) { synchronized (this) { if (ticket > 0) { try {
Thread.currentThread().sleep(50); } catch (InterruptedException e) {
e.printStackTrace(); } System.out.println(Thread.currentThread().getName() +
"售出" + ticket-- + "号座位"); }else { break; } } } } }
2、同步方法
class Cinema implements Runnable { int ticket = 30; boolean flag = true;
public void run() { while (flag) { sell(); } } public synchronized void sell()
{ if (ticket > 0) { try { Thread.currentThread().sleep(50); } catch
(InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "售出" + ticket-- + "号座位");
flag = true; } else { flag = false; } } }
同步代码块、同步方法都可以决解线程安全问题。其实,线程安全的单利模式也是可以的,只要保证操作资源的线程同一时间内是唯一的就可以了。
结果正常:
注意:线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,此时并不会释放锁。
五、死锁
说到调用 sleep() 方法不会释放锁,那么如果多个线程同时操作对方的资源,谁都不愿意释放的话,那程序就会停止,就会造成死锁的情况了。
死锁就是不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
举个实际例子:
有一天,老王和老张在工地里吃饭,恰巧剩下最后一双筷子。老王拿了一根,老张拿了一根(就当这俩有毛病吧)。于是老张在等待老王放弃筷子,那么老王也想让老张先放弃。这时,他们俩就抢了起来啊,然后咔嚓一声,其中一根断了(意味着程序bug),那么这俩货就这样僵持住了,谁也吃不了。
死锁代码:
public class TestDeadlock implements Runnable { Zhang zhang = new Zhang();
Wang wang = new Wang(); public void init() { zhang.waitting(wang); } public
static void main(String[] args) { System.out.println("老张、老王各有一根筷子");
TestDeadlock dl = new TestDeadlock(); new Thread(dl).start(); dl.init(); }
@Override public void run() { wang.waitting(zhang); } } class Zhang { public
synchronized void waitting(Wang wang) { try {
Thread.currentThread().sleep(200); } catch (InterruptedException e) {
e.printStackTrace(); } wang.eat(); } public synchronized void eat() {
System.out.println("老张想吃饭,等待老王给筷子"); } } class Wang { public synchronized void
waitting(Zhang zhang) { zhang.eat(); } public synchronized void eat() {
System.out.println("老王想吃饭,等待老张给筷子"); } }
死锁的原因就是不同的线程分别占了对方也需要的资源,这时谁也不肯退让,导致程序停止。我们不可能去专门编写死锁,但出现死锁时就要我们去解救。解决方法:专门的算法、原则。或者尽量减少同步资源的定义。
六、线程通信
线程的通信,通过wait() 与 notify() 和
notifyAll()三个方法实现。所谓通信,就是某一个线程被wait()之后,其他线程通过notify()和notifyAll()将其唤醒。wait()不同于sleep(),这一点很重要。sleep()方法可以通过自定义的一段时间后自动唤醒,而wait()只能被notify的时候才可以苏醒,否则线程将进入停滞状态。
* wait():令当前线程挂起并放弃CPU、同步资源,使别的线程可访问并修改共享资源,当前线程排队等候再次对资源的访问
* notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
* notifyAll ():唤醒正在排队等待资源的所有线程结束等待
例子:打印整数,实现单双号交替
public class TestThread3 { public static void main(String[] args) { MyThread
myThread = new MyThread(); Thread th1 = new Thread(myThread);
th1.setName("==单数=="); th1.start(); Thread th2 = new Thread(myThread);
th2.setName("==双数=="); th2.start(); } } class MyThread implements Runnable {
int count = 21; public void run() { while (true) { synchronized (this) {
notify(); if (count > 0) { System.out.println(Thread.currentThread().getName()
+ count--); } else { break; } try { wait(); } catch (InterruptedException e) {
e.printStackTrace(); } } } } }
到此为止,线程的几个内容已经基本讲完了。掌握这些线程的知识点,可以开发出更加高效的软件,多线程编程也是能写出更高效软件的一种手段。
©原文链接:https://blog.csdn.net/smile_Running/article/details/86745128
<https://blog.csdn.net/smile_Running/article/details/86745128>
©作者博客 ID:smile_running <https://blog.csdn.net/smile_running>
热门工具 换一换