@[TOC](浅谈Java中的线程(一))
-
线程和进程
-
线程:
a)线程是进程的的一个顺序执行流(程序级概念);
b)同类的多个线程共享一块内存空间和同一组系统资源,线程本身有一个供程序执行时的堆栈。
c)由于线程在切换时负荷小,因而又叫做轻负荷进程;
d)一个线程只可以归属于一个进程。
2.进程:
a)进程是操作系统中运行的一个任务(系统级概念);
b)进程是包含程序运行所需资源的内存单元;
c)进程是一些线程的集合体,一个进程至少包含一个线程;
d)进程拥有一个私有的虚拟地址空间,但只能被进程包含的线程访问。
-
线程适用场合:
a)程序需要同时完成多个任务;
b)可以使用单线程完成的但是利用多线程可以更快的完成。
-
并发原理:
a)在感官上表现出来多个线程“同时”运行,但是实际上是OS将时间分割成多个很小的时间片,尽可能均匀的分配给每个线
程,让拿到时间片的线程在CPU上运行而其他线程等待
b)线程状态图
-
线程的创建:
a)用Thread创建线程
1)继承Thread类(线程类),创建时应重写run方法;
public class ThreadDome1 {
public static void main(String[] args) {
Thread t = new Mythread();
Thread t1 = new Mythread1();
/*
* 当start方法执行后,线程纳入线程调度。
* 一旦被分配CPU时间片,线程就会自动调用run方法
*
*/
t.start(); //线程启动时调用start方法
t1.start();
}
}
public class Mythread extends Thread{
public void run() {
for (int i=0;i<1000;i++) {
System.out.println("你好啊!");
}
}
}
public class Mythread1 extends Thread1{
public void run() {
for(int i=0;i<1000;i++) {
System.out.println("好什么好,一点都不好!");
}
}
}
2)使用内部类来创建,重写run方法<-代码块->ThreadDome1-3
语法:Thread t = new Thread(){public void run(){/*代码块*/}};
Thread t = new Thread() {
public void run() {
for(int i=0;i<1000;i++) {
System.out.println("线程Dome1");
}
}
};
result:输出1000遍-线程Dome1
b)用Runnable创建线程
1)实现Runnable接口,重写run方法
public class ThreadDome {
MyRunnable R = new MyRunnable();
MyRunnable1 R2 = new MyRunnable1();
Thread A = new Thread(R);
Thread B = new Thread(R2);
}
class MyRunnable implements Runnable{
public void run() {
for (int i=0;i<1000;i++) {
System.out.println("你是谁啊!");
}
}
}
class MyRunnable1 implements Runnable{
public void run() {
for(int i=0;i<1000;i++ ) {
System.out.println("你猜我是谁");
}
}
}
2)使用内部类创建,重写run方法<-代码块->ThreadDome1-3
语法:Runnable r = new Runnable(){public void run(){/*代码片段*/}};
Runnable r = new Runnable() {
public void run() {
for(int i=0;i<1000;i++) {
System.out.println("线程Dome2");
}
}
};
Thread t = new Thread(r);
c)两种线程创建方法的优缺点
方法 | 优点 | 缺点 |
Runnable | 耦合低、可以多继承 | 结构较为复杂 |
Thread | 结构清晰 | 1.继承冲突:由于Java是单继承的,在实际开发中为了可以复用方法我们通常会继承某个超类, 但如果此时当前类需要继承的特性时就会导致无法同时继承这两个类 2.继承线程并重写run方法来定义线程会导致线程与任务存在一个必然的耦合关系,这不利于线程的重用 |
-
线程相关API
long getId():返回该线程的标示
String getName():返回该线程的名字
int getPriority():返回线程的优先级
boolean isAlive():检测线程是否处于活动状态
boolean isInterrupted():检测线程是否中断
boolean isDaemon():检测线程是否为守护线程
public class ThreadInfoDome {
public static void main(String[] args) {
Thread main = Thread.currentThread();
String name = main.getName();//获取线程名字
long id = main.getId(); //获取线程ID
int priority = main.getPriority();//获取线程优先级
System.out.println("Name:"+name+" ID:"+id+" Priority:"+priority);
//显示线程是否处于活动状态
boolean isAlive = main.isAlive();
//显示是否为守护线程
boolean isDeamon = main.isDaemon();
//显示线程是否被中断
boolean isInterrupted = main.isInterrupted();
System.out.println("是否处于活动状态:"+isAlive+",是否为守护线程:"+isDeamon+",是否被中断:"+isInterrupted);
}
}
result:
Name:main ID:1 Priority:5
是否处于活动状态:true,是否为守护线程:false,是否被中断:false
sleep():是当前线程处于阻塞状态
public class SleepDome {
public static void main(String[] args) {
/**
* 当调用一个线程的interrupted方法后目的是中断该线程,
* 但若该线程正处于阻塞状态时,则会抛出中断异常,
* 此时并不是直接把该线程中断,而只是打断了其阻塞状态。
*/
final Thread lin = new Thread() {//jdk8以后不再要求被调用的局部内部类用final修饰
public void run() {
System.out.println("睡觉");
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
System.out.println("被吵醒");
}
System.out.println("不睡了");
}
};
System.out.println("lin线程的活动状态为:"+lin.isAlive());
Thread huang = new Thread() {
public void run() {
System.out.println("开始干活");
for(int i=1;i<=10;i++) {
try {
Thread.sleep(1000);
System.out.println("第"+i+"下");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("搞定收工");
System.out.println("调用lin线程的interrupt()方法");
lin.interrupt();
}
};
lin.start();
huang.start();
}
}
result:
lin线程的活动状态为:false
睡觉
开始干活
第1下
第2下
第3下
第4下
第5下
第6下
第7下
第8下
第9下
第10下
搞定收工
调用lin线程的interrupt()方法
被吵醒
不睡了
join():等待当前线程结束
import javax.management.RuntimeErrorException;
/** 线程提供了一个方法Join
* 该方法可以协调线程之间的同步运行关系。它可以是一个线程在另一个线程后面等待,直到该线程运行完毕再继续运行
*
* 同步运行:执行有先后顺序
* 异步运行:各自执行各自的,多线程就是异步运行
* @author mymuyi
*
*/
public class JoinDome {
protected static boolean isFinish;
public static void main(String[] args) {
Thread download = new Thread() {
public void run () {
System.out.println("down:开始下载图片...");
for(int i=1;i<=100;i++) {
System.out.println("down:"+i+"%");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("down:图片下载完成");
isFinish = true;
}
};
Thread show = new Thread() {
public void run() {
System.out.println("show:开始显示图片");
//等待download线程运行完毕
try {
download.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
if(! isFinish) {
throw new RuntimeException("图片加载失败");
}
System.out.println("show:图片加载完毕");
}
};
download.start();
show.start();
}
}
result:
show:开始显示图片
down:开始下载图片...
down:1%
down:2%
down:3%
down:4%
down:5%
down:6%
down:7%
down:8%
down:9%
down:10%
down:11%
down:12%
down:13%
down:14%
down:15%
down:16%
down:17%
down:18%
down:19%
down:20%
down:21%
down:22%
down:23%
down:24%
down:25%
down:26%
down:27%
down:28%
down:29%
down:30%
down:31%
down:32%
down:33%
down:34%
down:35%
down:36%
down:37%
down:38%
down:39%
down:40%
down:41%
down:42%
down:43%
down:44%
down:45%
down:46%
down:47%
down:48%
down:49%
down:50%
down:51%
down:52%
down:53%
down:54%
down:55%
down:56%
down:57%
down:58%
down:59%
down:60%
down:61%
down:62%
down:63%
down:64%
down:65%
down:66%
down:67%
down:68%
down:69%
down:70%
down:71%
down:72%
down:73%
down:74%
down:75%
down:76%
down:77%
down:78%
down:79%
down:80%
down:81%
down:82%
down:83%
down:84%
down:85%
down:86%
down:87%
down:88%
down:89%
down:90%
down:91%
down:92%
down:93%
down:94%
down:95%
down:96%
down:97%
down:98%
down:99%
down:100%
down:图片下载完成
show:图片加载完毕
setPriority():设置线程优先级
public class PriorityDome {
public static void main(String[] args) {
Thread max = new Thread() {
public void run() {
for (int i=0;i<100000;i++) {
System.out.println("max");
}
}
};
Thread nor = new Thread() {
public void run() {
for (int i=0;i<100000;i++) {
System.out.println("nor");
}
}
};
Thread min = new Thread() {
public void run() {
for (int i=0;i<100000;i++) {
System.out.println("min");
}
}
};
long a=System.currentTimeMillis();
min.setPriority(1);
nor.setPriority(5);
max.setPriority(10);
max.start();
nor.start();
max.start();
long b = System.currentTimeMillis();
System.out.println((b-a)+"ms");
}
}
result:这里因为测试数据较大,因此就不给出结果了
currentThread():该方法可以获取运行这个方法的线程
public class CurrentThreadDome {
public static void main(String[] args) {
Thread main = Thread.currentThread();
System.out.println("运行main方法的线程是:"+main);
dosome();
Thread t = new Thread() {
public void run() {
Thread t = Thread.currentThread();
System.out.println("自定义线程"+t);
dosome();
}
};
t.start();
}
public static void dosome() {
Thread t = Thread.currentThread();
System.out.println("运行dosome方法的线程是:"+t);
}
}
setDaemon(boolean a):将线程设置为守护线程
public class DemonThreadDome {
public static void main(String[] args) {
Thread t1 = new Thread() {
public void run() {
for (int i=0;i<25;i++) {
System.out.println("t1线程开始执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t1线程准备结束");
System.out.println("t1线程结束执行");//当t1线程结束时,守护线程t2也会跟着t1结束
}
};
Thread t2 = new Thread() {
public void run() {
while(true) {
System.out.println("t2线程正在执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t1.start();
t2.setDaemon(true);//将线程设置为守护线程
t2.start();
}}
-
多线程并发安全:
当多个线程并发操作同一资源时,由于线程切换时机不确定,会导致操作资源的代码执行顺序未按设计要求执行,导致出现操作混乱严重时会导致系统瘫痪
public class SyncDome {
public static void main(String[] args) {
final Table T = new Table();
Thread t1 = new Thread() {
public void run() {
while(true) {
int bean = T.getBean();
Thread.yield();
System.out.println(getName()+":"+bean);
}
}
};
Thread t2 =new Thread() {
public void run() {
while(true) {
int bean = T.getBean();
Thread.yield();
System.out.println(getName()+":"+bean);
}
}
};
t1.start();
t2.start();
}
}
class Table {
private int beans = 20;
/*
* 当一个方法使用synchronize修饰后,该方法即为同步方法,即:多个线程不能同时在方法内部执行。
* 将多个线程异步操作一个资源改为同步排队操作就不会出现并发安全问题了。
* 在方法上使用synchronize,那么同步监视器对象就是当前方法所属对象,即:this
*/
public int getBean() {
if(beans == 0) {
throw new RuntimeException("没有豆子了");
}
Thread.yield();//主动让出CPU,模拟线程发生切换
return beans-- ;
}
}
而上面这段代码运行起来会出现两个结果:
第一种:
Thread-1:19
Thread-0:20
Thread-1:18
Thread-0:17
Thread-0:15
Thread-0:14
Thread-0:13
Thread-0:12
Thread-0:11
Thread-0:10
Thread-0:9
Thread-0:8
Thread-0:7
Thread-0:6
Thread-0:5
Thread-1:16
Thread-0:4
Thread-0:2
Thread-0:1
Thread-1:3
Exception in thread "Thread-0" Exception in thread "Thread-1" java.lang.RuntimeException: 没有豆子了
at javaSe.day08.Thread2.Table.getBean(SyncDome.java:67)
at javaSe.day08.Thread2.SyncDome$1.run(SyncDome.java:18)
java.lang.RuntimeException: 没有豆子了
at javaSe.day08.Thread2.Table.getBean(SyncDome.java:67)
at javaSe.day08.Thread2.SyncDome$2.run(SyncDome.java:27)
这种结果是正确的;
第二种结果就是由于多个线程抢占同一资源时造成了代码运行错误,由于这种错误不能以Ctrl+C和Ctrl+V的形式展现出来所以我就以图片的形式向大家展示:
当然为了避免这种情况,在Java中出现了一种新的机制来处理这种问题。
锁机制:
synchronize修饰的方法
public class SyncDome {
public static void main(String[] args) {
final Table T = new Table();
Thread t1 = new Thread() {
public void run() {
while(true) {
int bean = T.getBean();
Thread.yield();
System.out.println(getName()+":"+bean);
}
}
};
Thread t2 =new Thread() {
public void run() {
while(true) {
int bean = T.getBean();
Thread.yield();
System.out.println(getName()+":"+bean);
}
}
};
t1.start();
t2.start();
}
}
class Table {
private int beans = 20;
public synchronized int getBean() {
if(beans == 0) {
throw new RuntimeException("没有豆子了");
}
Thread.yield();//主动让出CPU,模拟线程发生切换
return beans-- ;
}
}
result:
Thread-0:19
Thread-1:20
Thread-1:17
Thread-0:18
Thread-1:16
Thread-1:14
Thread-1:13
Thread-0:15
Thread-0:11
Thread-0:10
Thread-1:12
Thread-0:9
Thread-0:7
Thread-1:8
Thread-0:6
Thread-0:4
Thread-0:3
Thread-0:2
Thread-0:1
Thread-1:5
Exception in thread "Thread-0" Exception in thread "Thread-1" java.lang.RuntimeException: 没有豆子了
at Table.getBean(SyncDome.java:46)
at SyncDome$2.run(SyncDome.java:27)
java.lang.RuntimeException: 没有豆子了
at Table.getBean(SyncDome.java:46)
at SyncDome$1.run(SyncDome.java:18
synchronize修饰代码块
public class syncDome2 {
public static void main(String[] args) {
final Shop shop = new Shop();
Thread t1 = new Thread() {
public void run() {
shop.buy();
}
};
Thread t2 = new Thread() {
public void run() {
shop.buy();
}
};
t1.start();
t2.start();
}
}
class Shop{
//public synchronized void buy() {
public void buy() {
Thread t = Thread.currentThread();
try {
System.out.println(t.getName()+":正在挑衣服");
Thread.sleep(5000);
/*
* 同步块语法:
* synchronize(同步监听对象){
* 需要同步运行的代码
* }
* 若希望多个线程同步块中的代码,则必须保证多个线程看到的同步监视器对象是同一个
* 同步监视器对象可以是Java的任何对象,结合实际情况自行挑选,只要保证多个线程看到的是同一个对象即可
*/
synchronized (this) {
System.out.println(t.getName()+":正在试衣服");
Thread.sleep(5000);
}
System.out.println(t.getName()+":结账离开");
} catch (Exception e) {
}
}
}
result:
Thread-0:正在挑衣服
Thread-1:正在挑衣服
Thread-0:正在试衣服
Thread-1:正在试衣服
Thread-0:结账离开
Thread-1:结账离开
静态方法锁
public class syncDome4 {
public static void main(String[] args) {
Boo boo = new Boo();
Thread t1 = new Thread() {
public void run() {
boo.methodA();
}
};
Thread t2 = new Thread() {
public void run() {
boo.methodB();
}
};
t1.start();
t2.start();
}
}
class Boo{
public synchronized static void methodA() {
Thread t = Thread.currentThread();
try {
System.out.println(t.getName()+"正在运行A方法");
t.sleep(3000);
System.out.println(t.getName()+"A方法运行完毕");
} catch (Exception e) {
// TODO: handle exception
}
}
public synchronized static void methodB() {
Thread t = Thread.currentThread();
try {
System.out.println(t.getName()+"正在运行B方法");
t.sleep(3000);
System.out.println(t.getName()+"B方法运行完毕");
} catch (Exception e) {
// TODO: handle exception
}
}
}
result:
Thread-1正在运行B方法
Thread-1B方法运行完毕
Thread-0正在运行A方法
Thread-0A方法运行完毕
-
线程的结束:
1.正常结束
2.线程中断
3.抛出未处理异常
好了,今天的线程就大概写到这了,后续我会继续更新的