线程
进程
操作系统级别独立运行的任务,包含了某些资源的内存区域,
线程
进程所包含的而一个或多个执行单元称为线程,一个进程中至少有一个线程,没有线程,则进程无意义,操作系统会结束进程.
线程的使用场合
* 在一个程序中需要同时完成多个任务的情况,将每个任务定义为一个线程;
* 也可以用于单一线程可以完成,到那时使用多线程可以更快的情况,比如下载文件.
并发原理
多个线程同时运行只是感官上的表现,事实上线程是并发的,OS将时间划分为多个时间片段,尽可能均匀分
配给每一个线程,获取时间片段的线程被CPU运行,而其他线程都是等待,微观上表现为走走停停,宏观上表
现为都在运行,该现象称为并发,但不是实际意义上的同时发生.
线程创建的两种方式:
1:继承Thread类,并重写run方法来定义要运行的任务
class MyThread1 extends Thread {
@Override
public void run() {
for(int i = 0 ;i < 1000 ; i++){
System.out.println("哈哈哈hahahaha");
}
}
}
class MyThread2 extends Thread {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("啊啊啊");
}
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
Thread t1 = new MyThread1();
Thread t2 = new MyThread2();
/*
* 启动线程要调用线程的start方法,而不是直接调用run方法.
* start方法会将当前线程纳入调度线程,使线程可以并发运行.
* 当start方法执行后,该线程的run()方法可以很快的自动执行.
*/
t1.start();
t2.start();
System.out.println(t1.getName());
}
}
第一种创建线程的方式结构简单,有利于使用匿名内部类创建
但是也存在明显的不足:
1:由于要继承Thread类,而Java本身是单继承,这就导致如果继承Thread类就不能继承其他类,这在实际开发中会有诸多不便.
2:继承Thread类后需要重写run方法来定义线程要执行的任务,这就导致线程与任务存在了一个必然的偶然关系,不利于线程的重用
/*
* 继承Thread,匿名内部类形式
*/
new Thread() { //1,new 类(){}继承这个类
public void run() { //2,重写run方法
for(int i = 0; i < 3000; i++) { //3,将要执行的代码,写在run方法中
System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaa");
}
}
}.start();
2:实现Runnable接口,单独定义线程任务
class MyRunnable1 implements Runnable {
public void run(){
for(int i = 0; i< 1000 ;i++){
System.out.println("who are you?");
}
}
}
class MyRunnable2 implements Runnable {
public void run(){
for(int i = 0; i < 1000 ;i++){
System.out.println("I'm a student!");
}
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
//单独创建两个任务
Runnable r1 = new MyRunnable1();
Runnable r2 = new MyRunnable2();
//创建线程
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
}
/*
* 实现Runnable接口,匿名内部类形式
*
*/
new Thread(new Runnable(){ //1,new 接口(){}实现这个接口
public void run() { //2,重写run方法
for(int i = 0; i < 3000; i++) { //3,将要执行的代码,写在run方法中
System.out.println("bb");
}
}
}).start();
匿名内部类的两种创建
/*
* 继承Thread的匿名内部类形式
*/
Thread t1 = new Thread(){
public void run(){
for(int i = 0 ;i < 1000 ;i++){
System.out.println("你是谁啊?");
}
}
};
/*
* 实现Runnable接口的匿名内部类形式
*/
Runnable r2 = new Runnable() {
public void run() {
for(int i = 0 ; i< 1000 ; i++){
System.out.println("我是你爹啊!");
}
}
};
Thread t2 = new Thread(r2);
static Thread currentThread()
线程提供了一个静态方法:该方法可以获取到运行这个方法的线程
在ThreadLocal这个API中就要利用这个方法完成操作.而ThreadLocal在后期有很多应用,比如Spring利用AOP完成事务控制时就要使用.
public class CurrentThread {
public static void main(String[] args) {
//获取main方法的线程
Thread main = Thread.currentThread();
System.out.println("运行main方法的线程为:"+main);//Thread[main,5,main]
dosome();//Thread[main,5,main]
Thread thread = new Thread(){
@Override
public void run() {
Thread t = Thread.currentThread();
System.out.println("自定义线程为:"+t);//Thread[Thread-0,5,main]
dosome();//Thread[Thread-0,5,main]
}
};
thread.start();
}
public static void dosome(){
Thread dosome = Thread.currentThread();
System.out.println("运行dosome方法的线程为:"+dosome);
}
}
获取线程信息的相关方法
long getId() 获取线程唯一标识
String getName() 获取线程的名字
int getPriority() 获取线程优先级==默认值5,1-10;1==最低优先级,10==最高优先级,理论上线程优先级越高,获取CPU时间片的几率越高
setPriority() 设置线程优先级
boolean isAlive()
boolean isDaemon() 是否为守护线程
boolean isInterrupted() 是否被强制中断
min.setPriority(Thread.MIN_PRIORITY);
max.setPriority(Thread.MAX_PRIORITY);
线程阻塞static void sleep(long ms)
该方法会将运行该方法的线程置于阻塞状态指定毫秒.当超时后,线程会重新回到RUNNABLE状态,等待获取 CPU时间片,再次并发运行.
当一个线程调用sleep()方法后进入阻塞状态,在这个过程中若被其他线程调用了该线程的interrput方法中断时,这时并非直接将该线程中断,而是会打断其睡眠阻塞,这时候sleep方法会抛出中断异常.
所以,当一个线程处于阻塞时,interrput方法是中断阻塞状态,否则是中断该线程.
/*
* JDK1.8之前,由于jvm内存分配问题,有一个要求,
* 当一个方法中的局部变量(包括该方法参数)若被这个方法的其他局部内部类所引用时,
* 该变量必须被声明为final
*/
Thread lin = new Thread() {
@Override
public void run() {
System.out.println("林:刚美容完,睡一觉!");
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
System.out.println("林:干嘛呢!干嘛呢!干嘛呢!干嘛呢!破了相了!");
}
System.out.println("林:醒了!");
}
};
Thread huang = new Thread(){
@Override
public void run() {
System.out.println("黄:开始砸墙!");
for(int i = 0 ; i < 5 ;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println("80!");
}
System.out.println("黄:搞定!");
lin.interrupt();//调用lin的强制中断,中断lin的sleep,捕获InterruptedException异常
}
};
lin.start();
huang.start();
守护线程===必须在线程启动之前设置setDaemon(true)
守护线程也称为后台线程,默认创建的线程都不是守护线程,也可称为默认创建的线程为普通线程或前台线程.
守护线程在使用上与普通线程一样,但是在结束实际上有一点不同,即:进程结束时
进程的结束:当一个进程中的所有普通线程都结束时,进程就会结束,这时进程中所有运行的守护线程都会被强制中断.
主线程也是普通线程,GC就是运行在守护线程上
守护线程是不需要关心什么时候结束的,只需要满足其他结束跟着结束的线程就设置为守护线程
Thread rose = new Thread() {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("rose:let me die!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
System.out.println("rose:aaAAAAAAAAAAAAAAAaaaaa.........");
System.out.println("rose:噗通~~~~~~~~");
}
};
Thread jack = new Thread() {
@Override
public void run() {
while (true) {
System.out.println("jack:you jump ,i jump!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
}
};
rose.start();
jack.setDaemon(true);
jack.start();
//while(true){}
main线程是普通线程,第一个结束的线程是main线程(普通线程)----rose线程(普通线程)----jack线程(守护线程)
线程的同步运行===join
该方法可以使一个线程在另一个线程上等待(置于阻塞状态),知道其完成后再继续运行
join可以协调线程之间的同步运行
同步:执行有先后顺序的称为同步运行
异步:执行无先后顺序 ,通常并发运行代码就使异步运行
//表示图片是否下载完毕
private static boolean isFinish = false;
final Thread download = new Thread(){
@Override
public void run() {
System.out.println("download:开始下载图片!");
for(int i = 0 ;i < 100 ; i++){
System.out.println("download:"+i + "%");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
}
System.out.println("download:图片下载完成!");
isFinish = true;
}
};
Thread show = new Thread(){
@Override
public void run() {
System.out.println("show:开始加载图片...");
//加载前先等待下载线程将图片下载完毕
try {
/*
* 当show线程执行download.join()方法后,show线程处于阻塞状态,知道download线程停止后才会解除阻塞
*/
download.join();//download加入show,show处于阻塞,相当于把show加到download后排队
} catch (InterruptedException e) {
e.printStackTrace();
}
//图片若没有下载完毕则抛出异常
if(!isFinish){
/*
* 当一个异常抛到run方法之外,意味着这个线程就停止了.(实际开发中尽量避免这也做)
* 终止线程的三种方法:
* 1:结束,2:interrput,3:往run抛异常
*/
throw new RuntimeException("图片下载失败!!!!!!!!");//非检查异常,编译器不会提示处理
}
System.out.println("show:图片加载完成!");
}
};
download.start();
show.start();
并发存在的问题
当多个线程并发操作同一个资源时,由于线程切换实际不可控,会导致操作逻辑执行顺序出现混乱,严重时导致系统瘫痪.
当一个方法被synchronized修饰后,该方法编程同步方法,即:多个线程无法同时再该方法内部运行,将并发操作修改为同步操作可以解决并发安全问题
class Table {
private int beans = 30;
/*
* 当一个方法被synchronized修饰后,该方法编程同步方法,
* 即:多个线程无法同时再该方法内部运行,将并发操作修改为同步操作可以解决并发安全问题
*/
public synchronized int getBean(){
if(beans == 0){
throw new RuntimeException("没有豆子了!");
}
/*
* 当一个线程执行到Thread的静态方法yield()后,该线程会主动放弃本次CPU时间片,回到RUNNABLE状态,
* 等待线程调度再次分配时间片.
* 注:线程没有方法可以主动获取时间片,但可以主动放弃时间片
*/
Thread.yield();//礼让线程,让出当前时间
return beans--;
}
}
同步块
有效的缩小同步范围可以保证并发安全的前提下尽可能的提高并发效率.
同步块可以更准确的控制需要同步运行的代码片段
语法:
synchronized(同步监视器对象) {
需要同步运行的代码片段
}
使用同步块时需要注意,必须保证多个线程看到的同步监视器对象时同一个时,这些线程运行里面的代码才是同步的.
在方法上加synchronized,那么同步监视器对象就是当前方法所属对象,即:方法中看到的this
class Shop {
public void buy(){
Thread t = Thread.currentThread();
try {
System.out.println(t.getName() + ":正在挑衣服");
Thread.sleep(5000);
synchronized (this) {//this是Shop的实例
System.out.println(t.getName() + ":正在试衣服");
Thread.sleep(5000);
}
System.out.println(t.getName() + ":正在结账");
} catch (Exception e) {
}
}
}
静态方法若使用synchronized修饰,那么该方法一定具有同步效果.静态方法所使用的同步监视器对象为其方法所属类的类对象.
类对象,即:Class的一个实例,每个类都有且只有一个Class实例与之对应
class Boo {
public synchronized static void dosome(){
Thread t = Thread.currentThread();
try {
System.out.println(t.getName() + "正在运行dosome");
Thread.sleep(5000);
System.out.println(t.getName() + "dosome运行结束");
}catch(Exception e){
}
}
}
互斥锁
当Synchronized修饰多段代码,但是他们的同步监视器对象是同一个时,这些代码之间就是互斥的.即:多个线程不同同时运行这些方法.(具有互斥就看是不是具有同一个监视器)
public class SyncDemo4 {
public static void main(String[] args) {
final Foo foo = new Foo();
Thread t1 = new Thread(new Runnable() {
public void run() {
foo.methodA();
}
});
Thread t2 = new Thread(new Runnable() {
public void run() {
foo.methodB();
}
});
t1.start();
t2.start();
}
}
class Foo {
public synchronized void methodA() {
try {
Thread t = Thread.currentThread();
System.out.println(t.getName() + "正在运行A方法...");
Thread.sleep(5000);
System.out.println(t.getName() + "A方法运行完毕!");
} catch (Exception e) {
}
}
public synchronized void methodB() {
try {
Thread t = Thread.currentThread();
System.out.println(t.getName() + "正在运行B方法...");
Thread.sleep(5000);
System.out.println(t.getName() + "B方法运行完毕!");
} catch (Exception e) {
}
}
}