09 - JavaSE之线程
线程
线程的基本概念
线程是一个程序里面不同的执行路径。
- 进程与线程的区别
- 每个进程都有独立的代码和数据空间(进程上下文),进程间的切换开销大。
- 线程可以看作轻量级的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程的切换开销小。
- 多进程:在操作系统中能同时运行多个程序。
- 多线程:在同一应用程序中有多个顺序流同时执行。
线程的创建与启动
- Java 的线程是通过 java.lang.Thread 类来实现的。
- VM 启动时,会有一个由主方法 main 所定义的线程。
- 可以通过创建 Thread 的实例来创建新的线程。
- 每个线程都是通过某个特定的 Thread 对象所对应的 run() 方法来完成这个线程要做的任务,方法 run() 成为线程体。
- 通过调用 Thread 类的 start() 方法来启动一个线程。
- 有两种方法创建新的线程:
第一种(推荐使用):定义线程类实现 Runnable 接口,然后重写 run 方法, 然后以这个线程类创建 Thread 类,然后调用这个 Thread 类的 start() 方法,就可以开始执行这个线程,这个线程具体要执行的内容在 run 方法里面。
public class Test {
public static void main(String[] args) {
MyThread mt = new MyThread();
Thread th = new Thread(mt);
th.start();for(int i=0; i<100; i++) {
System.out.println("Main: " + i);
}
}
}
class MyThread implements Runnable {
@Override
public void run() {
for(int i=0; i<100; i++) {
System.out.println("MyThread:" + i);
}
}
}
PS: 如果我们没有 new一个 Thread 对象出来,而是直接使用 MyThread 的 run 方法(mt.run()),这就是方法调用,而不是启动线程了,结果就是先后打印语句,而不是并行打印语句了。
第二种:可以定义一个 Thread 的子类 myThread,并且重写 Thread 的 run 方法(Thread 也实现了Runnable 接口),然后生成子类 myThread 的对象,最后调用子类 myThread 对象的 start() 方法即可。
public class Test {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();for(int i=0; i<100; i++) {
System.out.println("------ " + i);
}
}
}
class MyThread extends Thread {
@Override
public void run() {
for(int i=0; i<100; i++) {
System.out.println("MyThread:" + i);
}
}
}
线程的状态转换
线程控制基本方法
isAlive() // 判断线程是否还活着,即线程是否还未终止
getPriority() // 获得线程的优先级数值
setPriority() // 设置线程的优先级数值
Thread.sleep(...) // 将当前线程指定睡眠时间
join() // 将一个线程合并到某个线程上,成为一个线程执行
yield() // 让出CPU,当前线程进入就绪队列等待调度
wait() // 当前线程进入对象的 wait pool
notify()/notifyAll() // 唤醒对象 wait pool中的一个/所有等待线程
- sleep 方法
可以调用 Thread 的静态方法 Thread.sleep(long ms) 使得当前线程休眠。(哪个线程调用了Thread.sleep 方法,哪个线程就 sleep)
import java.util.*;
public class Test {
public static void main(String[] args) {
MyThread mt = new MyThread();
Thread th = new Thread(mt);
th.start();
try {
Thread.sleep(10000);
} catch(InterruptedException e) {
}
th.interrupt();
}
}
class MyThread implements Runnable {
private boolean flag = true;
@Override
public void run() {
while(flag) {
System.out.println("=== " + new Date() + " ===");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
flag = false;
}
}
}
}
- join 方法
将一个线程合并到某个线程上,成为一个线程执行。(如下程序就先执行线程 th ,之后才会执行主线程 Main ,而不是并行执行。)
import java.util.*;
public class Test {
public static void main(String[] args) {
MyThread mt = new MyThread();
Thread th = new Thread(mt);
th.start();
try {
th.join();
} catch(InterruptedException e) {
}
for(int i=0; i<10; i++) {
System.out.println("Main Thread......");
}
th.interrupt();
}
}
class MyThread implements Runnable {
@Override
public void run() {
for(int i=0; i<10; i++) {
System.out.println("=== " + new Date() + " ===");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
}
}
- yield 方法
让出CPU,当前线程进入就绪队列等待调度。
public class Test {
public static void main(String[] args) {
MyThread mt1 = new MyThread("mt1");
MyThread mt2 = new MyThread("mt2");
mt1.start();
mt2.start();
}
}
class MyThread extends Thread {
MyThread(String name) {
super(name);
}
@Override
public void run() {
try {
for (int i = 0; i < 50; i++) {
System.out.println(getName() + " --- " + i);
Thread.sleep(100);
if (i % 10 == 0) {
Thread.yield();
}
}
} catch (InterruptedException e) {
}
}
}
线程的优先级
Java 提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程。线程调度器按照线程的优先级决定应调度哪个线程来执行。
线程的优先级用数字表示,范围从 1 到 10,一个线程的缺省优先级是 5。
Thread.MIN_PRIORITY = 1
Thread.MAX_PRIORITY = 10
Thread.NORM_PRIORITY = 5使用下面方法获得或设置线程对象的优先级:
int getPriority();
void setPriority(int newPriority);
Java 中的线程临界区
- 系统中每次只允许一个线程访问的资源叫做临界资源。
- 对临界资源进行访问的程序代码区域叫做临界区。
- Java 中通过 synchronized 关键字和对象锁机制对临界区进行管理。
- Java 中的每个对象都可以作为对象锁使用。
线程同步
我们先看这样一个程序:
public class Test implements Runnable {
Timer time = new Timer();
public static void main(String args[]){
Test test = new Test();
Thread t1 = new Thread(test);
Thread t2 = new Thread(test);
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
@Override
public void run() {
time.add(Thread.currentThread().getName());
}
}
class Timer {
private static int num = 0;
public void add(String name) {
num ++;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
System.out.println(name + " 是第 " + num + " 个使用timer的线程");
}
}
我们两个线程同时执行,而且调用同一个方法,相当于访问同一个共享资源 num,执行的结果为:
t2 是第 2 个使用timer的线程
t1 是第 2 个使用timer的线程
这是怎么回事呢?按照猜想,虽然 t1 t2 线程并行执行,但是先开启的 t1 进程,num = 1;在开启的 t2 线程,num = 2,所以,应该是:t1 是第 1 个使用timer的线程;t2 是第 2 个使用timer的线程 才对啊?
其实,这就涉及到线程同步的问题,如果在一个线程访问一个共享对象的时候没有给这个共享资源上锁的话,那么这个线程操作的共享资源可能就是错误的,因为可能别的进程也在访问这个共享资源。
那么,我们就需要在进程访问这个共享资源的时候,将其上锁,上锁的方式有两种:(还是以上面的程序为例:)
// 方式一
class Timer {
private static int num = 0;
public void add(String name) {
synchronized (this) {} { // 资源上锁
num ++;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
System.out.println(name + " 是第 " + num + " 个使用timer的线程");
}
}
}
synchronized (this) {
// 需要锁定的内容
}
synchronized(this)表示: 锁定当前对象。括号中的语句在一个线程执行的过程中,不会被另一个线程打断。
// 方式二
class Timer {
private static int num = 0;
synchronized public void add(String name) { // 资源上锁
num ++;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
System.out.println(name + " 是第 " + num + " 个使用timer的线程");
}
}
在 add 函数加上 synchronized 关键字,就表示哪个进程调用了add 函数,那么这个进程在执行这个方法的时候,锁定当前对象。
- 在 Java 语言中引入了对象互斥锁的概念,保证共享数据操作的完整性。每个对象都对应于一个可称为“互斥锁”的标记,这个标记保证在任意时刻,智能有一个线程访问该对象。
- 关键字 synchronized 来与对象的互斥锁联系。当某个对象 synchronized 修饰时,表明该对象在任一时刻只能由一个线程访问。
线程死锁
什么是线程死锁?
如果两个或多个线程分别拥有不同的资源, 而同时又需要对方释放资源才能继续运行时,就会发生死锁。简单来说:死锁就是当一个或多个进程都在等待系统资源,而资源本身又被占用时,所产生的一种状态。
public class Test implements Runnable {
public int flag = 0;
static Object o1 = new Object();
static Object o2 = new Object();
public static void main(String args[]){
Test test1 = new Test();
Test test2 = new Test();
test1.flag = 1;
test2.flag = 2;
Thread t1 = new Thread(test1);
Thread t2 = new Thread(test2);
t1.start();
t2.start();
}
public void run() {
System.out.println("flag=" + flag);
if(1 == flag) {
synchronized (o1) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (o2) {
System.out.println("1");
}
}
}
if(2 == flag) {
synchronized (o2) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (o1) {
System.out.println("2");
}
}
}
}
}
注意:以上代码中 Object 对象 O1 O2 一定是 static 的,否则不能得到进程死锁。还有一定要有 Thread.sleep 语句。
举例:
public class Test implements Runnable {
public int b = 100;
public synchronized void m1() throws Exception{
b = 1000;
Thread.sleep(3000);
System.out.println("m1:b = " + b);
}
public void m2 () {
System.out.println("m2:b = " + b);
b = 2;
System.out.println("m2:b = " + b);
}
public void run() {
try {
m1();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
Test tt = new Test();
Thread t = new Thread(tt);
t.start();
Thread.sleep(1000);
tt.m2();
System.out.println("b = " + tt.b);
}
}
提问:各个打印 b 的值为多少?
m2:b = 1000
m2:b = 2
b = 2
m1:b = 2
为什么 b 的值可以被修改呢?
因为,如果一个方法加了 synchronized ,而且有一个进程正在访问这个方法,那么只能说明别的进程不可以同时访问这个方法,但是并不妨碍别的进程访问其他的方法,如果其他的方法中有对你需要保护的对象(这里是 b)进行操作的话,也是允许的。
所以,如果要保护一个需要同步的对象的话,对访问这个对象的所有方法考虑加不加 synchronized 。因为一个方法加了锁,只是另一个线程不能访问加了锁的方法,但是可以访问其他的方法,其他的方法可能修改了你需要同步的对象。synchronized 修饰的方法可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法。(所以,上面的 m2 方法也需要加 synchronized 修饰。)
生产者和消费者问题:
/**
* 这里模拟的是生产者和消费者的问题:这里生产者生产樱花膏,消费者吃樱花膏。然后
* 还有一个装樱花膏的篮子 basket. 我们需要做的生产者生产的樱花膏按照顺序放入篮子,
* 吃货消费者按照顺序从顶部依次拿出来吃,所以 basket 的数据结构是栈。
*
* 注意:生产者生产的樱花膏要和吃货需要吃的樱花膏数量一致,否则程序会wait
*/
public class Test {
public final int NUM = 10;
public static void main(String[] args) {
BasketStack bs = new BasketStack();
Producer p = new Producer(bs);
Consumer c = new Consumer(bs);
new Thread(p).start();
new Thread(p).start();
new Thread(p).start();
new Thread(c).start();
}
}
// 樱花膏
class Sakura {
private int id = 0;
Sakura (int id) {
this.id = id;
}
@Override
public String toString() {
return ((Integer)id).toString();
}
}
// 篮子
class BasketStack {
int index = 0;
Sakura[] sakuras = new Sakura[6]; // 一个篮子只能装6个樱花膏
public synchronized void push(Sakura sa) {
while(index == (sakuras.length - 1)) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notifyAll();
sakuras[index] = sa;
index ++;
}
public synchronized Sakura pop() {
while(index == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notifyAll();
index--;
return sakuras[index];
}
}
// 生产者
class Producer implements Runnable {
BasketStack bs = null;
Producer(BasketStack bs) {
this.bs = bs;
}
@Override
public void run() {
for(int i=0; i<10; i++) {
Sakura sa = new Sakura(i);
bs.push(sa);
System.out.println("生产者:" + sa.toString());
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 吃货
class Consumer implements Runnable {
BasketStack bs = null;
Consumer(BasketStack bs) {
this.bs = bs;
}
@Override
public void run() {
for(int i=0; i<30; i++) {
System.out.println("吃货:" + bs.pop().toString());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
wait和sleep的区别:
1.wait之后 锁就不归我所有,别的线程可以访问锁定对象;sleep后 锁还是我的,别的线程不可以访问锁定对象。
2.调用 wait方法的时候必须先锁定对象,否则谈何wait。
2.wait是Object的方法,sleep是Thread的方法。
总结(关键字联想)
- 线程/进程的概念
- 创建和启动线程的方式
- sleep
- join
- yield
- synchronized
- wait
- notify/notifyAll
09 - JavaSE之线程的更多相关文章
- JavaSE中线程与并行API框架学习笔记——线程为什么会不安全?
前言:休整一个多月之后,终于开始投简历了.这段时间休息了一阵子,又病了几天,真正用来复习准备的时间其实并不多.说实话,心里不是非常有底气. 这可能是学生时代遗留的思维惯性--总想着做好万全准备才去做事 ...
- JavaSE中线程与并行API框架学习笔记1——线程是什么?
前言:虽然工作了三年,但是几乎没有使用到多线程之类的内容.这其实是工作与学习的矛盾.我们在公司上班,很多时候都只是在处理业务代码,很少接触底层技术. 可是你不可能一辈子都写业务代码,而且跳槽之后新单位 ...
- javaSE之线程联合
首先定义 : 一个线程A在占有CPU资源期间 ,可以让其他线程调用join()和本线程联合. 嗯哈,像书本这个列子: 如: B.join(); 我们称A在运行期间联合了B, 如果线程A在占有CPU资源 ...
- python 自动化之路 day 09 进程、线程、协程篇
本节内容 操作系统发展史介绍 进程.与线程区别 python GIL全局解释器锁 线程 语法 join 线程锁之Lock\Rlock\信号量 将线程变为守护进程 Event事件 queue队列 生产者 ...
- Java并发基础09. 多个线程间共享数据问题
先看一个多线程间共享数据的问题: 设计四个线程,其中两个线程每次对data增加1,另外两个线程每次对data减少1. 从问题来看,很明显涉及到了线程间通数据的共享,四个线程共享一个 data,共同操作 ...
- Python_进程、线程及协程
一.Python进程 IO密集型----多线程 计算密集型----多进程 1.单进程 from multiprocessing import Process def foo(i): print('你好 ...
- SchuledExecutorService 使用controller控制线程关闭
1:SchuledExecutorService 使用controller控制线程关闭 package com.li.controller; import org.springframework.s ...
- 《Cracking the Coding Interview》——第16章:线程与锁——题目1
2014-04-27 19:09 题目:线程和进程有什么区别? 解法:理论题,操作系统教材上应该有很详细的解释.我回忆了一下,写了如下几点. 代码: // 16.1 What is the diffe ...
- 马士兵Java视频教程 —— 学习顺序
第一部分:J2se学习视频内容包括: 尚学堂科技_马士兵_JAVA视频教程_JDK5.0_下载-安装-配置 尚学堂科技_马士兵_JAVA视频教程_J2SE_5.0_第01章_JAVA简介_源代码_及重 ...
随机推荐
- faceswap linux安裝教程
http://www.mamicode.com/info-detail-2602743.html https://blog.csdn.net/sinat_26918145/article/detail ...
- SSH:Struts + Spring + Hibernate 轻量级Java EE企业框架
Java EE(Java Platform,Enterprise Edition)是sun公司(2009年4月20日甲骨文将其收购)推出的企业级应用程序版本.这个版本以前称为 J2EE.能够帮助我们开 ...
- (动态规划 最长有序子序列)Monkey and Banana --HDU --1069
链接: http://acm.hdu.edu.cn/showproblem.php?pid=1069 http://acm.hust.edu.cn/vjudge/contest/view.action ...
- hdu 1300 Deck
题目 分析:对于n张卡片的最佳摆法,我们只需要在n-1张卡片的摆法下面加一张边缘与桌檐重合的卡片,并将所有卡片一起向桌檐外移动.对于一种最佳摆法,其中心一定在桌檐上,所以一定符合杠杆原理,支点是桌檐. ...
- hdu 5037 周期优化
http://acm.hdu.edu.cn/showproblem.php?pid=5037 有只青蛙踩石子过河,河宽m,有n个石子坐标已知.青蛙每次最多跳L.现在可以在河中再放一些石子,使得青蛙过河 ...
- SignalR简单封装
需求:Asp.Net MVC 开发客户端,实现与服务器端实时通信. 众所周知,Web开发是基于http的请求响应模型,每次刷新都需要客户端(浏览器)主动发起请求,那么,这个问题怎么解?Asp.Net ...
- 深入理解Aspnet Core之Identity(3)
主题 账户管理一个比较常见的功能就是密码强度策略,Identity已经内置了一个通用的可配置的策略,我们一般情况下可以直接拿来用即可.本篇我会介绍一些Identity内置的密码策略类:Password ...
- C# GDI绘制波形图
直接上效果图如下 public partial class WaveChartUserCtrl : UserControl { Color axisColor = Color.FromArgb(69, ...
- AndroidStudio的一些快捷键的使用
1.返回上一次浏览快捷键的设置 https://blog.csdn.net/yingtian648/article/details/73277388 2.格式化代码的快捷键的设置 htt ...
- 标准的sql执行顺序
正常情况下是先join再进行where过滤